Skip to content
This repository was archived by the owner on Oct 10, 2024. It is now read-only.

Commit 11e3788

Browse files
nostdmi-stammischat
authored
Controller Gas Proxy and CHI token integration (#605)
* Create gas token refundable and gas token proxy contracts * Add ENS set node method to the ENS resolvable interface * Add the the gas proxy contract to the build script and regenerate bindings * Add gas proxy tests and rename token_whitelist test directory to token-whitelist * Remove ENS from gasRefundable contract and set the default gas token to CHI * Run formatter on new contracts * Add gasProxy to CI and run tools * Create tests for the gas proxy and gas refundable contracts and add associated mock contracts * Rerun slither and formatter * Address pull request comments - merge set methods and other improvements * Add gasProxy binding * Revert controllable change and remove gas token address from constructor * Upgrade to ethertest v0.9.0 * Add executeTransaction tests and increase the test coverage * Add gas estimation tests and verify the amount of gas freed by the proxy * Update ethertest branch, update go version, remove unnecessary test function * Remove parens from refundGas modifier * Check the amount of gas refunded and amount of tokens burned * Add test for meta-transaction via gas proxy * Add test for ExecutedTransaction event emission * Renamed a variable ... this is a nothing operation * This fixes the slither tests, they broke because i changed a parameter name from returndata to returnData * Adjust gas cost values in the test suite Co-authored-by: nostdm <[email protected]> Co-authored-by: i-stam <[email protected]> Co-authored-by: Mischa Tuffield <[email protected]>
1 parent c1e8aa6 commit 11e3788

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+3852
-101
lines changed

.circleci/config.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
- image: mythril/myth@sha256:e78a96bd0f5e57fe45bcd1c744509932bee83706a00293e53a60e83cd0ab89f3
6363
resource_class: xlarge
6464
working_directory: /tmp/contracts
65-
parallelism: 7
65+
parallelism: 8
6666
steps:
6767
- checkout
6868
- run:
@@ -114,12 +114,19 @@ jobs:
114114
myth analyze --solv=0.5.17 ./controller.sol --execution-timeout=800 --max-depth=15 --parallel-solving
115115
fi
116116
no_output_timeout: 20m
117+
- run:
118+
working_directory: contracts
119+
command: |
120+
if [[ "${CIRCLE_NODE_INDEX}" == 7 ]]; then
121+
myth analyze --solv=0.5.17 ./gasProxy.sol --execution-timeout=800 --max-depth=15 --parallel-solving
122+
fi
123+
no_output_timeout: 20m
117124

118125
slither:
119126
docker:
120127
- image: trailofbits/eth-security-toolbox@sha256:3fb96e2d9de772f5e97f1c3c650c8a3d28660f8a64a60b76269da1ac19b86a28
121128
working_directory: /tmp/contracts
122-
parallelism: 6
129+
parallelism: 7
123130
steps:
124131
- run: sudo -n apt-get update && sudo -n apt-get install -y openssh-client
125132
- checkout
@@ -166,6 +173,13 @@ jobs:
166173
slither ../../contracts/walletDeployer.sol
167174
fi
168175
no_output_timeout: 5m
176+
- run:
177+
working_directory: tools/slither
178+
command: |
179+
if [[ "${CIRCLE_NODE_INDEX}" == 6 ]]; then
180+
slither ../../contracts/gasProxy.sol
181+
fi
182+
no_output_timeout: 5m
169183

170184
echidna:
171185
docker:

build.sh

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ contract_sources=(
1818
'tokenWhitelist'
1919
'walletDeployer'
2020
'walletCache'
21+
'gasProxy'
2122
'mocks/token'
2223
'mocks/burnerToken'
2324
'mocks/nonCompliantToken'
@@ -27,7 +28,9 @@ contract_sources=(
2728
'mocks/isValidSignatureExporter'
2829
'mocks/parseIntScientificExporter'
2930
'mocks/tokenWhitelistableExporter'
30-
'mocks/walletMock'
31+
'mocks/wallet'
32+
'mocks/gasToken'
33+
'mocks/gasBurner'
3134
'externals/ens/PublicResolver'
3235
'externals/ens/ENSRegistry'
3336
'externals/upgradeability/UpgradeabilityProxy'
@@ -59,6 +62,7 @@ contracts=(
5962
"tokenWhitelist/TokenWhitelist tokenWhitelist.go TokenWhitelist bindings"
6063
"walletDeployer/WalletDeployer walletDeployer.go WalletDeployer bindings"
6164
"walletCache/WalletCache walletCache.go WalletCache bindings"
65+
"gasProxy/GasProxy gasProxy.go GasProxy bindings"
6266
"mocks/token/Token mocks/token.go Token mocks"
6367
"mocks/burnerToken/BurnerToken mocks/burnerToken.go BurnerToken mocks"
6468
"mocks/nonCompliantToken/NonCompliantToken mocks/nonCompliantToken.go NonCompliantToken mocks"
@@ -69,7 +73,9 @@ contracts=(
6973
"mocks/isValidSignatureExporter/IsValidSignatureExporter mocks/isValidSignatureExporter.go IsValidSignatureExporter mocks"
7074
"mocks/parseIntScientificExporter/ParseIntScientificExporter mocks/parseIntScientificExporter.go ParseIntScientificExporter mocks"
7175
"mocks/tokenWhitelistableExporter/TokenWhitelistableExporter mocks/tokenWhitelistableExporter.go TokenWhitelistableExporter mocks"
72-
"mocks/walletMock/WalletMock mocks/walletMock.go WalletMock mocks"
76+
"mocks/wallet/Wallet mocks/wallet.go Wallet mocks"
77+
"mocks/gasToken/GasToken mocks/gasToken.go GasToken mocks"
78+
"mocks/gasBurner/GasBurner mocks/gasBurner.go GasBurner mocks"
7379
"externals/ens/ENSRegistry/ENSRegistry externals/ens/ENSRegistry.go ENSRegistry ens"
7480
"externals/ens/PublicResolver/PublicResolver externals/ens/PublicResolver.go PublicResolver ens"
7581
"externals/upgradeability/UpgradeabilityProxy/UpgradeabilityProxy externals/upgradeability/UpgradeabilityProxy.go UpgradeabilityProxy upgradeability"

contracts/gasProxy.sol

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright (C) 2019 The Contract Wallet Company Limited
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
pragma solidity ^0.5.17;
17+
pragma experimental ABIEncoderV2;
18+
19+
import "./internals/controllable.sol";
20+
import "./internals/gasRefundable.sol";
21+
22+
23+
contract GasProxy is Controllable, GasRefundable {
24+
/// @notice Emits the transaction executed by the controller.
25+
event ExecutedTransaction(address _destination, uint256 _value, bytes _data, bytes _returnData);
26+
27+
/// @param _ens_ is the address of the ENS registry.
28+
/// @param _controllerNode_ ENS node of the controller contract.
29+
constructor(address _ens_, bytes32 _controllerNode_) public {
30+
_initializeENSResolvable(_ens_);
31+
_initializeControllable(_controllerNode_);
32+
}
33+
34+
/// @param _gasTokenAddress Address of the gas token used to refund gas.
35+
/// @param _parameters Gas cost of the gas token free method call and amount of gas refunded per unit of gas token.
36+
function setGasToken(address _gasTokenAddress, GasTokenParameters calldata _parameters) external onlyAdmin {
37+
_setGasToken(_gasTokenAddress, _parameters);
38+
}
39+
40+
/// @notice Executes a controller operation and refunds gas using gas tokens.
41+
/// @param _destination Destination address of the executed transaction.
42+
/// @param _value Amount of ETH (wei) to be sent together with the transaction.
43+
/// @param _data Data payload of the controller transaction.
44+
function executeTransaction(address _destination, uint256 _value, bytes calldata _data) external onlyController refundGas returns (bytes memory) {
45+
(bool success, bytes memory returnData) = _destination.call.value(_value)(_data);
46+
require(success, "external call failed");
47+
emit ExecutedTransaction(_destination, _value, _data, returnData);
48+
return returnData;
49+
}
50+
}

contracts/internals/ensResolvable.sol

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import "../externals/ens/PublicResolver.sol";
2626
///@title ENSResolvable - Ethereum Name Service Resolver
2727
///@notice contract should be used to get an address for an ENS node
2828
contract ENSResolvable is Initializable {
29+
/// @notice Emits the address of the new ENS registry when it is set.
30+
event ENSSetRegistry(address _ensRegistry);
31+
2932
/// @notice Address of the ENS registry contract set to the default ENS registry address.
3033
address private _ensRegistry = address(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e);
3134

@@ -35,19 +38,19 @@ contract ENSResolvable is Initializable {
3538
_;
3639
}
3740

38-
/// @notice this is used to that one can observe which ENS registry is being used
41+
/// @return Current address of the ENS registry contract.
3942
function ensRegistry() public view returns (address) {
4043
return _ensRegistry;
4144
}
4245

43-
/// @notice helper function used to get the address of a node
44-
/// @param _node of the ENS entry that needs resolving
45-
/// @return the address of the said node
46+
/// @notice Helper function used to get the address of a node.
47+
/// @param _node of the ENS entry that needs resolving.
48+
/// @return The address of the resolved ENS node.
4649
function _ensResolve(bytes32 _node) internal view initialized returns (address) {
4750
return PublicResolver(ENS(_ensRegistry).resolver(_node)).addr(_node);
4851
}
4952

50-
/// @param _ensReg is the ENS registry used
53+
/// @param _ensReg is the ENS registry used.
5154
function _initializeENSResolvable(address _ensReg) internal initializer {
5255
// Set ENS registry or use default
5356
if (_ensReg != address(0)) {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright (C) 2019 The Contract Wallet Company Limited
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
pragma solidity ^0.5.17;
17+
pragma experimental ABIEncoderV2;
18+
19+
20+
interface IGasToken {
21+
function freeUpTo(uint256) external returns (uint256);
22+
}
23+
24+
25+
contract GasRefundable {
26+
/// @notice Emits the new gas token information when it is set.
27+
event SetGasToken(address _gasTokenAddress, GasTokenParameters _gasTokenParameters);
28+
29+
struct GasTokenParameters {
30+
uint256 freeCallGasCost;
31+
uint256 gasRefundPerUnit;
32+
}
33+
34+
/// @notice Address of the gas token used to refund gas (default: CHI).
35+
IGasToken private _gasToken = IGasToken(0x0000000000004946c0e9F43F4Dee607b0eF1fA1c);
36+
/// @notice Gas token parameters parameters used in the gas refund calcualtion (default: CHI).
37+
GasTokenParameters private _gasTokenParameters = GasTokenParameters({freeCallGasCost: 14154, gasRefundPerUnit: 41130});
38+
39+
/// @notice Refunds gas based on the amount of gas spent in the transaction and the gas token parameters.
40+
modifier refundGas() {
41+
uint256 gasStart = gasleft();
42+
_;
43+
uint256 gasSpent = 21000 + gasStart - gasleft() + 16 * msg.data.length;
44+
_gasToken.freeUpTo((gasSpent + _gasTokenParameters.freeCallGasCost) / _gasTokenParameters.gasRefundPerUnit);
45+
}
46+
47+
/// @param _gasTokenAddress Address of the gas token used to refund gas.
48+
/// @param _parameters Gas cost of the gas token free method call and amount of gas refunded per unit of gas token.
49+
function _setGasToken(address _gasTokenAddress, GasTokenParameters memory _parameters) internal {
50+
require(_gasTokenAddress != address(0), "gas token address is 0x0");
51+
require(_parameters.freeCallGasCost != 0, "free call gas cost is 0");
52+
require(_parameters.gasRefundPerUnit != 0, "gas refund per unit is 0");
53+
_gasToken = IGasToken(_gasTokenAddress);
54+
_gasTokenParameters.freeCallGasCost = _parameters.freeCallGasCost;
55+
_gasTokenParameters.gasRefundPerUnit = _parameters.gasRefundPerUnit;
56+
emit SetGasToken(_gasTokenAddress, _parameters);
57+
}
58+
59+
/// @return Address of the gas token used to refund gas.
60+
function gasToken() external view returns (address) {
61+
return address(_gasToken);
62+
}
63+
64+
/// @return Gas cost of the gas token free method call.
65+
/// @return Amount of gas refunded per unit of gas token.
66+
function gasTokenParameters() external view returns (GasTokenParameters memory) {
67+
return _gasTokenParameters;
68+
}
69+
}

contracts/mocks/gasBurner.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
pragma solidity ^0.5.17;
2+
3+
4+
contract GasBurner {
5+
function dummy() public pure {
6+
assembly {
7+
invalid()
8+
}
9+
}
10+
11+
function burnGas(uint256 burn) public {
12+
// Calls self.dummy() to burn gas.
13+
assembly {
14+
mstore(0x0, 0x32e43a1100000000000000000000000000000000000000000000000000000000)
15+
let ret := call(burn, address(), 0, 0x0, 0x04, 0x0, 0)
16+
}
17+
}
18+
}

contracts/mocks/gasToken.sol

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
pragma solidity ^0.5.17;
2+
3+
import "../externals/SafeMath.sol";
4+
5+
6+
contract GasToken {
7+
using SafeMath for uint256;
8+
9+
uint256 public totalMinted;
10+
uint256 public totalBurned;
11+
12+
mapping(address => uint256) private _balances;
13+
14+
function balanceOf(address account) public view returns (uint256) {
15+
return _balances[account];
16+
}
17+
18+
function totalSupply() public view returns (uint256) {
19+
return totalMinted - totalBurned;
20+
}
21+
22+
function mint(uint256 value) public {
23+
uint256 offset = totalMinted;
24+
assembly {
25+
mstore(0, 0x766ffa233a79675b0530301caf58abcfa2eb3318585733ff60005260176009f3)
26+
27+
for {
28+
let i := div(value, 32)
29+
} i {
30+
i := sub(i, 1)
31+
} {
32+
pop(create2(0, 0, 32, add(offset, 0)))
33+
pop(create2(0, 0, 32, add(offset, 1)))
34+
pop(create2(0, 0, 32, add(offset, 2)))
35+
pop(create2(0, 0, 32, add(offset, 3)))
36+
pop(create2(0, 0, 32, add(offset, 4)))
37+
pop(create2(0, 0, 32, add(offset, 5)))
38+
pop(create2(0, 0, 32, add(offset, 6)))
39+
pop(create2(0, 0, 32, add(offset, 7)))
40+
pop(create2(0, 0, 32, add(offset, 8)))
41+
pop(create2(0, 0, 32, add(offset, 9)))
42+
pop(create2(0, 0, 32, add(offset, 10)))
43+
pop(create2(0, 0, 32, add(offset, 11)))
44+
pop(create2(0, 0, 32, add(offset, 12)))
45+
pop(create2(0, 0, 32, add(offset, 13)))
46+
pop(create2(0, 0, 32, add(offset, 14)))
47+
pop(create2(0, 0, 32, add(offset, 15)))
48+
pop(create2(0, 0, 32, add(offset, 16)))
49+
pop(create2(0, 0, 32, add(offset, 17)))
50+
pop(create2(0, 0, 32, add(offset, 18)))
51+
pop(create2(0, 0, 32, add(offset, 19)))
52+
pop(create2(0, 0, 32, add(offset, 20)))
53+
pop(create2(0, 0, 32, add(offset, 21)))
54+
pop(create2(0, 0, 32, add(offset, 22)))
55+
pop(create2(0, 0, 32, add(offset, 23)))
56+
pop(create2(0, 0, 32, add(offset, 24)))
57+
pop(create2(0, 0, 32, add(offset, 25)))
58+
pop(create2(0, 0, 32, add(offset, 26)))
59+
pop(create2(0, 0, 32, add(offset, 27)))
60+
pop(create2(0, 0, 32, add(offset, 28)))
61+
pop(create2(0, 0, 32, add(offset, 29)))
62+
pop(create2(0, 0, 32, add(offset, 30)))
63+
pop(create2(0, 0, 32, add(offset, 31)))
64+
offset := add(offset, 32)
65+
}
66+
67+
for {
68+
let i := and(value, 0x1F)
69+
} i {
70+
i := sub(i, 1)
71+
} {
72+
pop(create2(0, 0, 32, offset))
73+
offset := add(offset, 1)
74+
}
75+
}
76+
77+
_mint(msg.sender, value);
78+
totalMinted = offset;
79+
}
80+
81+
function _destroyChildren(uint256 value) internal {
82+
assembly {
83+
let i := sload(totalBurned_slot)
84+
let end := add(i, value)
85+
sstore(totalBurned_slot, end)
86+
87+
let data := mload(0x40)
88+
mstore(data, 0xff00000000fa233a79675b0530301caf58abcfa2eb0000000000000000000000)
89+
mstore(add(data, 53), 0x841da0d3b4b49d75c2a11068e21bceeb2e5d8c9e31ab7cea45c9ce114a2033dc)
90+
let ptr := add(data, 21)
91+
for {
92+
93+
} lt(i, end) {
94+
i := add(i, 1)
95+
} {
96+
mstore(ptr, i)
97+
pop(call(gas(), keccak256(data, 85), 0, 0, 0, 0, 0))
98+
}
99+
}
100+
}
101+
102+
function free(uint256 value) public returns (uint256) {
103+
if (value > 0) {
104+
_burn(msg.sender, value);
105+
_destroyChildren(value);
106+
}
107+
return value;
108+
}
109+
110+
function freeUpTo(uint256 value) public returns (uint256) {
111+
return free(_min(value, balanceOf(msg.sender)));
112+
}
113+
114+
function transfer(address recipient, uint256 amount) public {
115+
_balances[msg.sender] = _balances[msg.sender].sub(amount);
116+
_balances[recipient] = _balances[recipient].add(amount);
117+
}
118+
119+
function _min(uint256 a, uint256 b) private pure returns (uint256) {
120+
return a < b ? a : b;
121+
}
122+
123+
function _mint(address account, uint256 amount) private {
124+
_balances[account] = _balances[account].add(amount);
125+
}
126+
127+
function _burn(address account, uint256 amount) private {
128+
_balances[account] = _balances[account].sub(amount);
129+
}
130+
}

0 commit comments

Comments
 (0)