Skip to content

Commit bace2da

Browse files
authored
Gas optimizations (#277)
1 parent 8e9c840 commit bace2da

28 files changed

+301
-249
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,4 @@ jobs:
4141
with:
4242
path: "**/node_modules"
4343
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
44-
- run: yarn
45-
- run: yarn build
46-
- run: yarn hardhat codesize --contractname GnosisSafe
47-
- run: yarn benchmark
44+
- run: (yarn && yarn build && yarn hardhat codesize --contractname GnosisSafe && yarn benchmark) || echo "Benchmark failed"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ yarn hardhat --network <network> etherscan-verify
6060
Documentation
6161
-------------
6262
- [Safe developer portal](http://docs.gnosis.io/safe)
63+
- [Error codes](docs/error_codes.md)
6364
- [Coding guidelines](docs/guidelines.md)
6465

6566
Audits/ Formal Verification

contracts/GnosisSafe.sol

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ contract GnosisSafe
2121

2222
using GnosisSafeMath for uint256;
2323

24-
string public constant NAME = "Gnosis Safe";
2524
string public constant VERSION = "1.2.0";
2625

2726
// keccak256(
@@ -117,7 +116,7 @@ contract GnosisSafe
117116
function execTransaction(
118117
address to,
119118
uint256 value,
120-
bytes memory data,
119+
bytes calldata data,
121120
Enum.Operation operation,
122121
uint256 safeTxGas,
123122
uint256 baseGas,
@@ -155,7 +154,7 @@ contract GnosisSafe
155154
}
156155
// We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500)
157156
// 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
158-
require(gasleft() >= (safeTxGas * 64 / 63).max(safeTxGas + 2500) + 500, "Not enough gas to execute safe transaction");
157+
require(gasleft() >= (safeTxGas * 64 / 63).max(safeTxGas + 2500) + 500, "GS010");
159158
// Use scope here to limit variable lifetime and prevent `stack too deep` errors
160159
{
161160
uint256 gasUsed = gasleft();
@@ -189,10 +188,10 @@ contract GnosisSafe
189188
// For ETH we will only adjust the gas price to not be higher than the actual used gas price
190189
payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice);
191190
// solium-disable-next-line security/no-send
192-
require(receiver.send(payment), "Could not pay gas costs with ether");
191+
require(receiver.send(payment), "GS011");
193192
} else {
194193
payment = gasUsed.add(baseGas).mul(gasPrice);
195-
require(transferToken(gasToken, receiver, payment), "Could not pay gas costs with token");
194+
require(transferToken(gasToken, receiver, payment), "GS012");
196195
}
197196
}
198197

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

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

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

245244
// Check signature
246245
bytes memory contractSignature;
@@ -249,13 +248,13 @@ contract GnosisSafe
249248
// The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s
250249
contractSignature := add(add(signatures, s), 0x20)
251250
}
252-
require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "Invalid contract signature provided");
251+
require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "GS024");
253252
// If v is 1 then it is an approved hash
254253
} else if (v == 1) {
255254
// When handling approved hashes the address of the approver is encoded into r
256255
currentOwner = address(uint160(uint256(r)));
257256
// Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction
258-
require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "Hash has not been approved");
257+
require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "GS025");
259258
} else if (v > 30) {
260259
// To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover
261260
currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v - 4, r, s);
@@ -265,7 +264,7 @@ contract GnosisSafe
265264
}
266265
require (
267266
currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS,
268-
"Invalid owner provided"
267+
"GS026"
269268
);
270269
lastOwner = currentOwner;
271270
}
@@ -300,7 +299,7 @@ contract GnosisSafe
300299
function approveHash(bytes32 hashToApprove)
301300
external
302301
{
303-
require(owners[msg.sender] != address(0), "Only owners can approve a hash");
302+
require(owners[msg.sender] != address(0), "GS030");
304303
approvedHashes[msg.sender][hashToApprove] = 1;
305304
emit ApproveHash(hashToApprove, msg.sender);
306305
}
@@ -366,7 +365,7 @@ contract GnosisSafe
366365
function encodeTransactionData(
367366
address to,
368367
uint256 value,
369-
bytes memory data,
368+
bytes calldata data,
370369
Enum.Operation operation,
371370
uint256 safeTxGas,
372371
uint256 baseGas,
@@ -400,7 +399,7 @@ contract GnosisSafe
400399
function getTransactionHash(
401400
address to,
402401
uint256 value,
403-
bytes memory data,
402+
bytes calldata data,
404403
Enum.Operation operation,
405404
uint256 safeTxGas,
406405
uint256 baseGas,

contracts/accessors/SimulateTxAccessor.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// SPDX-License-Identifier: LGPL-3.0-only
12
pragma solidity >=0.7.0 <0.9.0;
23

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

12-
constructor() public {
13+
constructor() {
1314
guard = GUARD_VALUE;
1415
}
1516

contracts/base/ModuleManager.sol

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ contract ModuleManager is SelfAuthorized, Executor {
2222
function setupModules(address to, bytes memory data)
2323
internal
2424
{
25-
require(modules[SENTINEL_MODULES] == address(0), "Modules have already been initialized");
25+
require(modules[SENTINEL_MODULES] == address(0), "GS100");
2626
modules[SENTINEL_MODULES] = SENTINEL_MODULES;
2727
if (to != address(0))
2828
// Setup has to complete successfully or transaction fails.
29-
require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "Could not finish initialization");
29+
require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000");
3030
}
3131

3232
/// @dev Allows to add a module to the whitelist.
@@ -38,9 +38,9 @@ contract ModuleManager is SelfAuthorized, Executor {
3838
authorized
3939
{
4040
// Module address cannot be null or sentinel.
41-
require(module != address(0) && module != SENTINEL_MODULES, "Invalid module address provided");
41+
require(module != address(0) && module != SENTINEL_MODULES, "GS101");
4242
// Module cannot be added twice.
43-
require(modules[module] == address(0), "Module has already been added");
43+
require(modules[module] == address(0), "GS102");
4444
modules[module] = modules[SENTINEL_MODULES];
4545
modules[SENTINEL_MODULES] = module;
4646
emit EnabledModule(module);
@@ -56,8 +56,8 @@ contract ModuleManager is SelfAuthorized, Executor {
5656
authorized
5757
{
5858
// Validate module address and check that it corresponds to module index.
59-
require(module != address(0) && module != SENTINEL_MODULES, "Invalid module address provided");
60-
require(modules[prevModule] == module, "Invalid prevModule, module pair provided");
59+
require(module != address(0) && module != SENTINEL_MODULES, "GS101");
60+
require(modules[prevModule] == module, "GS103");
6161
modules[prevModule] = modules[module];
6262
modules[module] = address(0);
6363
emit DisabledModule(module);
@@ -73,7 +73,7 @@ contract ModuleManager is SelfAuthorized, Executor {
7373
returns (bool success)
7474
{
7575
// Only whitelisted modules are allowed.
76-
require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "Method can only be called from an enabled module");
76+
require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104");
7777
// Execute transaction without further confirmations.
7878
success = execute(to, value, data, operation, gasleft());
7979
if (success) emit ExecutionFromModuleSuccess(msg.sender);

contracts/base/OwnerManager.sol

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ contract OwnerManager is SelfAuthorized {
2525
{
2626
// Threshold can only be 0 at initialization.
2727
// Check ensures that setup function can only be called once.
28-
require(threshold == 0, "Owners have already been setup");
28+
require(threshold == 0, "GS200");
2929
// Validate that threshold is smaller than number of added owners.
30-
require(_threshold <= _owners.length, "Threshold cannot exceed owner count");
30+
require(_threshold <= _owners.length, "GS201");
3131
// There has to be at least one Safe owner.
32-
require(_threshold >= 1, "Threshold needs to be greater than 0");
32+
require(_threshold >= 1, "GS202");
3333
// Initializing Safe owners.
3434
address currentOwner = SENTINEL_OWNERS;
3535
for (uint256 i = 0; i < _owners.length; i++) {
3636
// Owner address cannot be null.
3737
address owner = _owners[i];
38-
require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "Invalid owner address provided");
38+
require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203");
3939
// No duplicate owners allowed.
40-
require(owners[owner] == address(0), "Duplicate owner address provided");
40+
require(owners[owner] == address(0), "GS204");
4141
owners[currentOwner] = owner;
4242
currentOwner = owner;
4343
}
@@ -56,9 +56,9 @@ contract OwnerManager is SelfAuthorized {
5656
authorized
5757
{
5858
// Owner address cannot be null, the sentinel or the Safe itself.
59-
require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "Invalid owner address provided");
59+
require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "GS203");
6060
// No duplicate owners allowed.
61-
require(owners[owner] == address(0), "Address is already an owner");
61+
require(owners[owner] == address(0), "GS204");
6262
owners[owner] = owners[SENTINEL_OWNERS];
6363
owners[SENTINEL_OWNERS] = owner;
6464
ownerCount++;
@@ -79,10 +79,10 @@ contract OwnerManager is SelfAuthorized {
7979
authorized
8080
{
8181
// Only allow to remove an owner, if threshold can still be reached.
82-
require(ownerCount - 1 >= _threshold, "New owner count needs to be larger than new threshold");
82+
require(ownerCount - 1 >= _threshold, "GS201");
8383
// Validate owner address and check that it corresponds to owner index.
84-
require(owner != address(0) && owner != SENTINEL_OWNERS, "Invalid owner address provided");
85-
require(owners[prevOwner] == owner, "Invalid prevOwner, owner pair provided");
84+
require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203");
85+
require(owners[prevOwner] == owner, "GS205");
8686
owners[prevOwner] = owners[owner];
8787
owners[owner] = address(0);
8888
ownerCount--;
@@ -103,12 +103,12 @@ contract OwnerManager is SelfAuthorized {
103103
authorized
104104
{
105105
// Owner address cannot be null, the sentinel or the Safe itself.
106-
require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "Invalid owner address provided");
106+
require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "GS203");
107107
// No duplicate owners allowed.
108-
require(owners[newOwner] == address(0), "Address is already an owner");
108+
require(owners[newOwner] == address(0), "GS204");
109109
// Validate oldOwner address and check that it corresponds to owner index.
110-
require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "Invalid owner address provided");
111-
require(owners[prevOwner] == oldOwner, "Invalid prevOwner, owner pair provided");
110+
require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "GS203");
111+
require(owners[prevOwner] == oldOwner, "GS205");
112112
owners[newOwner] = owners[oldOwner];
113113
owners[prevOwner] = newOwner;
114114
owners[oldOwner] = address(0);
@@ -125,9 +125,9 @@ contract OwnerManager is SelfAuthorized {
125125
authorized
126126
{
127127
// Validate that threshold is smaller than number of owners.
128-
require(_threshold <= ownerCount, "Threshold cannot exceed owner count");
128+
require(_threshold <= ownerCount, "GS201");
129129
// There has to be at least one Safe owner.
130-
require(_threshold >= 1, "Threshold needs to be greater than 0");
130+
require(_threshold >= 1, "GS202");
131131
threshold = _threshold;
132132
emit ChangedThreshold(threshold);
133133
}

contracts/common/SecuredTokenTransfer.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ contract SecuredTokenTransfer {
1818
internal
1919
returns (bool transferred)
2020
{
21-
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", receiver, amount);
21+
// 0xa9059cbb - keccack("transfer(address,uint256)")
22+
bytes memory data = abi.encodeWithSelector(0xa9059cbb, receiver, amount);
2223
// solium-disable-next-line security/no-inline-assembly
2324
assembly {
2425
let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0)

contracts/common/SelfAuthorized.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pragma solidity >=0.7.0 <0.9.0;
66
/// @author Richard Meissner - <richard@gnosis.pm>
77
contract SelfAuthorized {
88
function requireSelfCall() private view {
9-
require(msg.sender == address(this), "Method can only be called from this contract");
9+
require(msg.sender == address(this), "GS031");
1010
}
1111

1212
modifier authorized() {

contracts/common/StorageAccessible.sol

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ pragma solidity >=0.7.0 <0.9.0;
44
/// @title StorageAccessible - generic base contract that allows callers to access all internal storage.
55
/// @notice Adjusted version of https://github.com/gnosis/util-contracts/blob/3db1e531cb243a48ea91c60a800d537c1000612a/contracts/StorageAccessible.sol
66
contract StorageAccessible {
7-
bytes4 internal constant SIMULATE_DELEGATECALL_INTERNAL_SELECTOR = bytes4(
8-
keccak256("simulateDelegatecallInternal(address,bytes)")
9-
);
10-
117
/**
128
* @dev Reads `length` bytes of storage in the currents contract
139
* @param offset - the offset in the current contract's storage in words to start reading from
@@ -29,59 +25,24 @@ contract StorageAccessible {
2925
return result;
3026
}
3127

32-
/**
33-
* @dev Performs a delegetecall on a targetContract in the context of self.
34-
* Internally reverts execution to avoid side effects (making it static). Catches revert and returns encoded result as bytes.
35-
* @param targetContract Address of the contract containing the code to execute.
36-
* @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).
37-
*/
38-
function simulateDelegatecall(
39-
address targetContract,
40-
bytes memory calldataPayload
41-
) public returns (bytes memory) {
42-
bytes memory innerCall = abi.encodeWithSelector(
43-
SIMULATE_DELEGATECALL_INTERNAL_SELECTOR,
44-
targetContract,
45-
calldataPayload
46-
);
47-
(, bytes memory response) = address(this).call(innerCall);
48-
bool innerSuccess = response[response.length - 1] == 0x01;
49-
setLength(response, response.length - 1);
50-
if (innerSuccess) {
51-
return response;
52-
} else {
53-
revertWith(response);
54-
}
55-
}
56-
5728
/**
5829
* @dev Performs a delegetecall on a targetContract in the context of self.
5930
* Internally reverts execution to avoid side effects (making it static). Returns encoded result as revert message
6031
* concatenated with the success flag of the inner call as a last byte.
6132
* @param targetContract Address of the contract containing the code to execute.
6233
* @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).
6334
*/
64-
function simulateDelegatecallInternal(
35+
function simulate(
6536
address targetContract,
66-
bytes memory calldataPayload
67-
) public returns (bytes memory) {
37+
bytes calldata calldataPayload
38+
) external returns (bytes memory) {
6839
(bool success, bytes memory response) = targetContract.delegatecall(
6940
calldataPayload
7041
);
71-
revertWith(abi.encodePacked(response, success));
72-
}
73-
74-
function revertWith(bytes memory response) internal pure {
75-
// solium-disable-next-line security/no-inline-assembly
76-
assembly {
77-
revert(add(response, 0x20), mload(response))
78-
}
79-
}
80-
81-
function setLength(bytes memory buffer, uint256 length) internal pure {
42+
bytes memory responseWithStatus = abi.encodePacked(response, success);
8243
// solium-disable-next-line security/no-inline-assembly
8344
assembly {
84-
mstore(buffer, length)
45+
revert(add(responseWithStatus, 0x20), mload(responseWithStatus))
8546
}
8647
}
8748
}

0 commit comments

Comments
 (0)