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

Commit

Permalink
Controller Gas Proxy and CHI token integration (#605)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
4 people authored Aug 3, 2020
1 parent c1e8aa6 commit 11e3788
Show file tree
Hide file tree
Showing 45 changed files with 3,852 additions and 101 deletions.
18 changes: 16 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- image: mythril/myth@sha256:e78a96bd0f5e57fe45bcd1c744509932bee83706a00293e53a60e83cd0ab89f3
resource_class: xlarge
working_directory: /tmp/contracts
parallelism: 7
parallelism: 8
steps:
- checkout
- run:
Expand Down Expand Up @@ -114,12 +114,19 @@ jobs:
myth analyze --solv=0.5.17 ./controller.sol --execution-timeout=800 --max-depth=15 --parallel-solving
fi
no_output_timeout: 20m
- run:
working_directory: contracts
command: |
if [[ "${CIRCLE_NODE_INDEX}" == 7 ]]; then
myth analyze --solv=0.5.17 ./gasProxy.sol --execution-timeout=800 --max-depth=15 --parallel-solving
fi
no_output_timeout: 20m

slither:
docker:
- image: trailofbits/eth-security-toolbox@sha256:3fb96e2d9de772f5e97f1c3c650c8a3d28660f8a64a60b76269da1ac19b86a28
working_directory: /tmp/contracts
parallelism: 6
parallelism: 7
steps:
- run: sudo -n apt-get update && sudo -n apt-get install -y openssh-client
- checkout
Expand Down Expand Up @@ -166,6 +173,13 @@ jobs:
slither ../../contracts/walletDeployer.sol
fi
no_output_timeout: 5m
- run:
working_directory: tools/slither
command: |
if [[ "${CIRCLE_NODE_INDEX}" == 6 ]]; then
slither ../../contracts/gasProxy.sol
fi
no_output_timeout: 5m

echidna:
docker:
Expand Down
10 changes: 8 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ contract_sources=(
'tokenWhitelist'
'walletDeployer'
'walletCache'
'gasProxy'
'mocks/token'
'mocks/burnerToken'
'mocks/nonCompliantToken'
Expand All @@ -27,7 +28,9 @@ contract_sources=(
'mocks/isValidSignatureExporter'
'mocks/parseIntScientificExporter'
'mocks/tokenWhitelistableExporter'
'mocks/walletMock'
'mocks/wallet'
'mocks/gasToken'
'mocks/gasBurner'
'externals/ens/PublicResolver'
'externals/ens/ENSRegistry'
'externals/upgradeability/UpgradeabilityProxy'
Expand Down Expand Up @@ -59,6 +62,7 @@ contracts=(
"tokenWhitelist/TokenWhitelist tokenWhitelist.go TokenWhitelist bindings"
"walletDeployer/WalletDeployer walletDeployer.go WalletDeployer bindings"
"walletCache/WalletCache walletCache.go WalletCache bindings"
"gasProxy/GasProxy gasProxy.go GasProxy bindings"
"mocks/token/Token mocks/token.go Token mocks"
"mocks/burnerToken/BurnerToken mocks/burnerToken.go BurnerToken mocks"
"mocks/nonCompliantToken/NonCompliantToken mocks/nonCompliantToken.go NonCompliantToken mocks"
Expand All @@ -69,7 +73,9 @@ contracts=(
"mocks/isValidSignatureExporter/IsValidSignatureExporter mocks/isValidSignatureExporter.go IsValidSignatureExporter mocks"
"mocks/parseIntScientificExporter/ParseIntScientificExporter mocks/parseIntScientificExporter.go ParseIntScientificExporter mocks"
"mocks/tokenWhitelistableExporter/TokenWhitelistableExporter mocks/tokenWhitelistableExporter.go TokenWhitelistableExporter mocks"
"mocks/walletMock/WalletMock mocks/walletMock.go WalletMock mocks"
"mocks/wallet/Wallet mocks/wallet.go Wallet mocks"
"mocks/gasToken/GasToken mocks/gasToken.go GasToken mocks"
"mocks/gasBurner/GasBurner mocks/gasBurner.go GasBurner mocks"
"externals/ens/ENSRegistry/ENSRegistry externals/ens/ENSRegistry.go ENSRegistry ens"
"externals/ens/PublicResolver/PublicResolver externals/ens/PublicResolver.go PublicResolver ens"
"externals/upgradeability/UpgradeabilityProxy/UpgradeabilityProxy externals/upgradeability/UpgradeabilityProxy.go UpgradeabilityProxy upgradeability"
Expand Down
50 changes: 50 additions & 0 deletions contracts/gasProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (C) 2019 The Contract Wallet Company Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

pragma solidity ^0.5.17;
pragma experimental ABIEncoderV2;

import "./internals/controllable.sol";
import "./internals/gasRefundable.sol";


contract GasProxy is Controllable, GasRefundable {
/// @notice Emits the transaction executed by the controller.
event ExecutedTransaction(address _destination, uint256 _value, bytes _data, bytes _returnData);

/// @param _ens_ is the address of the ENS registry.
/// @param _controllerNode_ ENS node of the controller contract.
constructor(address _ens_, bytes32 _controllerNode_) public {
_initializeENSResolvable(_ens_);
_initializeControllable(_controllerNode_);
}

/// @param _gasTokenAddress Address of the gas token used to refund gas.
/// @param _parameters Gas cost of the gas token free method call and amount of gas refunded per unit of gas token.
function setGasToken(address _gasTokenAddress, GasTokenParameters calldata _parameters) external onlyAdmin {
_setGasToken(_gasTokenAddress, _parameters);
}

/// @notice Executes a controller operation and refunds gas using gas tokens.
/// @param _destination Destination address of the executed transaction.
/// @param _value Amount of ETH (wei) to be sent together with the transaction.
/// @param _data Data payload of the controller transaction.
function executeTransaction(address _destination, uint256 _value, bytes calldata _data) external onlyController refundGas returns (bytes memory) {
(bool success, bytes memory returnData) = _destination.call.value(_value)(_data);
require(success, "external call failed");
emit ExecutedTransaction(_destination, _value, _data, returnData);
return returnData;
}
}
13 changes: 8 additions & 5 deletions contracts/internals/ensResolvable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import "../externals/ens/PublicResolver.sol";
///@title ENSResolvable - Ethereum Name Service Resolver
///@notice contract should be used to get an address for an ENS node
contract ENSResolvable is Initializable {
/// @notice Emits the address of the new ENS registry when it is set.
event ENSSetRegistry(address _ensRegistry);

/// @notice Address of the ENS registry contract set to the default ENS registry address.
address private _ensRegistry = address(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e);

Expand All @@ -35,19 +38,19 @@ contract ENSResolvable is Initializable {
_;
}

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

/// @notice helper function used to get the address of a node
/// @param _node of the ENS entry that needs resolving
/// @return the address of the said node
/// @notice Helper function used to get the address of a node.
/// @param _node of the ENS entry that needs resolving.
/// @return The address of the resolved ENS node.
function _ensResolve(bytes32 _node) internal view initialized returns (address) {
return PublicResolver(ENS(_ensRegistry).resolver(_node)).addr(_node);
}

/// @param _ensReg is the ENS registry used
/// @param _ensReg is the ENS registry used.
function _initializeENSResolvable(address _ensReg) internal initializer {
// Set ENS registry or use default
if (_ensReg != address(0)) {
Expand Down
69 changes: 69 additions & 0 deletions contracts/internals/gasRefundable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright (C) 2019 The Contract Wallet Company Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

pragma solidity ^0.5.17;
pragma experimental ABIEncoderV2;


interface IGasToken {
function freeUpTo(uint256) external returns (uint256);
}


contract GasRefundable {
/// @notice Emits the new gas token information when it is set.
event SetGasToken(address _gasTokenAddress, GasTokenParameters _gasTokenParameters);

struct GasTokenParameters {
uint256 freeCallGasCost;
uint256 gasRefundPerUnit;
}

/// @notice Address of the gas token used to refund gas (default: CHI).
IGasToken private _gasToken = IGasToken(0x0000000000004946c0e9F43F4Dee607b0eF1fA1c);
/// @notice Gas token parameters parameters used in the gas refund calcualtion (default: CHI).
GasTokenParameters private _gasTokenParameters = GasTokenParameters({freeCallGasCost: 14154, gasRefundPerUnit: 41130});

/// @notice Refunds gas based on the amount of gas spent in the transaction and the gas token parameters.
modifier refundGas() {
uint256 gasStart = gasleft();
_;
uint256 gasSpent = 21000 + gasStart - gasleft() + 16 * msg.data.length;
_gasToken.freeUpTo((gasSpent + _gasTokenParameters.freeCallGasCost) / _gasTokenParameters.gasRefundPerUnit);
}

/// @param _gasTokenAddress Address of the gas token used to refund gas.
/// @param _parameters Gas cost of the gas token free method call and amount of gas refunded per unit of gas token.
function _setGasToken(address _gasTokenAddress, GasTokenParameters memory _parameters) internal {
require(_gasTokenAddress != address(0), "gas token address is 0x0");
require(_parameters.freeCallGasCost != 0, "free call gas cost is 0");
require(_parameters.gasRefundPerUnit != 0, "gas refund per unit is 0");
_gasToken = IGasToken(_gasTokenAddress);
_gasTokenParameters.freeCallGasCost = _parameters.freeCallGasCost;
_gasTokenParameters.gasRefundPerUnit = _parameters.gasRefundPerUnit;
emit SetGasToken(_gasTokenAddress, _parameters);
}

/// @return Address of the gas token used to refund gas.
function gasToken() external view returns (address) {
return address(_gasToken);
}

/// @return Gas cost of the gas token free method call.
/// @return Amount of gas refunded per unit of gas token.
function gasTokenParameters() external view returns (GasTokenParameters memory) {
return _gasTokenParameters;
}
}
18 changes: 18 additions & 0 deletions contracts/mocks/gasBurner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pragma solidity ^0.5.17;


contract GasBurner {
function dummy() public pure {
assembly {
invalid()
}
}

function burnGas(uint256 burn) public {
// Calls self.dummy() to burn gas.
assembly {
mstore(0x0, 0x32e43a1100000000000000000000000000000000000000000000000000000000)
let ret := call(burn, address(), 0, 0x0, 0x04, 0x0, 0)
}
}
}
130 changes: 130 additions & 0 deletions contracts/mocks/gasToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
pragma solidity ^0.5.17;

import "../externals/SafeMath.sol";


contract GasToken {
using SafeMath for uint256;

uint256 public totalMinted;
uint256 public totalBurned;

mapping(address => uint256) private _balances;

function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}

function totalSupply() public view returns (uint256) {
return totalMinted - totalBurned;
}

function mint(uint256 value) public {
uint256 offset = totalMinted;
assembly {
mstore(0, 0x766ffa233a79675b0530301caf58abcfa2eb3318585733ff60005260176009f3)

for {
let i := div(value, 32)
} i {
i := sub(i, 1)
} {
pop(create2(0, 0, 32, add(offset, 0)))
pop(create2(0, 0, 32, add(offset, 1)))
pop(create2(0, 0, 32, add(offset, 2)))
pop(create2(0, 0, 32, add(offset, 3)))
pop(create2(0, 0, 32, add(offset, 4)))
pop(create2(0, 0, 32, add(offset, 5)))
pop(create2(0, 0, 32, add(offset, 6)))
pop(create2(0, 0, 32, add(offset, 7)))
pop(create2(0, 0, 32, add(offset, 8)))
pop(create2(0, 0, 32, add(offset, 9)))
pop(create2(0, 0, 32, add(offset, 10)))
pop(create2(0, 0, 32, add(offset, 11)))
pop(create2(0, 0, 32, add(offset, 12)))
pop(create2(0, 0, 32, add(offset, 13)))
pop(create2(0, 0, 32, add(offset, 14)))
pop(create2(0, 0, 32, add(offset, 15)))
pop(create2(0, 0, 32, add(offset, 16)))
pop(create2(0, 0, 32, add(offset, 17)))
pop(create2(0, 0, 32, add(offset, 18)))
pop(create2(0, 0, 32, add(offset, 19)))
pop(create2(0, 0, 32, add(offset, 20)))
pop(create2(0, 0, 32, add(offset, 21)))
pop(create2(0, 0, 32, add(offset, 22)))
pop(create2(0, 0, 32, add(offset, 23)))
pop(create2(0, 0, 32, add(offset, 24)))
pop(create2(0, 0, 32, add(offset, 25)))
pop(create2(0, 0, 32, add(offset, 26)))
pop(create2(0, 0, 32, add(offset, 27)))
pop(create2(0, 0, 32, add(offset, 28)))
pop(create2(0, 0, 32, add(offset, 29)))
pop(create2(0, 0, 32, add(offset, 30)))
pop(create2(0, 0, 32, add(offset, 31)))
offset := add(offset, 32)
}

for {
let i := and(value, 0x1F)
} i {
i := sub(i, 1)
} {
pop(create2(0, 0, 32, offset))
offset := add(offset, 1)
}
}

_mint(msg.sender, value);
totalMinted = offset;
}

function _destroyChildren(uint256 value) internal {
assembly {
let i := sload(totalBurned_slot)
let end := add(i, value)
sstore(totalBurned_slot, end)

let data := mload(0x40)
mstore(data, 0xff00000000fa233a79675b0530301caf58abcfa2eb0000000000000000000000)
mstore(add(data, 53), 0x841da0d3b4b49d75c2a11068e21bceeb2e5d8c9e31ab7cea45c9ce114a2033dc)
let ptr := add(data, 21)
for {

} lt(i, end) {
i := add(i, 1)
} {
mstore(ptr, i)
pop(call(gas(), keccak256(data, 85), 0, 0, 0, 0, 0))
}
}
}

function free(uint256 value) public returns (uint256) {
if (value > 0) {
_burn(msg.sender, value);
_destroyChildren(value);
}
return value;
}

function freeUpTo(uint256 value) public returns (uint256) {
return free(_min(value, balanceOf(msg.sender)));
}

function transfer(address recipient, uint256 amount) public {
_balances[msg.sender] = _balances[msg.sender].sub(amount);
_balances[recipient] = _balances[recipient].add(amount);
}

function _min(uint256 a, uint256 b) private pure returns (uint256) {
return a < b ? a : b;
}

function _mint(address account, uint256 amount) private {
_balances[account] = _balances[account].add(amount);
}

function _burn(address account, uint256 amount) private {
_balances[account] = _balances[account].sub(amount);
}
}
Loading

0 comments on commit 11e3788

Please sign in to comment.