Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gas optimizations #277

Merged
merged 7 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,4 @@ jobs:
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- run: yarn
- run: yarn build
- run: yarn hardhat codesize --contractname GnosisSafe
- run: yarn benchmark
- run: (yarn && yarn build && yarn hardhat codesize --contractname GnosisSafe && yarn benchmark) || echo "Benchmark failed"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ yarn hardhat --network <network> etherscan-verify
Documentation
-------------
- [Safe developer portal](http://docs.gnosis.io/safe)
- [Error codes](docs/error_codes.md)
- [Coding guidelines](docs/guidelines.md)

Audits/ Formal Verification
Expand Down
31 changes: 15 additions & 16 deletions contracts/GnosisSafe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ contract GnosisSafe

using GnosisSafeMath for uint256;

string public constant NAME = "Gnosis Safe";
string public constant VERSION = "1.2.0";

// keccak256(
Expand Down Expand Up @@ -117,7 +116,7 @@ contract GnosisSafe
function execTransaction(
address to,
uint256 value,
bytes memory data,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
Expand Down Expand Up @@ -155,7 +154,7 @@ contract GnosisSafe
}
// We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500)
// We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150
require(gasleft() >= (safeTxGas * 64 / 63).max(safeTxGas + 2500) + 500, "Not enough gas to execute safe transaction");
require(gasleft() >= (safeTxGas * 64 / 63).max(safeTxGas + 2500) + 500, "GS010");
// Use scope here to limit variable lifetime and prevent `stack too deep` errors
{
uint256 gasUsed = gasleft();
Expand Down Expand Up @@ -189,10 +188,10 @@ contract GnosisSafe
// For ETH we will only adjust the gas price to not be higher than the actual used gas price
payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice);
// solium-disable-next-line security/no-send
require(receiver.send(payment), "Could not pay gas costs with ether");
require(receiver.send(payment), "GS011");
} else {
payment = gasUsed.add(baseGas).mul(gasPrice);
require(transferToken(gasToken, receiver, payment), "Could not pay gas costs with token");
require(transferToken(gasToken, receiver, payment), "GS012");
}
}

Expand All @@ -209,9 +208,9 @@ contract GnosisSafe
// Load threshold to avoid multiple storage loads
uint256 _threshold = threshold;
// Check that a threshold is set
require(_threshold > 0, "Threshold needs to be defined!");
require(_threshold > 0, "GS001");
// Check that the provided signature data is not too short
require(signatures.length >= _threshold.mul(65), "Signatures data too short");
require(signatures.length >= _threshold.mul(65), "GS020");
// There cannot be an owner with address 0.
address lastOwner = address(0);
address currentOwner;
Expand All @@ -229,18 +228,18 @@ contract GnosisSafe
// Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes
// This check is not completely accurate, since it is possible that more signatures than the threshold are send.
// Here we only check that the pointer is not pointing inside the part that is being processed
require(uint256(s) >= _threshold.mul(65), "Invalid contract signature location: inside static part");
require(uint256(s) >= _threshold.mul(65), "GS021");

// Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes)
require(uint256(s).add(32) <= signatures.length, "Invalid contract signature location: length not present");
require(uint256(s).add(32) <= signatures.length, "GS022");

// Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length
uint256 contractSignatureLen;
// solium-disable-next-line security/no-inline-assembly
assembly {
contractSignatureLen := mload(add(add(signatures, s), 0x20))
}
require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "Invalid contract signature location: data not complete");
require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "GS023");

// Check signature
bytes memory contractSignature;
Expand All @@ -249,13 +248,13 @@ contract GnosisSafe
// The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s
contractSignature := add(add(signatures, s), 0x20)
}
require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "Invalid contract signature provided");
require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "GS024");
// If v is 1 then it is an approved hash
} else if (v == 1) {
// When handling approved hashes the address of the approver is encoded into r
currentOwner = address(uint160(uint256(r)));
// Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction
require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "Hash has not been approved");
require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "GS025");
} else if (v > 30) {
// To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover
currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v - 4, r, s);
Expand All @@ -265,7 +264,7 @@ contract GnosisSafe
}
require (
currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS,
"Invalid owner provided"
"GS026"
);
lastOwner = currentOwner;
}
Expand Down Expand Up @@ -300,7 +299,7 @@ contract GnosisSafe
function approveHash(bytes32 hashToApprove)
external
{
require(owners[msg.sender] != address(0), "Only owners can approve a hash");
require(owners[msg.sender] != address(0), "GS030");
approvedHashes[msg.sender][hashToApprove] = 1;
emit ApproveHash(hashToApprove, msg.sender);
}
Expand Down Expand Up @@ -366,7 +365,7 @@ contract GnosisSafe
function encodeTransactionData(
address to,
uint256 value,
bytes memory data,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
Expand Down Expand Up @@ -400,7 +399,7 @@ contract GnosisSafe
function getTransactionHash(
address to,
uint256 value,
bytes memory data,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
Expand Down
3 changes: 2 additions & 1 deletion contracts/accessors/SimulateTxAccessor.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

import "../base/Executor.sol";
Expand All @@ -9,7 +10,7 @@ contract SimulateTxAccessor is Executor {
bytes32 constant private GUARD_VALUE = keccak256("simulate_tx_accessor.guard.bytes32");
bytes32 guard;

constructor() public {
constructor() {
guard = GUARD_VALUE;
}

Expand Down
14 changes: 7 additions & 7 deletions contracts/base/ModuleManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ contract ModuleManager is SelfAuthorized, Executor {
function setupModules(address to, bytes memory data)
internal
{
require(modules[SENTINEL_MODULES] == address(0), "Modules have already been initialized");
require(modules[SENTINEL_MODULES] == address(0), "GS100");
modules[SENTINEL_MODULES] = SENTINEL_MODULES;
if (to != address(0))
// Setup has to complete successfully or transaction fails.
require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "Could not finish initialization");
require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000");
}

/// @dev Allows to add a module to the whitelist.
Expand All @@ -38,9 +38,9 @@ contract ModuleManager is SelfAuthorized, Executor {
authorized
{
// Module address cannot be null or sentinel.
require(module != address(0) && module != SENTINEL_MODULES, "Invalid module address provided");
require(module != address(0) && module != SENTINEL_MODULES, "GS101");
// Module cannot be added twice.
require(modules[module] == address(0), "Module has already been added");
require(modules[module] == address(0), "GS102");
modules[module] = modules[SENTINEL_MODULES];
modules[SENTINEL_MODULES] = module;
emit EnabledModule(module);
Expand All @@ -56,8 +56,8 @@ contract ModuleManager is SelfAuthorized, Executor {
authorized
{
// Validate module address and check that it corresponds to module index.
require(module != address(0) && module != SENTINEL_MODULES, "Invalid module address provided");
require(modules[prevModule] == module, "Invalid prevModule, module pair provided");
require(module != address(0) && module != SENTINEL_MODULES, "GS101");
require(modules[prevModule] == module, "GS103");
modules[prevModule] = modules[module];
modules[module] = address(0);
emit DisabledModule(module);
Expand All @@ -73,7 +73,7 @@ contract ModuleManager is SelfAuthorized, Executor {
returns (bool success)
{
// Only whitelisted modules are allowed.
require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "Method can only be called from an enabled module");
require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104");
// Execute transaction without further confirmations.
success = execute(to, value, data, operation, gasleft());
if (success) emit ExecutionFromModuleSuccess(msg.sender);
Expand Down
32 changes: 16 additions & 16 deletions contracts/base/OwnerManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ contract OwnerManager is SelfAuthorized {
{
// Threshold can only be 0 at initialization.
// Check ensures that setup function can only be called once.
require(threshold == 0, "Owners have already been setup");
require(threshold == 0, "GS200");
// Validate that threshold is smaller than number of added owners.
require(_threshold <= _owners.length, "Threshold cannot exceed owner count");
require(_threshold <= _owners.length, "GS201");
// There has to be at least one Safe owner.
require(_threshold >= 1, "Threshold needs to be greater than 0");
require(_threshold >= 1, "GS202");
// Initializing Safe owners.
address currentOwner = SENTINEL_OWNERS;
for (uint256 i = 0; i < _owners.length; i++) {
// Owner address cannot be null.
address owner = _owners[i];
require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "Invalid owner address provided");
require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203");
// No duplicate owners allowed.
require(owners[owner] == address(0), "Duplicate owner address provided");
require(owners[owner] == address(0), "GS204");
owners[currentOwner] = owner;
currentOwner = owner;
}
Expand All @@ -56,9 +56,9 @@ contract OwnerManager is SelfAuthorized {
authorized
{
// Owner address cannot be null, the sentinel or the Safe itself.
require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "Invalid owner address provided");
require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "GS203");
// No duplicate owners allowed.
require(owners[owner] == address(0), "Address is already an owner");
require(owners[owner] == address(0), "GS204");
owners[owner] = owners[SENTINEL_OWNERS];
owners[SENTINEL_OWNERS] = owner;
ownerCount++;
Expand All @@ -79,10 +79,10 @@ contract OwnerManager is SelfAuthorized {
authorized
{
// Only allow to remove an owner, if threshold can still be reached.
require(ownerCount - 1 >= _threshold, "New owner count needs to be larger than new threshold");
require(ownerCount - 1 >= _threshold, "GS201");
// Validate owner address and check that it corresponds to owner index.
require(owner != address(0) && owner != SENTINEL_OWNERS, "Invalid owner address provided");
require(owners[prevOwner] == owner, "Invalid prevOwner, owner pair provided");
require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203");
require(owners[prevOwner] == owner, "GS205");
owners[prevOwner] = owners[owner];
owners[owner] = address(0);
ownerCount--;
Expand All @@ -103,12 +103,12 @@ contract OwnerManager is SelfAuthorized {
authorized
{
// Owner address cannot be null, the sentinel or the Safe itself.
require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "Invalid owner address provided");
require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "GS203");
// No duplicate owners allowed.
require(owners[newOwner] == address(0), "Address is already an owner");
require(owners[newOwner] == address(0), "GS204");
// Validate oldOwner address and check that it corresponds to owner index.
require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "Invalid owner address provided");
require(owners[prevOwner] == oldOwner, "Invalid prevOwner, owner pair provided");
require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "GS203");
require(owners[prevOwner] == oldOwner, "GS205");
owners[newOwner] = owners[oldOwner];
owners[prevOwner] = newOwner;
owners[oldOwner] = address(0);
Expand All @@ -125,9 +125,9 @@ contract OwnerManager is SelfAuthorized {
authorized
{
// Validate that threshold is smaller than number of owners.
require(_threshold <= ownerCount, "Threshold cannot exceed owner count");
require(_threshold <= ownerCount, "GS201");
// There has to be at least one Safe owner.
require(_threshold >= 1, "Threshold needs to be greater than 0");
require(_threshold >= 1, "GS202");
threshold = _threshold;
emit ChangedThreshold(threshold);
}
Expand Down
3 changes: 2 additions & 1 deletion contracts/common/SecuredTokenTransfer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ contract SecuredTokenTransfer {
internal
returns (bool transferred)
{
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", receiver, amount);
// 0xa9059cbb - keccack("transfer(address,uint256)")
bytes memory data = abi.encodeWithSelector(0xa9059cbb, receiver, amount);
// solium-disable-next-line security/no-inline-assembly
assembly {
let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0)
Expand Down
2 changes: 1 addition & 1 deletion contracts/common/SelfAuthorized.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pragma solidity >=0.7.0 <0.9.0;
/// @author Richard Meissner - <[email protected]>
contract SelfAuthorized {
function requireSelfCall() private view {
require(msg.sender == address(this), "Method can only be called from this contract");
require(msg.sender == address(this), "GS031");
}

modifier authorized() {
Expand Down
49 changes: 5 additions & 44 deletions contracts/common/StorageAccessible.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ pragma solidity >=0.7.0 <0.9.0;
/// @title StorageAccessible - generic base contract that allows callers to access all internal storage.
/// @notice Adjusted version of https://github.com/gnosis/util-contracts/blob/3db1e531cb243a48ea91c60a800d537c1000612a/contracts/StorageAccessible.sol
contract StorageAccessible {
bytes4 internal constant SIMULATE_DELEGATECALL_INTERNAL_SELECTOR = bytes4(
keccak256("simulateDelegatecallInternal(address,bytes)")
);

/**
* @dev Reads `length` bytes of storage in the currents contract
* @param offset - the offset in the current contract's storage in words to start reading from
Expand All @@ -29,59 +25,24 @@ contract StorageAccessible {
return result;
}

/**
* @dev Performs a delegetecall on a targetContract in the context of self.
* Internally reverts execution to avoid side effects (making it static). Catches revert and returns encoded result as bytes.
* @param targetContract Address of the contract containing the code to execute.
* @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).
*/
function simulateDelegatecall(
address targetContract,
bytes memory calldataPayload
) public returns (bytes memory) {
bytes memory innerCall = abi.encodeWithSelector(
SIMULATE_DELEGATECALL_INTERNAL_SELECTOR,
targetContract,
calldataPayload
);
(, bytes memory response) = address(this).call(innerCall);
bool innerSuccess = response[response.length - 1] == 0x01;
setLength(response, response.length - 1);
if (innerSuccess) {
return response;
} else {
revertWith(response);
}
}

/**
* @dev Performs a delegetecall on a targetContract in the context of self.
* Internally reverts execution to avoid side effects (making it static). Returns encoded result as revert message
* concatenated with the success flag of the inner call as a last byte.
* @param targetContract Address of the contract containing the code to execute.
* @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).
*/
function simulateDelegatecallInternal(
function simulate(
address targetContract,
bytes memory calldataPayload
) public returns (bytes memory) {
bytes calldata calldataPayload
) external returns (bytes memory) {
(bool success, bytes memory response) = targetContract.delegatecall(
calldataPayload
);
revertWith(abi.encodePacked(response, success));
}

function revertWith(bytes memory response) internal pure {
// solium-disable-next-line security/no-inline-assembly
assembly {
revert(add(response, 0x20), mload(response))
}
}

function setLength(bytes memory buffer, uint256 length) internal pure {
bytes memory responseWithStatus = abi.encodePacked(response, success);
// solium-disable-next-line security/no-inline-assembly
assembly {
mstore(buffer, length)
revert(add(responseWithStatus, 0x20), mload(responseWithStatus))
}
}
}
Loading