Transaction Hash:
Block:
12031617 at Mar-13-2021 05:30:56 PM +UTC
Transaction Fee:
0.0334163254 ETH
$62.64
Gas Used:
202,646 Gas / 164.9 Gwei
Emitted Events:
104 |
Proxy.0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365( 0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365, 0x000000000000000000000000645ba45dbe3c6942c812a46f9ee8115c89b524ec, 0x0000000000000000000000004132b0d5153cc305a23334e47b318142e2d74402, 0x000000000000000000000000000000000000000000000000013fbe85edc90000, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000000 )
|
105 |
TransferManager.Transfer( wallet=Proxy, token=0xEeeeeEee...eeeeeEEeE, amount=90000000000000000, to=0x4132b0d5153cc305a23334e47b318142e2d74402, data=0x )
|
106 |
Proxy.0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365( 0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365, 0x000000000000000000000000645ba45dbe3c6942c812a46f9ee8115c89b524ec, 0x000000000000000000000000482579f93dc13e6b434e38b5a0447ca543d88a46, 0x000000000000000000000000000000000000000000000000003578b010f91000, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000000 )
|
107 |
RelayerManager.Refund( wallet=Proxy, refundAddress=0x482579f93dc13e6b434e38b5a0447ca543d88a46, refundToken=0xEeeeeEee...eeeeeEEeE, refundAmount=15050871360000000 )
|
108 |
RelayerManager.TransactionExecuted( wallet=Proxy, success=True, returnData=0x, signedHash=BA36390A0CB3327F89D3DB1DA4B01FFB6E3EB45614BBB4044AB29C95213AE8E1 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x045B32ef...Ad7299a55 | |||||
0x10A0847c...493630032 | (Argent: Relayer Manager) | ||||
0x4132B0d5...2E2d74402 | 0.074850228406717883 Eth | 0.164850228406717883 Eth | 0.09 | ||
0x482579F9...543D88A46 | 9.518614509302182551 Eth | 9.533665380662182551 Eth | 0.01505087136 | ||
0x5A0b54D5...D3E029c4c
Miner
| (Spark Pool) | 1.120581246532887401 Eth | 1.153997571932887401 Eth | 0.0334163254 | |
0x77522581...6931c056f | 0.1116 Eth | 0.00654912864 Eth | 0.10505087136 | ||
0xF27696C8...f59342fA6 | (Argent: Relayer 3) |
3.644682226409002438 Eth
Nonce: 181950
|
3.611265901009002438 Eth
Nonce: 181951
| 0.0334163254 |
Execution Trace
RelayerManager.execute( _wallet=0x775225818bA851948733A611922A8916931c056f, _feature=0x5094a8f54B12AEc540bF7cCd0Dd7B62f4FecF7f2, _data=0x2DF546F4000000000000000000000000775225818BA851948733A611922A8916931C056F000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE0000000000000000000000004132B0D5153CC305A23334E47B318142E2D74402000000000000000000000000000000000000000000000000013FBE85EDC9000000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000000, _nonce=4094135541045725561052059049407736074510070947, _signatures=0xD50497B93E28AEA87510784F0FBFD89D319E5FD601ECEC1630DB82BA4FB94B184B0B5B43DCF3F42D5E74CFF610D70F80D79FDE519E9F4855F5CA2B94EB4C51CB1C, _gasPrice=141120000000, _gasLimit=106653, _refundToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, _refundAddress=0x482579F93dC13e6B434E38b5a0447ca543D88A46 ) => ( True )
-
VersionManager.isFeatureAuthorised( _wallet=0x775225818bA851948733A611922A8916931c056f, _feature=0x5094a8f54B12AEc540bF7cCd0Dd7B62f4FecF7f2 ) => ( True )
-
TransferManager.getRequiredSignatures( 0x775225818bA851948733A611922A8916931c056f, 0x2DF546F4000000000000000000000000775225818BA851948733A611922A8916931C056F000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE0000000000000000000000004132B0D5153CC305A23334E47B318142E2D74402000000000000000000000000000000000000000000000000013FBE85EDC9000000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000000 ) => ( 1, 1 )
-
Null: 0x000...001.ba36390a( )
Proxy.STATICCALL( )
-
BaseWallet.DELEGATECALL( )
-
TransferManager.transferToken( _wallet=0x775225818bA851948733A611922A8916931c056f, _token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, _to=0x4132B0d5153cc305a23334E47B318142E2d74402, _amount=90000000000000000, _data=0x )
-
VersionManager.isFeatureAuthorised( _wallet=0x775225818bA851948733A611922A8916931c056f, _feature=0x10A0847c2D170008dDCa7C3a688124f493630032 ) => ( True )
-
LockStorage.isLocked( _wallet=0x775225818bA851948733A611922A8916931c056f ) => ( False )
-
0x391f0e86da951c03b1183c60b195090671adea88.13f4a0ea( )
-
0x045b32efa0d97a681cc415f1b37c972ad7299a55.13565b2c( )
VersionManager.invokeStorage( _wallet=0x775225818bA851948733A611922A8916931c056f, _storage=0x045B32efA0D97a681Cc415f1B37C972Ad7299a55, _data=0x5AE5BC52000000000000000000000000775225818BA851948733A611922A8916931C056F000000000000000000000000000000000000000000000000013FBE85EDC9000000000000000000000000000000000000000000000000000000000000604E4850 )
0x045b32efa0d97a681cc415f1b37c972ad7299a55.5ae5bc52( )
Proxy.d6eb1bbf( )
-
BaseWallet.authorised( 0x645BA45dBe3c6942c812A46f9EE8115C89B524EC ) => ( True )
-
VersionManager.checkAuthorisedFeatureAndInvokeWallet( _wallet=0x775225818bA851948733A611922A8916931c056f, _to=0x4132B0d5153cc305a23334E47B318142E2d74402, _value=90000000000000000, _data=0x ) => ( _res=0x )
Proxy.8f6f0332( )
BaseWallet.invoke( _target=0x4132B0d5153cc305a23334E47B318142E2d74402, _value=90000000000000000, _data=0x ) => ( _result=0x )
- ETH 0.09
0x4132b0d5153cc305a23334e47b318142e2d74402.CALL( )
- ETH 0.09
-
-
0x045b32efa0d97a681cc415f1b37c972ad7299a55.13565b2c( )
VersionManager.invokeStorage( _wallet=0x775225818bA851948733A611922A8916931c056f, _storage=0x045B32efA0D97a681Cc415f1B37C972Ad7299a55, _data=0x5AE5BC52000000000000000000000000775225818BA851948733A611922A8916931C056F00000000000000000000000000000000000000000000000001753735FEC2100000000000000000000000000000000000000000000000000000000000604E4850 )
0x045b32efa0d97a681cc415f1b37c972ad7299a55.5ae5bc52( )
Proxy.d6eb1bbf( )
-
BaseWallet.authorised( 0x645BA45dBe3c6942c812A46f9EE8115C89B524EC ) => ( True )
-
VersionManager.checkAuthorisedFeatureAndInvokeWallet( _wallet=0x775225818bA851948733A611922A8916931c056f, _to=0x482579F93dC13e6B434E38b5a0447ca543D88A46, _value=15050871360000000, _data=0x ) => ( _res=0x )
Proxy.8f6f0332( )
BaseWallet.invoke( _target=0x482579F93dC13e6B434E38b5a0447ca543D88A46, _value=15050871360000000, _data=0x ) => ( _result=0x )
- ETH 0.01505087136
0x482579f93dc13e6b434e38b5a0447ca543d88a46.CALL( )
- ETH 0.01505087136
execute[RelayerManager (ln:1022)]
gasleft[RelayerManager (ln:1036)]
verifyData[RelayerManager (ln:1038)]
isFeatureAuthorisedInVersionManager[RelayerManager (ln:1039)]
getRequiredSignatures[RelayerManager (ln:1041)]
getSignHash[RelayerManager (ln:1044)]
getChainId[RelayerManager (ln:1129)]
checkAndUpdateUniqueness[RelayerManager (ln:1054)]
validateSignatures[RelayerManager (ln:1060)]
getGuardians[RelayerManager (ln:1203)]
recoverSigner[RelayerManager (ln:1207)]
ecrecover[Utils (ln:40)]
isOwner[RelayerManager (ln:1211)]
isOwner[RelayerManager (ln:1217)]
isGuardianOrGuardianSigner[RelayerManager (ln:1226)]
isContract[GuardianUtils (ln:665)]
isGuardianOwner[GuardianUtils (ln:665)]
call[RelayerManager (ln:1061)]
refund[RelayerManager (ln:1064)]
add[RelayerManager (ln:1258)]
sub[RelayerManager (ln:1258)]
gasleft[RelayerManager (ln:1258)]
mul[RelayerManager (ln:1259)]
min[RelayerManager (ln:1259)]
add[RelayerManager (ln:1261)]
sub[RelayerManager (ln:1261)]
gasleft[RelayerManager (ln:1261)]
mul[RelayerManager (ln:1262)]
min[RelayerManager (ln:1262)]
getEtherValue[RelayerManager (ln:1263)]
getTokenPrice[LimitUtils (ln:863)]
div[LimitUtils (ln:864)]
mul[LimitUtils (ln:864)]
checkAndUpdateDailySpent[RelayerManager (ln:1264)]
getLimitAndDailySpent[LimitUtils (ln:829)]
currentLimit[LimitUtils (ln:830)]
DailySpent[LimitUtils (ln:836)]
safe128[LimitUtils (ln:836)]
safe64[LimitUtils (ln:836)]
setDailySpent[LimitUtils (ln:837)]
invokeStorage[LimitUtils (ln:904)]
encodeWithSelector[LimitUtils (ln:907)]
add[LimitUtils (ln:839)]
DailySpent[LimitUtils (ln:840)]
safe128[LimitUtils (ln:840)]
add[LimitUtils (ln:840)]
safe64[LimitUtils (ln:840)]
setDailySpent[LimitUtils (ln:841)]
invokeStorage[LimitUtils (ln:904)]
encodeWithSelector[LimitUtils (ln:907)]
invokeWallet[RelayerManager (ln:1268)]
encodeWithSignature[RelayerManager (ln:1270)]
invokeWallet[RelayerManager (ln:1271)]
decode[RelayerManager (ln:1274)]
Refund[RelayerManager (ln:1277)]
TransactionExecuted[RelayerManager (ln:1073)]
File 1 of 6: RelayerManager
File 2 of 6: Proxy
File 3 of 6: TransferManager
File 4 of 6: VersionManager
File 5 of 6: BaseWallet
File 6 of 6: LockStorage
pragma experimental ABIEncoderV2; // File: contracts/modules/common/Utils.sol // Copyright (C) 2020 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only /** * @title Utils * @notice Common utility methods used by modules. */ library Utils { /** * @notice Helper method to recover the signer at a given position from a list of concatenated signatures. * @param _signedHash The signed hash * @param _signatures The concatenated signatures. * @param _index The index of the signature to recover. */ function recoverSigner(bytes32 _signedHash, bytes memory _signatures, uint _index) internal pure returns (address) { uint8 v; bytes32 r; bytes32 s; // we jump 32 (0x20) as the first slot of bytes contains the length // we jump 65 (0x41) per signature // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask // solhint-disable-next-line no-inline-assembly assembly { r := mload(add(_signatures, add(0x20,mul(0x41,_index)))) s := mload(add(_signatures, add(0x40,mul(0x41,_index)))) v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff) } require(v == 27 || v == 28); address recoveredAddress = ecrecover(_signedHash, v, r, s); require(recoveredAddress != address(0), "Utils: ecrecover returned 0"); return recoveredAddress; } /** * @notice Helper method to parse data and extract the method signature. */ function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) { require(_data.length >= 4, "RM: Invalid functionPrefix"); // solhint-disable-next-line no-inline-assembly assembly { prefix := mload(add(_data, 0x20)) } } /** * @notice Returns ceil(a / b). */ function ceil(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a / b; if (a % b == 0) { return c; } else { return c + 1; } } function min(uint256 a, uint256 b) internal pure returns (uint256) { if (a < b) { return a; } return b; } } // File: @openzeppelin/contracts/math/SafeMath.sol /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } // File: contracts/wallet/IWallet.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IWallet * @notice Interface for the BaseWallet */ interface IWallet { /** * @notice Returns the wallet owner. * @return The wallet owner address. */ function owner() external view returns (address); /** * @notice Returns the number of authorised modules. * @return The number of authorised modules. */ function modules() external view returns (uint); /** * @notice Sets a new owner for the wallet. * @param _newOwner The new owner. */ function setOwner(address _newOwner) external; /** * @notice Checks if a module is authorised on the wallet. * @param _module The module address to check. * @return `true` if the module is authorised, otherwise `false`. */ function authorised(address _module) external view returns (bool); /** * @notice Returns the module responsible for a static call redirection. * @param _sig The signature of the static call. * @return the module doing the redirection */ function enabled(bytes4 _sig) external view returns (address); /** * @notice Enables/Disables a module. * @param _module The target module. * @param _value Set to `true` to authorise the module. */ function authoriseModule(address _module, bool _value) external; /** * @notice Enables a static method by specifying the target module to which the call must be delegated. * @param _module The target module. * @param _method The static method signature. */ function enableStaticCall(address _module, bytes4 _method) external; } // File: contracts/infrastructure/IModuleRegistry.sol // Copyright (C) 2020 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IModuleRegistry * @notice Interface for the registry of authorised modules. */ interface IModuleRegistry { function registerModule(address _module, bytes32 _name) external; function deregisterModule(address _module) external; function registerUpgrader(address _upgrader, bytes32 _name) external; function deregisterUpgrader(address _upgrader) external; function recoverToken(address _token) external; function moduleInfo(address _module) external view returns (bytes32); function upgraderInfo(address _upgrader) external view returns (bytes32); function isRegisteredModule(address _module) external view returns (bool); function isRegisteredModule(address[] calldata _modules) external view returns (bool); function isRegisteredUpgrader(address _upgrader) external view returns (bool); } // File: contracts/infrastructure/storage/ILockStorage.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; interface ILockStorage { function isLocked(address _wallet) external view returns (bool); function getLock(address _wallet) external view returns (uint256); function getLocker(address _wallet) external view returns (address); function setLock(address _wallet, address _locker, uint256 _releaseAfter) external; } // File: contracts/modules/common/IFeature.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IFeature * @notice Interface for a Feature. * @author Julien Niset - <[email protected]>, Olivier VDB - <[email protected]> */ interface IFeature { enum OwnerSignature { Anyone, // Anyone Required, // Owner required Optional, // Owner and/or guardians Disallowed // guardians only } /** * @notice Utility method to recover any ERC20 token that was sent to the Feature by mistake. * @param _token The token to recover. */ function recoverToken(address _token) external; /** * @notice Inits a Feature for a wallet by e.g. setting some wallet specific parameters in storage. * @param _wallet The wallet. */ function init(address _wallet) external; /** * @notice Helper method to check if an address is an authorised feature of a target wallet. * @param _wallet The target wallet. * @param _feature The address. */ function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) external view returns (bool); /** * @notice Gets the number of valid signatures that must be provided to execute a * specific relayed transaction. * @param _wallet The target wallet. * @param _data The data of the relayed transaction. * @return The number of required signatures and the wallet owner signature requirement. */ function getRequiredSignatures(address _wallet, bytes calldata _data) external view returns (uint256, OwnerSignature); /** * @notice Gets the list of static call signatures that this feature responds to on behalf of wallets */ function getStaticCallSignatures() external view returns (bytes4[] memory); } // File: lib/other/ERC20.sol pragma solidity >=0.5.4 <0.7.0; /** * ERC20 contract interface. */ interface ERC20 { function totalSupply() external view returns (uint); function decimals() external view returns (uint); function balanceOf(address tokenOwner) external view returns (uint balance); function allowance(address tokenOwner, address spender) external view returns (uint remaining); function transfer(address to, uint tokens) external returns (bool success); function approve(address spender, uint tokens) external returns (bool success); function transferFrom(address from, address to, uint tokens) external returns (bool success); } // File: contracts/infrastructure/storage/ILimitStorage.sol // 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 <http://www.gnu.org/licenses/>. /** * @title ILimitStorage * @notice LimitStorage interface */ interface ILimitStorage { struct Limit { // the current limit uint128 current; // the pending limit if any uint128 pending; // when the pending limit becomes the current limit uint64 changeAfter; } struct DailySpent { // The amount already spent during the current period uint128 alreadySpent; // The end of the current period uint64 periodEnd; } function setLimit(address _wallet, Limit memory _limit) external; function getLimit(address _wallet) external view returns (Limit memory _limit); function setDailySpent(address _wallet, DailySpent memory _dailySpent) external; function getDailySpent(address _wallet) external view returns (DailySpent memory _dailySpent); function setLimitAndDailySpent(address _wallet, Limit memory _limit, DailySpent memory _dailySpent) external; function getLimitAndDailySpent(address _wallet) external view returns (Limit memory _limit, DailySpent memory _dailySpent); } // File: contracts/modules/common/IVersionManager.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IVersionManager * @notice Interface for the VersionManager module. * @author Olivier VDB - <[email protected]> */ interface IVersionManager { /** * @notice Returns true if the feature is authorised for the wallet * @param _wallet The target wallet. * @param _feature The feature. */ function isFeatureAuthorised(address _wallet, address _feature) external view returns (bool); /** * @notice Lets a feature (caller) invoke a wallet. * @param _wallet The target wallet. * @param _to The target address for the transaction. * @param _value The value of the transaction. * @param _data The data of the transaction. */ function checkAuthorisedFeatureAndInvokeWallet( address _wallet, address _to, uint256 _value, bytes calldata _data ) external returns (bytes memory _res); /* ******* Backward Compatibility with old Storages and BaseWallet *************** */ /** * @notice Sets a new owner for the wallet. * @param _newOwner The new owner. */ function setOwner(address _wallet, address _newOwner) external; /** * @notice Lets a feature write data to a storage contract. * @param _wallet The target wallet. * @param _storage The storage contract. * @param _data The data of the call */ function invokeStorage(address _wallet, address _storage, bytes calldata _data) external; /** * @notice Upgrade a wallet to a new version. * @param _wallet the wallet to upgrade * @param _toVersion the new version */ function upgradeWallet(address _wallet, uint256 _toVersion) external; } // File: contracts/modules/common/BaseFeature.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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.s // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /** * @title BaseFeature * @notice Base Feature contract that contains methods common to all Feature contracts. * @author Julien Niset - <[email protected]>, Olivier VDB - <[email protected]> */ contract BaseFeature is IFeature { // Empty calldata bytes constant internal EMPTY_BYTES = ""; // Mock token address for ETH address constant internal ETH_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // The address of the Lock storage ILockStorage internal lockStorage; // The address of the Version Manager IVersionManager internal versionManager; event FeatureCreated(bytes32 name); /** * @notice Throws if the wallet is locked. */ modifier onlyWhenUnlocked(address _wallet) { require(!lockStorage.isLocked(_wallet), "BF: wallet locked"); _; } /** * @notice Throws if the sender is not the VersionManager. */ modifier onlyVersionManager() { require(msg.sender == address(versionManager), "BF: caller must be VersionManager"); _; } /** * @notice Throws if the sender is not the owner of the target wallet. */ modifier onlyWalletOwner(address _wallet) { require(isOwner(_wallet, msg.sender), "BF: must be wallet owner"); _; } /** * @notice Throws if the sender is not an authorised feature of the target wallet. */ modifier onlyWalletFeature(address _wallet) { require(versionManager.isFeatureAuthorised(_wallet, msg.sender), "BF: must be a wallet feature"); _; } /** * @notice Throws if the sender is not the owner of the target wallet or the feature itself. */ modifier onlyWalletOwnerOrFeature(address _wallet) { // Wrapping in an internal method reduces deployment cost by avoiding duplication of inlined code verifyOwnerOrAuthorisedFeature(_wallet, msg.sender); _; } constructor( ILockStorage _lockStorage, IVersionManager _versionManager, bytes32 _name ) public { lockStorage = _lockStorage; versionManager = _versionManager; emit FeatureCreated(_name); } /** * @inheritdoc IFeature */ function recoverToken(address _token) external virtual override { uint total = ERC20(_token).balanceOf(address(this)); _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector, address(versionManager), total)); } /** * @notice Inits the feature for a wallet by doing nothing. * @dev !! Overriding methods need make sure `init()` can only be called by the VersionManager !! * @param _wallet The wallet. */ function init(address _wallet) external virtual override {} /** * @inheritdoc IFeature */ function getRequiredSignatures(address, bytes calldata) external virtual view override returns (uint256, OwnerSignature) { revert("BF: disabled method"); } /** * @inheritdoc IFeature */ function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) {} /** * @inheritdoc IFeature */ function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) public override view returns (bool) { return versionManager.isFeatureAuthorised(_wallet, _feature); } /** * @notice Checks that the wallet address provided as the first parameter of _data matches _wallet * @return false if the addresses are different. */ function verifyData(address _wallet, bytes calldata _data) internal pure returns (bool) { require(_data.length >= 36, "RM: Invalid dataWallet"); address dataWallet = abi.decode(_data[4:], (address)); return dataWallet == _wallet; } /** * @notice Helper method to check if an address is the owner of a target wallet. * @param _wallet The target wallet. * @param _addr The address. */ function isOwner(address _wallet, address _addr) internal view returns (bool) { return IWallet(_wallet).owner() == _addr; } /** * @notice Verify that the caller is an authorised feature or the wallet owner. * @param _wallet The target wallet. * @param _sender The caller. */ function verifyOwnerOrAuthorisedFeature(address _wallet, address _sender) internal view { require(isFeatureAuthorisedInVersionManager(_wallet, _sender) || isOwner(_wallet, _sender), "BF: must be owner or feature"); } /** * @notice Helper method to invoke a wallet. * @param _wallet The target wallet. * @param _to The target address for the transaction. * @param _value The value of the transaction. * @param _data The data of the transaction. */ function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data) internal returns (bytes memory _res) { _res = versionManager.checkAuthorisedFeatureAndInvokeWallet(_wallet, _to, _value, _data); } } // File: contracts/modules/common/GuardianUtils.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. /** * @title GuardianUtils * @notice Bundles guardian read logic. */ library GuardianUtils { /** * @notice Checks if an address is a guardian or an account authorised to sign on behalf of a smart-contract guardian * given a list of guardians. * @param _guardians the list of guardians * @param _guardian the address to test * @return true and the list of guardians minus the found guardian upon success, false and the original list of guardians if not found. */ function isGuardianOrGuardianSigner(address[] memory _guardians, address _guardian) internal view returns (bool, address[] memory) { if (_guardians.length == 0 || _guardian == address(0)) { return (false, _guardians); } bool isFound = false; address[] memory updatedGuardians = new address[](_guardians.length - 1); uint256 index = 0; for (uint256 i = 0; i < _guardians.length; i++) { if (!isFound) { // check if _guardian is an account guardian if (_guardian == _guardians[i]) { isFound = true; continue; } // check if _guardian is the owner of a smart contract guardian if (isContract(_guardians[i]) && isGuardianOwner(_guardians[i], _guardian)) { isFound = true; continue; } } if (index < updatedGuardians.length) { updatedGuardians[index] = _guardians[i]; index++; } } return isFound ? (true, updatedGuardians) : (false, _guardians); } /** * @notice Checks if an address is a contract. * @param _addr The address. */ function isContract(address _addr) internal view returns (bool) { uint32 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(_addr) } return (size > 0); } /** * @notice Checks if an address is the owner of a guardian contract. * The method does not revert if the call to the owner() method consumes more then 5000 gas. * @param _guardian The guardian contract * @param _owner The owner to verify. */ function isGuardianOwner(address _guardian, address _owner) internal view returns (bool) { address owner = address(0); bytes4 sig = bytes4(keccak256("owner()")); // solhint-disable-next-line no-inline-assembly assembly { let ptr := mload(0x40) mstore(ptr,sig) let result := staticcall(5000, _guardian, ptr, 0x20, ptr, 0x20) if eq(result, 1) { owner := mload(ptr) } } return owner == _owner; } } // File: contracts/infrastructure/ITokenPriceRegistry.sol // 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 <http://www.gnu.org/licenses/>. /** * @title ITokenPriceRegistry * @notice TokenPriceRegistry interface */ interface ITokenPriceRegistry { function getTokenPrice(address _token) external view returns (uint184 _price); function isTokenTradable(address _token) external view returns (bool _isTradable); } // File: contracts/modules/common/LimitUtils.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. /** * @title LimitUtils * @notice Helper library to manage the daily limit and interact with a contract implementing the ILimitStorage interface. * @author Julien Niset - <[email protected]> */ library LimitUtils { // large limit when the limit can be considered disabled uint128 constant internal LIMIT_DISABLED = uint128(-1); using SafeMath for uint256; // *************** Internal Functions ********************* // /** * @notice Changes the daily limit (expressed in ETH). * Decreasing the limit is immediate while increasing the limit is pending for the security period. * @param _lStorage The storage contract. * @param _versionManager The version manager. * @param _wallet The target wallet. * @param _targetLimit The target limit. * @param _securityPeriod The security period. */ function changeLimit( ILimitStorage _lStorage, IVersionManager _versionManager, address _wallet, uint256 _targetLimit, uint256 _securityPeriod ) internal returns (ILimitStorage.Limit memory) { ILimitStorage.Limit memory limit = _lStorage.getLimit(_wallet); uint256 currentLimit = currentLimit(limit); ILimitStorage.Limit memory newLimit; if (_targetLimit <= currentLimit) { uint128 targetLimit = safe128(_targetLimit); newLimit = ILimitStorage.Limit(targetLimit, targetLimit, safe64(block.timestamp)); } else { newLimit = ILimitStorage.Limit(safe128(currentLimit), safe128(_targetLimit), safe64(block.timestamp.add(_securityPeriod))); } setLimit(_versionManager, _lStorage, _wallet, newLimit); return newLimit; } /** * @notice Disable the daily limit. * The change is pending for the security period. * @param _lStorage The storage contract. * @param _versionManager The version manager. * @param _wallet The target wallet. * @param _securityPeriod The security period. */ function disableLimit( ILimitStorage _lStorage, IVersionManager _versionManager, address _wallet, uint256 _securityPeriod ) internal { changeLimit(_lStorage, _versionManager, _wallet, LIMIT_DISABLED, _securityPeriod); } /** * @notice Returns whether the daily limit is disabled for a wallet. * @param _wallet The target wallet. * @return _limitDisabled true if the daily limit is disabled, false otherwise. */ function isLimitDisabled(ILimitStorage _lStorage, address _wallet) internal view returns (bool) { ILimitStorage.Limit memory limit = _lStorage.getLimit(_wallet); uint256 currentLimit = currentLimit(limit); return (currentLimit == LIMIT_DISABLED); } /** * @notice Checks if a transfer is within the limit. If yes the daily spent is updated. * @param _lStorage The storage contract. * @param _versionManager The Version Manager. * @param _wallet The target wallet. * @param _amount The amount for the transfer * @return true if the transfer is withing the daily limit. */ function checkAndUpdateDailySpent( ILimitStorage _lStorage, IVersionManager _versionManager, address _wallet, uint256 _amount ) internal returns (bool) { (ILimitStorage.Limit memory limit, ILimitStorage.DailySpent memory dailySpent) = _lStorage.getLimitAndDailySpent(_wallet); uint256 currentLimit = currentLimit(limit); if (_amount == 0 || currentLimit == LIMIT_DISABLED) { return true; } ILimitStorage.DailySpent memory newDailySpent; if (dailySpent.periodEnd <= block.timestamp && _amount <= currentLimit) { newDailySpent = ILimitStorage.DailySpent(safe128(_amount), safe64(block.timestamp + 24 hours)); setDailySpent(_versionManager, _lStorage, _wallet, newDailySpent); return true; } else if (dailySpent.periodEnd > block.timestamp && _amount.add(dailySpent.alreadySpent) <= currentLimit) { newDailySpent = ILimitStorage.DailySpent(safe128(_amount.add(dailySpent.alreadySpent)), safe64(dailySpent.periodEnd)); setDailySpent(_versionManager, _lStorage, _wallet, newDailySpent); return true; } return false; } /** * @notice Helper method to Reset the daily consumption. * @param _versionManager The Version Manager. * @param _wallet The target wallet. */ function resetDailySpent(IVersionManager _versionManager, ILimitStorage limitStorage, address _wallet) internal { setDailySpent(_versionManager, limitStorage, _wallet, ILimitStorage.DailySpent(uint128(0), uint64(0))); } /** * @notice Helper method to get the ether value equivalent of a token amount. * @notice For low value amounts of tokens we accept this to return zero as these are small enough to disregard. * Note that the price stored for tokens = price for 1 token (in ETH wei) * 10^(18-token decimals). * @param _amount The token amount. * @param _token The address of the token. * @return The ether value for _amount of _token. */ function getEtherValue(ITokenPriceRegistry _priceRegistry, uint256 _amount, address _token) internal view returns (uint256) { uint256 price = _priceRegistry.getTokenPrice(_token); uint256 etherValue = price.mul(_amount).div(10**18); return etherValue; } /** * @notice Helper method to get the current limit from a Limit struct. * @param _limit The limit struct */ function currentLimit(ILimitStorage.Limit memory _limit) internal view returns (uint256) { if (_limit.changeAfter > 0 && _limit.changeAfter < block.timestamp) { return _limit.pending; } return _limit.current; } function safe128(uint256 _num) internal pure returns (uint128) { require(_num < 2**128, "LU: more then 128 bits"); return uint128(_num); } function safe64(uint256 _num) internal pure returns (uint64) { require(_num < 2**64, "LU: more then 64 bits"); return uint64(_num); } // *************** Storage invocations in VersionManager ********************* // function setLimit( IVersionManager _versionManager, ILimitStorage _lStorage, address _wallet, ILimitStorage.Limit memory _limit ) internal { _versionManager.invokeStorage( _wallet, address(_lStorage), abi.encodeWithSelector(_lStorage.setLimit.selector, _wallet, _limit) ); } function setDailySpent( IVersionManager _versionManager, ILimitStorage _lStorage, address _wallet, ILimitStorage.DailySpent memory _dailySpent ) private { _versionManager.invokeStorage( _wallet, address(_lStorage), abi.encodeWithSelector(_lStorage.setDailySpent.selector, _wallet, _dailySpent) ); } } // File: contracts/infrastructure/storage/IGuardianStorage.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; interface IGuardianStorage { /** * @notice Lets an authorised module add a guardian to a wallet. * @param _wallet The target wallet. * @param _guardian The guardian to add. */ function addGuardian(address _wallet, address _guardian) external; /** * @notice Lets an authorised module revoke a guardian from a wallet. * @param _wallet The target wallet. * @param _guardian The guardian to revoke. */ function revokeGuardian(address _wallet, address _guardian) external; /** * @notice Checks if an account is a guardian for a wallet. * @param _wallet The target wallet. * @param _guardian The account. * @return true if the account is a guardian for a wallet. */ function isGuardian(address _wallet, address _guardian) external view returns (bool); function isLocked(address _wallet) external view returns (bool); function getLock(address _wallet) external view returns (uint256); function getLocker(address _wallet) external view returns (address); function setLock(address _wallet, uint256 _releaseAfter) external; function getGuardians(address _wallet) external view returns (address[] memory); function guardianCount(address _wallet) external view returns (uint256); } // File: modules/RelayerManager.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. /** * @title RelayerManager * @notice Feature to execute transactions signed by ETH-less accounts and sent by a relayer. * @author Julien Niset <[email protected]>, Olivier VDB <[email protected]> */ contract RelayerManager is BaseFeature { bytes32 constant NAME = "RelayerManager"; uint256 constant internal BLOCKBOUND = 10000; using SafeMath for uint256; mapping (address => RelayerConfig) public relayer; // The storage of the limit ILimitStorage public limitStorage; // The Token price storage ITokenPriceRegistry public tokenPriceRegistry; // The Guardian storage IGuardianStorage public guardianStorage; struct RelayerConfig { uint256 nonce; mapping (bytes32 => bool) executedTx; } // Used to avoid stack too deep error struct StackExtension { uint256 requiredSignatures; OwnerSignature ownerSignatureRequirement; bytes32 signHash; bool success; bytes returnData; } event TransactionExecuted(address indexed wallet, bool indexed success, bytes returnData, bytes32 signedHash); event Refund(address indexed wallet, address indexed refundAddress, address refundToken, uint256 refundAmount); /* ***************** External methods ************************* */ constructor( ILockStorage _lockStorage, IGuardianStorage _guardianStorage, ILimitStorage _limitStorage, ITokenPriceRegistry _tokenPriceRegistry, IVersionManager _versionManager ) BaseFeature(_lockStorage, _versionManager, NAME) public { limitStorage = _limitStorage; tokenPriceRegistry = _tokenPriceRegistry; guardianStorage = _guardianStorage; } /** * @notice Executes a relayed transaction. * @param _wallet The target wallet. * @param _feature The target feature. * @param _data The data for the relayed transaction * @param _nonce The nonce used to prevent replay attacks. * @param _signatures The signatures as a concatenated byte array. * @param _gasPrice The gas price to use for the gas refund. * @param _gasLimit The gas limit to use for the gas refund. * @param _refundToken The token to use for the gas refund. * @param _refundAddress The address refunded to prevent front-running. */ function execute( address _wallet, address _feature, bytes calldata _data, uint256 _nonce, bytes calldata _signatures, uint256 _gasPrice, uint256 _gasLimit, address _refundToken, address _refundAddress ) external returns (bool) { uint startGas = gasleft(); require(startGas >= _gasLimit, "RM: not enough gas provided"); require(verifyData(_wallet, _data), "RM: Target of _data != _wallet"); require(isFeatureAuthorisedInVersionManager(_wallet, _feature), "RM: feature not authorised"); StackExtension memory stack; (stack.requiredSignatures, stack.ownerSignatureRequirement) = IFeature(_feature).getRequiredSignatures(_wallet, _data); require(stack.requiredSignatures > 0 || stack.ownerSignatureRequirement == OwnerSignature.Anyone, "RM: Wrong signature requirement"); require(stack.requiredSignatures * 65 == _signatures.length, "RM: Wrong number of signatures"); stack.signHash = getSignHash( address(this), _feature, 0, _data, _nonce, _gasPrice, _gasLimit, _refundToken, _refundAddress); require(checkAndUpdateUniqueness( _wallet, _nonce, stack.signHash, stack.requiredSignatures, stack.ownerSignatureRequirement), "RM: Duplicate request"); require(validateSignatures(_wallet, stack.signHash, _signatures, stack.ownerSignatureRequirement), "RM: Invalid signatures"); (stack.success, stack.returnData) = _feature.call(_data); // only refund when approved by owner and positive gas price if (_gasPrice > 0 && stack.ownerSignatureRequirement == OwnerSignature.Required) { refund( _wallet, startGas, _gasPrice, _gasLimit, _refundToken, _refundAddress, stack.requiredSignatures); } emit TransactionExecuted(_wallet, stack.success, stack.returnData, stack.signHash); return stack.success; } /** * @notice Gets the current nonce for a wallet. * @param _wallet The target wallet. */ function getNonce(address _wallet) external view returns (uint256 nonce) { return relayer[_wallet].nonce; } /** * @notice Checks if a transaction identified by its sign hash has already been executed. * @param _wallet The target wallet. * @param _signHash The sign hash of the transaction. */ function isExecutedTx(address _wallet, bytes32 _signHash) external view returns (bool executed) { return relayer[_wallet].executedTx[_signHash]; } /* ***************** Internal & Private methods ************************* */ /** * @notice Generates the signed hash of a relayed transaction according to ERC 1077. * @param _from The starting address for the relayed transaction (should be the relayer module) * @param _to The destination address for the relayed transaction (should be the target module) * @param _value The value for the relayed transaction. * @param _data The data for the relayed transaction which includes the wallet address. * @param _nonce The nonce used to prevent replay attacks. * @param _gasPrice The gas price to use for the gas refund. * @param _gasLimit The gas limit to use for the gas refund. * @param _refundToken The token to use for the gas refund. * @param _refundAddress The address refunded to prevent front-running. */ function getSignHash( address _from, address _to, uint256 _value, bytes memory _data, uint256 _nonce, uint256 _gasPrice, uint256 _gasLimit, address _refundToken, address _refundAddress ) internal pure returns (bytes32) { return keccak256( abi.encodePacked( "\x19Ethereum Signed Message:\n32", keccak256(abi.encodePacked( byte(0x19), byte(0), _from, _to, _value, _data, getChainId(), _nonce, _gasPrice, _gasLimit, _refundToken, _refundAddress)) )); } /** * @notice Checks if the relayed transaction is unique. If yes the state is updated. * For actions requiring 1 signature by the owner we use the incremental nonce. * For all other actions we check/store the signHash in a mapping. * @param _wallet The target wallet. * @param _nonce The nonce. * @param _signHash The signed hash of the transaction. * @param requiredSignatures The number of signatures required. * @param ownerSignatureRequirement The wallet owner signature requirement. * @return true if the transaction is unique. */ function checkAndUpdateUniqueness( address _wallet, uint256 _nonce, bytes32 _signHash, uint256 requiredSignatures, OwnerSignature ownerSignatureRequirement ) internal returns (bool) { if (requiredSignatures == 1 && ownerSignatureRequirement == OwnerSignature.Required) { // use the incremental nonce if (_nonce <= relayer[_wallet].nonce) { return false; } uint256 nonceBlock = (_nonce & 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000) >> 128; if (nonceBlock > block.number + BLOCKBOUND) { return false; } relayer[_wallet].nonce = _nonce; return true; } else { // use the txHash map if (relayer[_wallet].executedTx[_signHash] == true) { return false; } relayer[_wallet].executedTx[_signHash] = true; return true; } } /** * @notice Validates the signatures provided with a relayed transaction. * The method MUST throw if one or more signatures are not valid. * @param _wallet The target wallet. * @param _signHash The signed hash representing the relayed transaction. * @param _signatures The signatures as a concatenated byte array. * @param _option An enum indicating whether the owner is required, optional or disallowed. * @return A boolean indicating whether the signatures are valid. */ function validateSignatures( address _wallet, bytes32 _signHash, bytes memory _signatures, OwnerSignature _option ) internal view returns (bool) { if (_signatures.length == 0) { return true; } address lastSigner = address(0); address[] memory guardians; if (_option != OwnerSignature.Required || _signatures.length > 65) { guardians = guardianStorage.getGuardians(_wallet); // guardians are only read if they may be needed } bool isGuardian; for (uint256 i = 0; i < _signatures.length / 65; i++) { address signer = Utils.recoverSigner(_signHash, _signatures, i); if (i == 0) { if (_option == OwnerSignature.Required) { // First signer must be owner if (isOwner(_wallet, signer)) { continue; } return false; } else if (_option == OwnerSignature.Optional) { // First signer can be owner if (isOwner(_wallet, signer)) { continue; } } } if (signer <= lastSigner) { return false; // Signers must be different } lastSigner = signer; (isGuardian, guardians) = GuardianUtils.isGuardianOrGuardianSigner(guardians, signer); if (!isGuardian) { return false; } } return true; } /** * @notice Refunds the gas used to the Relayer. * @param _wallet The target wallet. * @param _startGas The gas provided at the start of the execution. * @param _gasPrice The gas price for the refund. * @param _gasLimit The gas limit for the refund. * @param _refundToken The token to use for the gas refund. * @param _refundAddress The address refunded to prevent front-running. * @param _requiredSignatures The number of signatures required. */ function refund( address _wallet, uint _startGas, uint _gasPrice, uint _gasLimit, address _refundToken, address _refundAddress, uint256 _requiredSignatures ) internal { address refundAddress = _refundAddress == address(0) ? msg.sender : _refundAddress; uint256 refundAmount; // skip daily limit when approved by guardians (and signed by owner) if (_requiredSignatures > 1) { uint256 gasConsumed = _startGas.sub(gasleft()).add(30000); refundAmount = Utils.min(gasConsumed, _gasLimit).mul(_gasPrice); } else { uint256 gasConsumed = _startGas.sub(gasleft()).add(40000); refundAmount = Utils.min(gasConsumed, _gasLimit).mul(_gasPrice); uint256 ethAmount = (_refundToken == ETH_TOKEN) ? refundAmount : LimitUtils.getEtherValue(tokenPriceRegistry, refundAmount, _refundToken); require(LimitUtils.checkAndUpdateDailySpent(limitStorage, versionManager, _wallet, ethAmount), "RM: refund is above daily limit"); } // refund in ETH or ERC20 if (_refundToken == ETH_TOKEN) { invokeWallet(_wallet, refundAddress, refundAmount, EMPTY_BYTES); } else { bytes memory methodData = abi.encodeWithSignature("transfer(address,uint256)", refundAddress, refundAmount); bytes memory transferSuccessBytes = invokeWallet(_wallet, _refundToken, 0, methodData); // Check token refund is successful, when `transfer` returns a success bool result if (transferSuccessBytes.length > 0) { require(abi.decode(transferSuccessBytes, (bool)), "RM: Refund transfer failed"); } } emit Refund(_wallet, refundAddress, _refundToken, refundAmount); } /** * @notice Returns the current chainId * @return chainId the chainId */ function getChainId() private pure returns (uint256 chainId) { // solhint-disable-next-line no-inline-assembly assembly { chainId := chainid() } } }
File 2 of 6: Proxy
pragma solidity ^0.6.12; // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only /** * @title Proxy * @notice Basic proxy that delegates all calls to a fixed implementing contract. * The implementing contract cannot be upgraded. * @author Julien Niset - <[email protected]> */ contract Proxy { address implementation; event Received(uint indexed value, address indexed sender, bytes data); constructor(address _implementation) public { implementation = _implementation; } fallback() external payable { // solhint-disable-next-line no-inline-assembly assembly { let target := sload(0) calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), target, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 {revert(0, returndatasize())} default {return (0, returndatasize())} } } receive() external payable { emit Received(msg.value, msg.sender, msg.data); } }
File 3 of 6: TransferManager
// Copyright (C) 2020 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.5.4 <0.7.0; /** * @title IModuleRegistry * @notice Interface for the registry of authorised modules. */ interface IModuleRegistry { function registerModule(address _module, bytes32 _name) external; function deregisterModule(address _module) external; function registerUpgrader(address _upgrader, bytes32 _name) external; function deregisterUpgrader(address _upgrader) external; function recoverToken(address _token) external; function moduleInfo(address _module) external view returns (bytes32); function upgraderInfo(address _upgrader) external view returns (bytes32); function isRegisteredModule(address _module) external view returns (bool); function isRegisteredModule(address[] calldata _modules) external view returns (bool); function isRegisteredUpgrader(address _upgrader) external view returns (bool); }// 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.6.12; /** * @title ITokenPriceRegistry * @notice TokenPriceRegistry interface */ interface ITokenPriceRegistry { function getTokenPrice(address _token) external view returns (uint184 _price); function isTokenTradable(address _token) external view returns (bool _isTradable); }// 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; /** * @title ILimitStorage * @notice LimitStorage interface */ interface ILimitStorage { struct Limit { // the current limit uint128 current; // the pending limit if any uint128 pending; // when the pending limit becomes the current limit uint64 changeAfter; } struct DailySpent { // The amount already spent during the current period uint128 alreadySpent; // The end of the current period uint64 periodEnd; } function setLimit(address _wallet, Limit memory _limit) external; function getLimit(address _wallet) external view returns (Limit memory _limit); function setDailySpent(address _wallet, DailySpent memory _dailySpent) external; function getDailySpent(address _wallet) external view returns (DailySpent memory _dailySpent); function setLimitAndDailySpent(address _wallet, Limit memory _limit, DailySpent memory _dailySpent) external; function getLimitAndDailySpent(address _wallet) external view returns (Limit memory _limit, DailySpent memory _dailySpent); }// Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.5.4 <0.7.0; interface ILockStorage { function isLocked(address _wallet) external view returns (bool); function getLock(address _wallet) external view returns (uint256); function getLocker(address _wallet) external view returns (address); function setLock(address _wallet, address _locker, uint256 _releaseAfter) external; }// Copyright (C) 2020 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.5.4 <0.7.0; /** * @title ITransferStorage * @notice TransferStorage interface */ interface ITransferStorage { function setWhitelist(address _wallet, address _target, uint256 _value) external; function getWhitelist(address _wallet, address _target) external view returns (uint256); }// Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import "./common/Utils.sol"; import "./common/BaseTransfer.sol"; import "./common/LimitUtils.sol"; import "../infrastructure/storage/ILimitStorage.sol"; import "../infrastructure/storage/ITransferStorage.sol"; import "../infrastructure/ITokenPriceRegistry.sol"; import "../../lib/other/ERC20.sol"; /** * @title TransferManager * @notice Feature to transfer and approve tokens (ETH or ERC20) or data (contract call) based on a security context (daily limit, whitelist, etc). * @author Julien Niset - <[email protected]> */ contract TransferManager is BaseTransfer { bytes32 constant NAME = "TransferManager"; bytes4 private constant ERC1271_ISVALIDSIGNATURE_BYTES32 = bytes4(keccak256("isValidSignature(bytes32,bytes)")); enum ActionType { Transfer } using SafeMath for uint256; struct TokenManagerConfig { // Mapping between pending action hash and their timestamp mapping (bytes32 => uint256) pendingActions; } // wallet specific storage mapping (address => TokenManagerConfig) internal configs; // The security period uint256 public securityPeriod; // The execution window uint256 public securityWindow; // The default limit uint128 public defaultLimit; // The Token storage ITransferStorage public transferStorage; // The previous limit manager needed to migrate the limits TransferManager public oldTransferManager; // The limit storage ILimitStorage public limitStorage; // The token price storage ITokenPriceRegistry public tokenPriceRegistry; // *************** Events *************************** // event AddedToWhitelist(address indexed wallet, address indexed target, uint64 whitelistAfter); event RemovedFromWhitelist(address indexed wallet, address indexed target); event PendingTransferCreated(address indexed wallet, bytes32 indexed id, uint256 indexed executeAfter, address token, address to, uint256 amount, bytes data); event PendingTransferExecuted(address indexed wallet, bytes32 indexed id); event PendingTransferCanceled(address indexed wallet, bytes32 indexed id); event DailyLimitMigrated(address indexed wallet, uint256 currentDailyLimit, uint256 pendingDailyLimit, uint256 changeDailyLimitAfter); event DailyLimitDisabled(address indexed wallet, uint256 securityPeriod); // *************** Constructor ********************** // constructor( ILockStorage _lockStorage, ITransferStorage _transferStorage, ILimitStorage _limitStorage, ITokenPriceRegistry _tokenPriceRegistry, IVersionManager _versionManager, uint256 _securityPeriod, uint256 _securityWindow, uint256 _defaultLimit, address _wethToken, TransferManager _oldTransferManager ) BaseFeature(_lockStorage, _versionManager, NAME) BaseTransfer(_wethToken) public { transferStorage = _transferStorage; limitStorage = _limitStorage; tokenPriceRegistry = _tokenPriceRegistry; securityPeriod = _securityPeriod; securityWindow = _securityWindow; defaultLimit = LimitUtils.safe128(_defaultLimit); oldTransferManager = _oldTransferManager; } /** * @inheritdoc IFeature */ function getRequiredSignatures(address, bytes calldata) external view override returns (uint256, OwnerSignature) { return (1, OwnerSignature.Required); } /** * @inheritdoc IFeature */ function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) { _sigs = new bytes4[](1); _sigs[0] = ERC1271_ISVALIDSIGNATURE_BYTES32; } /** * @notice Inits the feature for a wallet by setting up the isValidSignature (EIP 1271) * static call redirection from the wallet to the feature and copying all the parameters * of the daily limit from the previous implementation of the LimitManager module. * @param _wallet The target wallet. */ function init(address _wallet) external override(BaseFeature) onlyVersionManager { if (address(oldTransferManager) == address(0)) { setLimit(_wallet, ILimitStorage.Limit(defaultLimit, 0, 0)); } else { uint256 current = oldTransferManager.getCurrentLimit(_wallet); (uint256 pending, uint64 changeAfter) = oldTransferManager.getPendingLimit(_wallet); if (current == 0 && changeAfter == 0) { // new wallet: we setup the default limit setLimit(_wallet, ILimitStorage.Limit(defaultLimit, 0, 0)); } else { // migrate limit and daily spent (if we are in a rolling period) (uint256 unspent, uint64 periodEnd) = oldTransferManager.getDailyUnspent(_wallet); if (periodEnd < block.timestamp) { setLimit(_wallet, ILimitStorage.Limit(LimitUtils.safe128(current), LimitUtils.safe128(pending), changeAfter)); } else { setLimitAndDailySpent( _wallet, ILimitStorage.Limit(LimitUtils.safe128(current), LimitUtils.safe128(pending), changeAfter), ILimitStorage.DailySpent(LimitUtils.safe128(current.sub(unspent)), periodEnd) ); } emit DailyLimitMigrated(_wallet, current, pending, changeAfter); } } } // *************** External/Public Functions ********************* // /** * @notice Lets the owner transfer tokens (ETH or ERC20) from a wallet. * @param _wallet The target wallet. * @param _token The address of the token to transfer. * @param _to The destination address * @param _amount The amoutn of token to transfer * @param _data The data for the transaction */ function transferToken( address _wallet, address _token, address _to, uint256 _amount, bytes calldata _data ) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { if (isWhitelisted(_wallet, _to)) { // transfer to whitelist doTransfer(_wallet, _token, _to, _amount, _data); } else { uint256 etherAmount = (_token == ETH_TOKEN) ? _amount : LimitUtils.getEtherValue(tokenPriceRegistry, _amount, _token); if (LimitUtils.checkAndUpdateDailySpent(limitStorage, versionManager, _wallet, etherAmount)) { // transfer under the limit doTransfer(_wallet, _token, _to, _amount, _data); } else { // transfer above the limit (bytes32 id, uint256 executeAfter) = addPendingAction(ActionType.Transfer, _wallet, _token, _to, _amount, _data); emit PendingTransferCreated(_wallet, id, executeAfter, _token, _to, _amount, _data); } } } /** * @notice Lets the owner approve an allowance of ERC20 tokens for a spender (dApp). * @param _wallet The target wallet. * @param _token The address of the token to transfer. * @param _spender The address of the spender * @param _amount The amount of tokens to approve */ function approveToken( address _wallet, address _token, address _spender, uint256 _amount ) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { if (isWhitelisted(_wallet, _spender)) { // approve to whitelist doApproveToken(_wallet, _token, _spender, _amount); } else { // get current alowance uint256 currentAllowance = ERC20(_token).allowance(_wallet, _spender); if (_amount <= currentAllowance) { // approve if we reduce the allowance doApproveToken(_wallet, _token, _spender, _amount); } else { // check if delta is under the limit uint delta = _amount - currentAllowance; uint256 deltaInEth = LimitUtils.getEtherValue(tokenPriceRegistry, delta, _token); require(LimitUtils.checkAndUpdateDailySpent(limitStorage, versionManager, _wallet, deltaInEth), "TM: Approve above daily limit"); // approve if under the limit doApproveToken(_wallet, _token, _spender, _amount); } } } /** * @notice Lets the owner call a contract. * @param _wallet The target wallet. * @param _contract The address of the contract. * @param _value The amount of ETH to transfer as part of call * @param _data The encoded method data */ function callContract( address _wallet, address _contract, uint256 _value, bytes calldata _data ) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) onlyAuthorisedContractCall(_wallet, _contract) { checkAndUpdateDailySpentIfNeeded(_wallet, ETH_TOKEN, _value, _contract); doCallContract(_wallet, _contract, _value, _data); } /** * @notice Lets the owner do an ERC20 approve followed by a call to a contract. * We assume that the contract will pull the tokens and does not require ETH. * @param _wallet The target wallet. * @param _token The token to approve. * @param _proxy The address to approve, which may be different from the contract being called. * @param _amount The amount of ERC20 tokens to approve. * @param _contract The address of the contract. * @param _data The encoded method data */ function approveTokenAndCallContract( address _wallet, address _token, address _proxy, uint256 _amount, address _contract, bytes calldata _data ) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) onlyAuthorisedContractCall(_wallet, _contract) { checkAndUpdateDailySpentIfNeeded(_wallet, _token, _amount, _contract); doApproveTokenAndCallContract(_wallet, _token, _proxy, _amount, _contract, _data); } /** * @notice Lets the owner wrap ETH into WETH, approve the WETH and call a contract. * We assume that the contract will pull the tokens and does not require ETH. * @param _wallet The target wallet. * @param _proxy The address to approve, which may be different from the contract being called. * @param _amount The amount of ETH to wrap and approve. * @param _contract The address of the contract. * @param _data The encoded method data */ function approveWethAndCallContract( address _wallet, address _proxy, uint256 _amount, address _contract, bytes calldata _data ) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) onlyAuthorisedContractCall(_wallet, _contract) { checkAndUpdateDailySpentIfNeeded(_wallet, wethToken, _amount, _contract); doApproveWethAndCallContract(_wallet, _proxy, _amount, _contract, _data); } /** * @notice Adds an address to the whitelist of a wallet. * @param _wallet The target wallet. * @param _target The address to add. */ function addToWhitelist( address _wallet, address _target ) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { require(!isWhitelisted(_wallet, _target), "TT: target already whitelisted"); uint256 whitelistAfter = block.timestamp.add(securityPeriod); setWhitelist(_wallet, _target, whitelistAfter); emit AddedToWhitelist(_wallet, _target, uint64(whitelistAfter)); } /** * @notice Removes an address from the whitelist of a wallet. * @param _wallet The target wallet. * @param _target The address to remove. */ function removeFromWhitelist( address _wallet, address _target ) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { setWhitelist(_wallet, _target, 0); emit RemovedFromWhitelist(_wallet, _target); } /** * @notice Executes a pending transfer for a wallet. * The method can be called by anyone to enable orchestration. * @param _wallet The target wallet. * @param _token The token of the pending transfer. * @param _to The destination address of the pending transfer. * @param _amount The amount of token to transfer of the pending transfer. * @param _data The data associated to the pending transfer. * @param _block The block at which the pending transfer was created. */ function executePendingTransfer( address _wallet, address _token, address _to, uint _amount, bytes calldata _data, uint _block ) external onlyWhenUnlocked(_wallet) { bytes32 id = keccak256(abi.encodePacked(ActionType.Transfer, _token, _to, _amount, _data, _block)); uint executeAfter = configs[_wallet].pendingActions[id]; require(executeAfter > 0, "TT: unknown pending transfer"); uint executeBefore = executeAfter.add(securityWindow); require(executeAfter <= block.timestamp && block.timestamp <= executeBefore, "TT: transfer outside of the execution window"); delete configs[_wallet].pendingActions[id]; doTransfer(_wallet, _token, _to, _amount, _data); emit PendingTransferExecuted(_wallet, id); } function cancelPendingTransfer( address _wallet, bytes32 _id ) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { require(configs[_wallet].pendingActions[_id] > 0, "TT: unknown pending action"); delete configs[_wallet].pendingActions[_id]; emit PendingTransferCanceled(_wallet, _id); } /** * @notice Lets the owner of a wallet change its daily limit. * The limit is expressed in ETH. Changes to the limit take 24 hours. * @param _wallet The target wallet. * @param _newLimit The new limit. */ function changeLimit(address _wallet, uint256 _newLimit) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { ILimitStorage.Limit memory limit = LimitUtils.changeLimit(limitStorage, versionManager, _wallet, _newLimit, securityPeriod); emit LimitChanged(_wallet, _newLimit, limit.changeAfter); } /** * @notice Convenience method to disable the limit * The limit is disabled by setting it to an arbitrary large value. * @param _wallet The target wallet. */ function disableLimit(address _wallet) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { LimitUtils.disableLimit(limitStorage, versionManager, _wallet, securityPeriod); emit DailyLimitDisabled(_wallet, securityPeriod); } /** * @notice Gets the current daily limit for a wallet. * @param _wallet The target wallet. * @return _currentLimit The current limit expressed in ETH. */ function getCurrentLimit(address _wallet) external view returns (uint256 _currentLimit) { ILimitStorage.Limit memory limit = limitStorage.getLimit(_wallet); return LimitUtils.currentLimit(limit); } /** * @notice Returns whether the daily limit is disabled for a wallet. * @param _wallet The target wallet. * @return _limitDisabled true if the daily limit is disabled, false otherwise. */ function isLimitDisabled(address _wallet) public view returns (bool _limitDisabled) { return LimitUtils.isLimitDisabled(limitStorage, _wallet); } /** * @notice Gets a pending limit for a wallet if any. * @param _wallet The target wallet. * @return _pendingLimit The pending limit (in ETH). * @return _changeAfter The time at which the pending limit will become effective. */ function getPendingLimit(address _wallet) external view returns (uint256 _pendingLimit, uint64 _changeAfter) { ILimitStorage.Limit memory limit = limitStorage.getLimit(_wallet); return ((block.timestamp < limit.changeAfter)? (limit.pending, uint64(limit.changeAfter)) : (0,0)); } /** * @notice Gets the amount of tokens that has not yet been spent during the current period. * @param _wallet The target wallet. * @return _unspent The amount of tokens (in ETH) that has not been spent yet. * @return _periodEnd The end of the daily period. */ function getDailyUnspent(address _wallet) external view returns (uint256 _unspent, uint64 _periodEnd) { ( ILimitStorage.Limit memory limit, ILimitStorage.DailySpent memory dailySpent ) = limitStorage.getLimitAndDailySpent(_wallet); uint256 currentLimit = LimitUtils.currentLimit(limit); if (block.timestamp > dailySpent.periodEnd) { return (currentLimit, uint64(block.timestamp.add(24 hours))); } else if (dailySpent.alreadySpent < currentLimit) { return (currentLimit.sub(dailySpent.alreadySpent), dailySpent.periodEnd); } else { return (0, dailySpent.periodEnd); } } /** * @notice Checks if an address is whitelisted for a wallet. * @param _wallet The target wallet. * @param _target The address. * @return _isWhitelisted true if the address is whitelisted. */ function isWhitelisted(address _wallet, address _target) public view returns (bool _isWhitelisted) { uint whitelistAfter = transferStorage.getWhitelist(_wallet, _target); return whitelistAfter > 0 && whitelistAfter < block.timestamp; } /** * @notice Gets the info of a pending transfer for a wallet. * @param _wallet The target wallet. * @param _id The pending transfer ID. * @return _executeAfter The epoch time at which the pending transfer can be executed. */ function getPendingTransfer(address _wallet, bytes32 _id) external view returns (uint64 _executeAfter) { _executeAfter = uint64(configs[address(_wallet)].pendingActions[_id]); } /** * @notice Implementation of EIP 1271. * Should return whether the signature provided is valid for the provided data. * @param _msgHash Hash of a message signed on the behalf of address(this) * @param _signature Signature byte array associated with _msgHash */ function isValidSignature(bytes32 _msgHash, bytes memory _signature) public view returns (bytes4) { require(_signature.length == 65, "TM: invalid signature length"); address signer = Utils.recoverSigner(_msgHash, _signature, 0); require(isOwner(msg.sender, signer), "TM: Invalid signer"); return ERC1271_ISVALIDSIGNATURE_BYTES32; } // *************** Internal Functions ********************* // /** * @notice Creates a new pending action for a wallet. * @param _action The target action. * @param _wallet The target wallet. * @param _token The target token for the action. * @param _to The recipient of the action. * @param _amount The amount of token associated to the action. * @param _data The data associated to the action. * @return id The identifier for the new pending action. * @return executeAfter The time when the action can be executed */ function addPendingAction( ActionType _action, address _wallet, address _token, address _to, uint _amount, bytes memory _data ) internal returns (bytes32 id, uint256 executeAfter) { id = keccak256(abi.encodePacked(_action, _token, _to, _amount, _data, block.number)); require(configs[_wallet].pendingActions[id] == 0, "TM: duplicate pending action"); executeAfter = block.timestamp.add(securityPeriod); configs[_wallet].pendingActions[id] = executeAfter; } /** * @notice Make sure a contract call is not trying to call a supported ERC20. * @param _wallet The target wallet. * @param _contract The address of the contract. */ function coveredByDailyLimit(address _wallet, address _contract) internal view returns (bool) { return (tokenPriceRegistry.getTokenPrice(_contract) > 0 && !isLimitDisabled(_wallet)); } /** * @notice Verify and update the daily spent if the spender is not whitelisted. * Reverts if the daily spent is insufficient or if the contract to call is * protected by the daily limit (i.e. is a token contract). * @param _wallet The target wallet. * @param _token The token that the spender will spend. * @param _amount The amount of ERC20 or ETH that the spender will spend. * @param _contract The address of the contract called by the wallet for the spend to occur. */ function checkAndUpdateDailySpentIfNeeded( address _wallet, address _token, uint256 _amount, address _contract ) internal { if (!isWhitelisted(_wallet, _contract)) { // Make sure we don't call a supported ERC20 that's not whitelisted require(!coveredByDailyLimit(_wallet, _contract), "TM: Forbidden contract"); // Check if the amount is under the daily limit. // Check the entire amount because the currently approved amount will be restored and should still count towards the daily limit uint256 valueInEth; if (_token == ETH_TOKEN || _token == wethToken) { valueInEth = _amount; } else { valueInEth = LimitUtils.getEtherValue(tokenPriceRegistry, _amount, _token); } require(LimitUtils.checkAndUpdateDailySpent(limitStorage, versionManager, _wallet, valueInEth), "TM: Approve above daily limit"); } } // *************** Internal Functions ********************* // function setWhitelist(address _wallet, address _target, uint256 _whitelistAfter) internal { versionManager.invokeStorage( _wallet, address(transferStorage), abi.encodeWithSelector(transferStorage.setWhitelist.selector, _wallet, _target, _whitelistAfter) ); } function setLimit(address _wallet, ILimitStorage.Limit memory _limit) internal { versionManager.invokeStorage( _wallet, address(limitStorage), abi.encodeWithSelector(limitStorage.setLimit.selector, _wallet, _limit) ); } function setLimitAndDailySpent( address _wallet, ILimitStorage.Limit memory _limit, ILimitStorage.DailySpent memory _dailySpent ) internal { versionManager.invokeStorage( _wallet, address(limitStorage), abi.encodeWithSelector(limitStorage.setLimitAndDailySpent.selector, _wallet, _limit, _dailySpent) ); } } // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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.s // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.6.12; import "@openzeppelin/contracts/math/SafeMath.sol"; import "../../wallet/IWallet.sol"; import "../../infrastructure/IModuleRegistry.sol"; import "../../infrastructure/storage/ILockStorage.sol"; import "./IFeature.sol"; import "../../../lib/other/ERC20.sol"; import "./IVersionManager.sol"; /** * @title BaseFeature * @notice Base Feature contract that contains methods common to all Feature contracts. * @author Julien Niset - <[email protected]>, Olivier VDB - <[email protected]> */ contract BaseFeature is IFeature { // Empty calldata bytes constant internal EMPTY_BYTES = ""; // Mock token address for ETH address constant internal ETH_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // The address of the Lock storage ILockStorage internal lockStorage; // The address of the Version Manager IVersionManager internal versionManager; event FeatureCreated(bytes32 name); /** * @notice Throws if the wallet is locked. */ modifier onlyWhenUnlocked(address _wallet) { require(!lockStorage.isLocked(_wallet), "BF: wallet locked"); _; } /** * @notice Throws if the sender is not the VersionManager. */ modifier onlyVersionManager() { require(msg.sender == address(versionManager), "BF: caller must be VersionManager"); _; } /** * @notice Throws if the sender is not the owner of the target wallet. */ modifier onlyWalletOwner(address _wallet) { require(isOwner(_wallet, msg.sender), "BF: must be wallet owner"); _; } /** * @notice Throws if the sender is not an authorised feature of the target wallet. */ modifier onlyWalletFeature(address _wallet) { require(versionManager.isFeatureAuthorised(_wallet, msg.sender), "BF: must be a wallet feature"); _; } /** * @notice Throws if the sender is not the owner of the target wallet or the feature itself. */ modifier onlyWalletOwnerOrFeature(address _wallet) { // Wrapping in an internal method reduces deployment cost by avoiding duplication of inlined code verifyOwnerOrAuthorisedFeature(_wallet, msg.sender); _; } constructor( ILockStorage _lockStorage, IVersionManager _versionManager, bytes32 _name ) public { lockStorage = _lockStorage; versionManager = _versionManager; emit FeatureCreated(_name); } /** * @inheritdoc IFeature */ function recoverToken(address _token) external virtual override { uint total = ERC20(_token).balanceOf(address(this)); _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector, address(versionManager), total)); } /** * @notice Inits the feature for a wallet by doing nothing. * @dev !! Overriding methods need make sure `init()` can only be called by the VersionManager !! * @param _wallet The wallet. */ function init(address _wallet) external virtual override {} /** * @inheritdoc IFeature */ function getRequiredSignatures(address, bytes calldata) external virtual view override returns (uint256, OwnerSignature) { revert("BF: disabled method"); } /** * @inheritdoc IFeature */ function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) {} /** * @inheritdoc IFeature */ function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) public override view returns (bool) { return versionManager.isFeatureAuthorised(_wallet, _feature); } /** * @notice Checks that the wallet address provided as the first parameter of _data matches _wallet * @return false if the addresses are different. */ function verifyData(address _wallet, bytes calldata _data) internal pure returns (bool) { require(_data.length >= 36, "RM: Invalid dataWallet"); address dataWallet = abi.decode(_data[4:], (address)); return dataWallet == _wallet; } /** * @notice Helper method to check if an address is the owner of a target wallet. * @param _wallet The target wallet. * @param _addr The address. */ function isOwner(address _wallet, address _addr) internal view returns (bool) { return IWallet(_wallet).owner() == _addr; } /** * @notice Verify that the caller is an authorised feature or the wallet owner. * @param _wallet The target wallet. * @param _sender The caller. */ function verifyOwnerOrAuthorisedFeature(address _wallet, address _sender) internal view { require(isFeatureAuthorisedInVersionManager(_wallet, _sender) || isOwner(_wallet, _sender), "BF: must be owner or feature"); } /** * @notice Helper method to invoke a wallet. * @param _wallet The target wallet. * @param _to The target address for the transaction. * @param _value The value of the transaction. * @param _data The data of the transaction. */ function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data) internal returns (bytes memory _res) { _res = versionManager.checkAuthorisedFeatureAndInvokeWallet(_wallet, _to, _value, _data); } }// Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.6.12; import "./BaseFeature.sol"; import "./LimitUtils.sol"; /** * @title BaseTransfer * @notice Contains common methods to transfer tokens or call third-party contracts. * @author Olivier VDB - <[email protected]> */ abstract contract BaseTransfer is BaseFeature { // The address of the WETH token address public wethToken; // *************** Events *************************** // event Transfer(address indexed wallet, address indexed token, uint256 indexed amount, address to, bytes data); event Approved(address indexed wallet, address indexed token, uint256 amount, address spender); event CalledContract(address indexed wallet, address indexed to, uint256 amount, bytes data); event ApprovedAndCalledContract( address indexed wallet, address indexed to, address spender, address indexed token, uint256 amountApproved, uint256 amountSpent, bytes data ); event LimitChanged(address indexed wallet, uint indexed newLimit, uint64 indexed startAfter); // *************** Constructor ********************** // constructor(address _wethToken) public { wethToken = _wethToken; } // *************** Internal Functions ********************* // /** * @notice Make sure a contract call is not trying to call a module, a feature, or the wallet itself. * @param _wallet The target wallet. * @param _contract The address of the contract. */ modifier onlyAuthorisedContractCall(address _wallet, address _contract) { require( _contract != _wallet && // not calling the wallet !IWallet(_wallet).authorised(_contract) && // not calling an authorised module !versionManager.isFeatureAuthorised(_wallet, _contract), // not calling an authorised feature "BT: Forbidden contract" ); _; } /** * @notice Helper method to transfer ETH or ERC20 for a wallet. * @param _wallet The target wallet. * @param _token The ERC20 address. * @param _to The recipient. * @param _value The amount of ETH to transfer * @param _data The data to *log* with the transfer. */ function doTransfer(address _wallet, address _token, address _to, uint256 _value, bytes memory _data) internal { if (_token == ETH_TOKEN) { invokeWallet(_wallet, _to, _value, EMPTY_BYTES); } else { bytes memory methodData = abi.encodeWithSignature("transfer(address,uint256)", _to, _value); bytes memory transferSuccessBytes = invokeWallet(_wallet, _token, 0, methodData); // Check transfer is successful, when `transfer` returns a success bool result if (transferSuccessBytes.length > 0) { require(abi.decode(transferSuccessBytes, (bool)), "RM: Transfer failed"); } } emit Transfer(_wallet, _token, _value, _to, _data); } /** * @notice Helper method to approve spending the ERC20 of a wallet. * @param _wallet The target wallet. * @param _token The ERC20 address. * @param _spender The spender address. * @param _value The amount of token to transfer. */ function doApproveToken(address _wallet, address _token, address _spender, uint256 _value) internal { bytes memory methodData = abi.encodeWithSignature("approve(address,uint256)", _spender, _value); invokeWallet(_wallet, _token, 0, methodData); emit Approved(_wallet, _token, _value, _spender); } /** * @notice Helper method to call an external contract. * @param _wallet The target wallet. * @param _contract The contract address. * @param _value The ETH value to transfer. * @param _data The method data. */ function doCallContract(address _wallet, address _contract, uint256 _value, bytes memory _data) internal { invokeWallet(_wallet, _contract, _value, _data); emit CalledContract(_wallet, _contract, _value, _data); } /** * @notice Helper method to approve a certain amount of token and call an external contract. * The address that spends the _token and the address that is called with _data can be different. * @param _wallet The target wallet. * @param _token The ERC20 address. * @param _proxy The address to approve. * @param _amount The amount of tokens to transfer. * @param _contract The contract address. * @param _data The method data. */ function doApproveTokenAndCallContract( address _wallet, address _token, address _proxy, uint256 _amount, address _contract, bytes memory _data ) internal { // Ensure there is sufficient balance of token before we approve uint256 balance = ERC20(_token).balanceOf(_wallet); require(balance >= _amount, "BT: insufficient balance"); uint256 existingAllowance = ERC20(_token).allowance(_wallet, _proxy); uint256 totalAllowance = SafeMath.add(existingAllowance, _amount); // Approve the desired amount plus existing amount. This logic allows for potential gas saving later // when restoring the original approved amount, in cases where the _proxy uses the exact approved _amount. bytes memory methodData = abi.encodeWithSignature("approve(address,uint256)", _proxy, totalAllowance); invokeWallet(_wallet, _token, 0, methodData); invokeWallet(_wallet, _contract, 0, _data); // Calculate the approved amount that was spent after the call uint256 unusedAllowance = ERC20(_token).allowance(_wallet, _proxy); uint256 usedAllowance = SafeMath.sub(totalAllowance, unusedAllowance); // Ensure the amount spent does not exceed the amount approved for this call require(usedAllowance <= _amount, "BT: insufficient amount for call"); if (unusedAllowance != existingAllowance) { // Restore the original allowance amount if the amount spent was different (can be lower). methodData = abi.encodeWithSignature("approve(address,uint256)", _proxy, existingAllowance); invokeWallet(_wallet, _token, 0, methodData); } emit ApprovedAndCalledContract( _wallet, _contract, _proxy, _token, _amount, usedAllowance, _data); } /** * @notice Helper method to wrap ETH into WETH, approve a certain amount of WETH and call an external contract. * The address that spends the WETH and the address that is called with _data can be different. * @param _wallet The target wallet. * @param _proxy The address to approves. * @param _amount The amount of tokens to transfer. * @param _contract The contract address. * @param _data The method data. */ function doApproveWethAndCallContract( address _wallet, address _proxy, uint256 _amount, address _contract, bytes memory _data ) internal { uint256 wethBalance = ERC20(wethToken).balanceOf(_wallet); if (wethBalance < _amount) { // Wrap ETH into WETH invokeWallet(_wallet, wethToken, _amount - wethBalance, abi.encodeWithSignature("deposit()")); } doApproveTokenAndCallContract(_wallet, wethToken, _proxy, _amount, _contract, _data); } } // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.5.4 <0.7.0; /** * @title IFeature * @notice Interface for a Feature. * @author Julien Niset - <[email protected]>, Olivier VDB - <[email protected]> */ interface IFeature { enum OwnerSignature { Anyone, // Anyone Required, // Owner required Optional, // Owner and/or guardians Disallowed // guardians only } /** * @notice Utility method to recover any ERC20 token that was sent to the Feature by mistake. * @param _token The token to recover. */ function recoverToken(address _token) external; /** * @notice Inits a Feature for a wallet by e.g. setting some wallet specific parameters in storage. * @param _wallet The wallet. */ function init(address _wallet) external; /** * @notice Helper method to check if an address is an authorised feature of a target wallet. * @param _wallet The target wallet. * @param _feature The address. */ function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) external view returns (bool); /** * @notice Gets the number of valid signatures that must be provided to execute a * specific relayed transaction. * @param _wallet The target wallet. * @param _data The data of the relayed transaction. * @return The number of required signatures and the wallet owner signature requirement. */ function getRequiredSignatures(address _wallet, bytes calldata _data) external view returns (uint256, OwnerSignature); /** * @notice Gets the list of static call signatures that this feature responds to on behalf of wallets */ function getStaticCallSignatures() external view returns (bytes4[] memory); }// Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.5.4 <0.7.0; pragma experimental ABIEncoderV2; import "../../infrastructure/storage/ILimitStorage.sol"; /** * @title IVersionManager * @notice Interface for the VersionManager module. * @author Olivier VDB - <[email protected]> */ interface IVersionManager { /** * @notice Returns true if the feature is authorised for the wallet * @param _wallet The target wallet. * @param _feature The feature. */ function isFeatureAuthorised(address _wallet, address _feature) external view returns (bool); /** * @notice Lets a feature (caller) invoke a wallet. * @param _wallet The target wallet. * @param _to The target address for the transaction. * @param _value The value of the transaction. * @param _data The data of the transaction. */ function checkAuthorisedFeatureAndInvokeWallet( address _wallet, address _to, uint256 _value, bytes calldata _data ) external returns (bytes memory _res); /* ******* Backward Compatibility with old Storages and BaseWallet *************** */ /** * @notice Sets a new owner for the wallet. * @param _newOwner The new owner. */ function setOwner(address _wallet, address _newOwner) external; /** * @notice Lets a feature write data to a storage contract. * @param _wallet The target wallet. * @param _storage The storage contract. * @param _data The data of the call */ function invokeStorage(address _wallet, address _storage, bytes calldata _data) external; /** * @notice Upgrade a wallet to a new version. * @param _wallet the wallet to upgrade * @param _toVersion the new version */ function upgradeWallet(address _wallet, uint256 _toVersion) external; }// Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "../../infrastructure/storage/ILimitStorage.sol"; import "../../infrastructure/ITokenPriceRegistry.sol"; import "./IVersionManager.sol"; /** * @title LimitUtils * @notice Helper library to manage the daily limit and interact with a contract implementing the ILimitStorage interface. * @author Julien Niset - <[email protected]> */ library LimitUtils { // large limit when the limit can be considered disabled uint128 constant internal LIMIT_DISABLED = uint128(-1); using SafeMath for uint256; // *************** Internal Functions ********************* // /** * @notice Changes the daily limit (expressed in ETH). * Decreasing the limit is immediate while increasing the limit is pending for the security period. * @param _lStorage The storage contract. * @param _versionManager The version manager. * @param _wallet The target wallet. * @param _targetLimit The target limit. * @param _securityPeriod The security period. */ function changeLimit( ILimitStorage _lStorage, IVersionManager _versionManager, address _wallet, uint256 _targetLimit, uint256 _securityPeriod ) internal returns (ILimitStorage.Limit memory) { ILimitStorage.Limit memory limit = _lStorage.getLimit(_wallet); uint256 currentLimit = currentLimit(limit); ILimitStorage.Limit memory newLimit; if (_targetLimit <= currentLimit) { uint128 targetLimit = safe128(_targetLimit); newLimit = ILimitStorage.Limit(targetLimit, targetLimit, safe64(block.timestamp)); } else { newLimit = ILimitStorage.Limit(safe128(currentLimit), safe128(_targetLimit), safe64(block.timestamp.add(_securityPeriod))); } setLimit(_versionManager, _lStorage, _wallet, newLimit); return newLimit; } /** * @notice Disable the daily limit. * The change is pending for the security period. * @param _lStorage The storage contract. * @param _versionManager The version manager. * @param _wallet The target wallet. * @param _securityPeriod The security period. */ function disableLimit( ILimitStorage _lStorage, IVersionManager _versionManager, address _wallet, uint256 _securityPeriod ) internal { changeLimit(_lStorage, _versionManager, _wallet, LIMIT_DISABLED, _securityPeriod); } /** * @notice Returns whether the daily limit is disabled for a wallet. * @param _wallet The target wallet. * @return _limitDisabled true if the daily limit is disabled, false otherwise. */ function isLimitDisabled(ILimitStorage _lStorage, address _wallet) internal view returns (bool) { ILimitStorage.Limit memory limit = _lStorage.getLimit(_wallet); uint256 currentLimit = currentLimit(limit); return (currentLimit == LIMIT_DISABLED); } /** * @notice Checks if a transfer is within the limit. If yes the daily spent is updated. * @param _lStorage The storage contract. * @param _versionManager The Version Manager. * @param _wallet The target wallet. * @param _amount The amount for the transfer * @return true if the transfer is withing the daily limit. */ function checkAndUpdateDailySpent( ILimitStorage _lStorage, IVersionManager _versionManager, address _wallet, uint256 _amount ) internal returns (bool) { (ILimitStorage.Limit memory limit, ILimitStorage.DailySpent memory dailySpent) = _lStorage.getLimitAndDailySpent(_wallet); uint256 currentLimit = currentLimit(limit); if (_amount == 0 || currentLimit == LIMIT_DISABLED) { return true; } ILimitStorage.DailySpent memory newDailySpent; if (dailySpent.periodEnd <= block.timestamp && _amount <= currentLimit) { newDailySpent = ILimitStorage.DailySpent(safe128(_amount), safe64(block.timestamp + 24 hours)); setDailySpent(_versionManager, _lStorage, _wallet, newDailySpent); return true; } else if (dailySpent.periodEnd > block.timestamp && _amount.add(dailySpent.alreadySpent) <= currentLimit) { newDailySpent = ILimitStorage.DailySpent(safe128(_amount.add(dailySpent.alreadySpent)), safe64(dailySpent.periodEnd)); setDailySpent(_versionManager, _lStorage, _wallet, newDailySpent); return true; } return false; } /** * @notice Helper method to Reset the daily consumption. * @param _versionManager The Version Manager. * @param _wallet The target wallet. */ function resetDailySpent(IVersionManager _versionManager, ILimitStorage limitStorage, address _wallet) internal { setDailySpent(_versionManager, limitStorage, _wallet, ILimitStorage.DailySpent(uint128(0), uint64(0))); } /** * @notice Helper method to get the ether value equivalent of a token amount. * @notice For low value amounts of tokens we accept this to return zero as these are small enough to disregard. * Note that the price stored for tokens = price for 1 token (in ETH wei) * 10^(18-token decimals). * @param _amount The token amount. * @param _token The address of the token. * @return The ether value for _amount of _token. */ function getEtherValue(ITokenPriceRegistry _priceRegistry, uint256 _amount, address _token) internal view returns (uint256) { uint256 price = _priceRegistry.getTokenPrice(_token); uint256 etherValue = price.mul(_amount).div(10**18); return etherValue; } /** * @notice Helper method to get the current limit from a Limit struct. * @param _limit The limit struct */ function currentLimit(ILimitStorage.Limit memory _limit) internal view returns (uint256) { if (_limit.changeAfter > 0 && _limit.changeAfter < block.timestamp) { return _limit.pending; } return _limit.current; } function safe128(uint256 _num) internal pure returns (uint128) { require(_num < 2**128, "LU: more then 128 bits"); return uint128(_num); } function safe64(uint256 _num) internal pure returns (uint64) { require(_num < 2**64, "LU: more then 64 bits"); return uint64(_num); } // *************** Storage invocations in VersionManager ********************* // function setLimit( IVersionManager _versionManager, ILimitStorage _lStorage, address _wallet, ILimitStorage.Limit memory _limit ) internal { _versionManager.invokeStorage( _wallet, address(_lStorage), abi.encodeWithSelector(_lStorage.setLimit.selector, _wallet, _limit) ); } function setDailySpent( IVersionManager _versionManager, ILimitStorage _lStorage, address _wallet, ILimitStorage.DailySpent memory _dailySpent ) private { _versionManager.invokeStorage( _wallet, address(_lStorage), abi.encodeWithSelector(_lStorage.setDailySpent.selector, _wallet, _dailySpent) ); } }// Copyright (C) 2020 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.6.12; /** * @title Utils * @notice Common utility methods used by modules. */ library Utils { /** * @notice Helper method to recover the signer at a given position from a list of concatenated signatures. * @param _signedHash The signed hash * @param _signatures The concatenated signatures. * @param _index The index of the signature to recover. */ function recoverSigner(bytes32 _signedHash, bytes memory _signatures, uint _index) internal pure returns (address) { uint8 v; bytes32 r; bytes32 s; // we jump 32 (0x20) as the first slot of bytes contains the length // we jump 65 (0x41) per signature // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask // solhint-disable-next-line no-inline-assembly assembly { r := mload(add(_signatures, add(0x20,mul(0x41,_index)))) s := mload(add(_signatures, add(0x40,mul(0x41,_index)))) v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff) } require(v == 27 || v == 28); address recoveredAddress = ecrecover(_signedHash, v, r, s); require(recoveredAddress != address(0), "Utils: ecrecover returned 0"); return recoveredAddress; } /** * @notice Helper method to parse data and extract the method signature. */ function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) { require(_data.length >= 4, "RM: Invalid functionPrefix"); // solhint-disable-next-line no-inline-assembly assembly { prefix := mload(add(_data, 0x20)) } } /** * @notice Returns ceil(a / b). */ function ceil(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a / b; if (a % b == 0) { return c; } else { return c + 1; } } function min(uint256 a, uint256 b) internal pure returns (uint256) { if (a < b) { return a; } return b; } } // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.5.4 <0.7.0; /** * @title IWallet * @notice Interface for the BaseWallet */ interface IWallet { /** * @notice Returns the wallet owner. * @return The wallet owner address. */ function owner() external view returns (address); /** * @notice Returns the number of authorised modules. * @return The number of authorised modules. */ function modules() external view returns (uint); /** * @notice Sets a new owner for the wallet. * @param _newOwner The new owner. */ function setOwner(address _newOwner) external; /** * @notice Checks if a module is authorised on the wallet. * @param _module The module address to check. * @return `true` if the module is authorised, otherwise `false`. */ function authorised(address _module) external view returns (bool); /** * @notice Returns the module responsible for a static call redirection. * @param _sig The signature of the static call. * @return the module doing the redirection */ function enabled(bytes4 _sig) external view returns (address); /** * @notice Enables/Disables a module. * @param _module The target module. * @param _value Set to `true` to authorise the module. */ function authoriseModule(address _module, bool _value) external; /** * @notice Enables a static method by specifying the target module to which the call must be delegated. * @param _module The target module. * @param _method The static method signature. */ function enableStaticCall(address _module, bytes4 _method) external; }pragma solidity >=0.5.4 <0.7.0; /** * ERC20 contract interface. */ interface ERC20 { function totalSupply() external view returns (uint); function decimals() external view returns (uint); function balanceOf(address tokenOwner) external view returns (uint balance); function allowance(address tokenOwner, address spender) external view returns (uint remaining); function transfer(address to, uint tokens) external returns (bool success); function approve(address spender, uint tokens) external returns (bool success); function transferFrom(address from, address to, uint tokens) external returns (bool success); }pragma solidity ^0.6.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } }
File 4 of 6: VersionManager
pragma experimental ABIEncoderV2; // File: contracts/modules/common/Utils.sol // Copyright (C) 2020 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only /** * @title Utils * @notice Common utility methods used by modules. */ library Utils { /** * @notice Helper method to recover the signer at a given position from a list of concatenated signatures. * @param _signedHash The signed hash * @param _signatures The concatenated signatures. * @param _index The index of the signature to recover. */ function recoverSigner(bytes32 _signedHash, bytes memory _signatures, uint _index) internal pure returns (address) { uint8 v; bytes32 r; bytes32 s; // we jump 32 (0x20) as the first slot of bytes contains the length // we jump 65 (0x41) per signature // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask // solhint-disable-next-line no-inline-assembly assembly { r := mload(add(_signatures, add(0x20,mul(0x41,_index)))) s := mload(add(_signatures, add(0x40,mul(0x41,_index)))) v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff) } require(v == 27 || v == 28); address recoveredAddress = ecrecover(_signedHash, v, r, s); require(recoveredAddress != address(0), "Utils: ecrecover returned 0"); return recoveredAddress; } /** * @notice Helper method to parse data and extract the method signature. */ function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) { require(_data.length >= 4, "RM: Invalid functionPrefix"); // solhint-disable-next-line no-inline-assembly assembly { prefix := mload(add(_data, 0x20)) } } /** * @notice Returns ceil(a / b). */ function ceil(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a / b; if (a % b == 0) { return c; } else { return c + 1; } } function min(uint256 a, uint256 b) internal pure returns (uint256) { if (a < b) { return a; } return b; } } // File: contracts/infrastructure/base/Owned.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title Owned * @notice Basic contract to define an owner. * @author Julien Niset - <[email protected]> */ contract Owned { // The owner address public owner; event OwnerChanged(address indexed _newOwner); /** * @notice Throws if the sender is not the owner. */ modifier onlyOwner { require(msg.sender == owner, "Must be owner"); _; } constructor() public { owner = msg.sender; } /** * @notice Lets the owner transfer ownership of the contract to a new owner. * @param _newOwner The new owner. */ function changeOwner(address _newOwner) external onlyOwner { require(_newOwner != address(0), "Address must not be null"); owner = _newOwner; emit OwnerChanged(_newOwner); } } // File: contracts/infrastructure/storage/ITransferStorage.sol // Copyright (C) 2020 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title ITransferStorage * @notice TransferStorage interface */ interface ITransferStorage { function setWhitelist(address _wallet, address _target, uint256 _value) external; function getWhitelist(address _wallet, address _target) external view returns (uint256); } // File: contracts/infrastructure/storage/IGuardianStorage.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; interface IGuardianStorage { /** * @notice Lets an authorised module add a guardian to a wallet. * @param _wallet The target wallet. * @param _guardian The guardian to add. */ function addGuardian(address _wallet, address _guardian) external; /** * @notice Lets an authorised module revoke a guardian from a wallet. * @param _wallet The target wallet. * @param _guardian The guardian to revoke. */ function revokeGuardian(address _wallet, address _guardian) external; /** * @notice Checks if an account is a guardian for a wallet. * @param _wallet The target wallet. * @param _guardian The account. * @return true if the account is a guardian for a wallet. */ function isGuardian(address _wallet, address _guardian) external view returns (bool); function isLocked(address _wallet) external view returns (bool); function getLock(address _wallet) external view returns (uint256); function getLocker(address _wallet) external view returns (address); function setLock(address _wallet, uint256 _releaseAfter) external; function getGuardians(address _wallet) external view returns (address[] memory); function guardianCount(address _wallet) external view returns (uint256); } // File: contracts/modules/common/IModule.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IModule * @notice Interface for a module. * A module MUST implement the addModule() method to ensure that a wallet with at least one module * can never end up in a "frozen" state. * @author Julien Niset - <[email protected]> */ interface IModule { /** * @notice Inits a module for a wallet by e.g. setting some wallet specific parameters in storage. * @param _wallet The wallet. */ function init(address _wallet) external; /** * @notice Adds a module to a wallet. Cannot execute when wallet is locked (or under recovery) * @param _wallet The target wallet. * @param _module The modules to authorise. */ function addModule(address _wallet, address _module) external; } // File: @openzeppelin/contracts/math/SafeMath.sol /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } // File: contracts/wallet/IWallet.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IWallet * @notice Interface for the BaseWallet */ interface IWallet { /** * @notice Returns the wallet owner. * @return The wallet owner address. */ function owner() external view returns (address); /** * @notice Returns the number of authorised modules. * @return The number of authorised modules. */ function modules() external view returns (uint); /** * @notice Sets a new owner for the wallet. * @param _newOwner The new owner. */ function setOwner(address _newOwner) external; /** * @notice Checks if a module is authorised on the wallet. * @param _module The module address to check. * @return `true` if the module is authorised, otherwise `false`. */ function authorised(address _module) external view returns (bool); /** * @notice Returns the module responsible for a static call redirection. * @param _sig The signature of the static call. * @return the module doing the redirection */ function enabled(bytes4 _sig) external view returns (address); /** * @notice Enables/Disables a module. * @param _module The target module. * @param _value Set to `true` to authorise the module. */ function authoriseModule(address _module, bool _value) external; /** * @notice Enables a static method by specifying the target module to which the call must be delegated. * @param _module The target module. * @param _method The static method signature. */ function enableStaticCall(address _module, bytes4 _method) external; } // File: contracts/infrastructure/IModuleRegistry.sol // Copyright (C) 2020 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IModuleRegistry * @notice Interface for the registry of authorised modules. */ interface IModuleRegistry { function registerModule(address _module, bytes32 _name) external; function deregisterModule(address _module) external; function registerUpgrader(address _upgrader, bytes32 _name) external; function deregisterUpgrader(address _upgrader) external; function recoverToken(address _token) external; function moduleInfo(address _module) external view returns (bytes32); function upgraderInfo(address _upgrader) external view returns (bytes32); function isRegisteredModule(address _module) external view returns (bool); function isRegisteredModule(address[] calldata _modules) external view returns (bool); function isRegisteredUpgrader(address _upgrader) external view returns (bool); } // File: contracts/infrastructure/storage/ILockStorage.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; interface ILockStorage { function isLocked(address _wallet) external view returns (bool); function getLock(address _wallet) external view returns (uint256); function getLocker(address _wallet) external view returns (address); function setLock(address _wallet, address _locker, uint256 _releaseAfter) external; } // File: contracts/modules/common/IFeature.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IFeature * @notice Interface for a Feature. * @author Julien Niset - <[email protected]>, Olivier VDB - <[email protected]> */ interface IFeature { enum OwnerSignature { Anyone, // Anyone Required, // Owner required Optional, // Owner and/or guardians Disallowed // guardians only } /** * @notice Utility method to recover any ERC20 token that was sent to the Feature by mistake. * @param _token The token to recover. */ function recoverToken(address _token) external; /** * @notice Inits a Feature for a wallet by e.g. setting some wallet specific parameters in storage. * @param _wallet The wallet. */ function init(address _wallet) external; /** * @notice Helper method to check if an address is an authorised feature of a target wallet. * @param _wallet The target wallet. * @param _feature The address. */ function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) external view returns (bool); /** * @notice Gets the number of valid signatures that must be provided to execute a * specific relayed transaction. * @param _wallet The target wallet. * @param _data The data of the relayed transaction. * @return The number of required signatures and the wallet owner signature requirement. */ function getRequiredSignatures(address _wallet, bytes calldata _data) external view returns (uint256, OwnerSignature); /** * @notice Gets the list of static call signatures that this feature responds to on behalf of wallets */ function getStaticCallSignatures() external view returns (bytes4[] memory); } // File: lib/other/ERC20.sol pragma solidity >=0.5.4 <0.7.0; /** * ERC20 contract interface. */ interface ERC20 { function totalSupply() external view returns (uint); function decimals() external view returns (uint); function balanceOf(address tokenOwner) external view returns (uint balance); function allowance(address tokenOwner, address spender) external view returns (uint remaining); function transfer(address to, uint tokens) external returns (bool success); function approve(address spender, uint tokens) external returns (bool success); function transferFrom(address from, address to, uint tokens) external returns (bool success); } // File: contracts/infrastructure/storage/ILimitStorage.sol // 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 <http://www.gnu.org/licenses/>. /** * @title ILimitStorage * @notice LimitStorage interface */ interface ILimitStorage { struct Limit { // the current limit uint128 current; // the pending limit if any uint128 pending; // when the pending limit becomes the current limit uint64 changeAfter; } struct DailySpent { // The amount already spent during the current period uint128 alreadySpent; // The end of the current period uint64 periodEnd; } function setLimit(address _wallet, Limit memory _limit) external; function getLimit(address _wallet) external view returns (Limit memory _limit); function setDailySpent(address _wallet, DailySpent memory _dailySpent) external; function getDailySpent(address _wallet) external view returns (DailySpent memory _dailySpent); function setLimitAndDailySpent(address _wallet, Limit memory _limit, DailySpent memory _dailySpent) external; function getLimitAndDailySpent(address _wallet) external view returns (Limit memory _limit, DailySpent memory _dailySpent); } // File: contracts/modules/common/IVersionManager.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. pragma solidity >=0.5.4 <0.7.0; /** * @title IVersionManager * @notice Interface for the VersionManager module. * @author Olivier VDB - <[email protected]> */ interface IVersionManager { /** * @notice Returns true if the feature is authorised for the wallet * @param _wallet The target wallet. * @param _feature The feature. */ function isFeatureAuthorised(address _wallet, address _feature) external view returns (bool); /** * @notice Lets a feature (caller) invoke a wallet. * @param _wallet The target wallet. * @param _to The target address for the transaction. * @param _value The value of the transaction. * @param _data The data of the transaction. */ function checkAuthorisedFeatureAndInvokeWallet( address _wallet, address _to, uint256 _value, bytes calldata _data ) external returns (bytes memory _res); /* ******* Backward Compatibility with old Storages and BaseWallet *************** */ /** * @notice Sets a new owner for the wallet. * @param _newOwner The new owner. */ function setOwner(address _wallet, address _newOwner) external; /** * @notice Lets a feature write data to a storage contract. * @param _wallet The target wallet. * @param _storage The storage contract. * @param _data The data of the call */ function invokeStorage(address _wallet, address _storage, bytes calldata _data) external; /** * @notice Upgrade a wallet to a new version. * @param _wallet the wallet to upgrade * @param _toVersion the new version */ function upgradeWallet(address _wallet, uint256 _toVersion) external; } // File: contracts/modules/common/BaseFeature.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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.s // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /** * @title BaseFeature * @notice Base Feature contract that contains methods common to all Feature contracts. * @author Julien Niset - <[email protected]>, Olivier VDB - <[email protected]> */ contract BaseFeature is IFeature { // Empty calldata bytes constant internal EMPTY_BYTES = ""; // Mock token address for ETH address constant internal ETH_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // The address of the Lock storage ILockStorage internal lockStorage; // The address of the Version Manager IVersionManager internal versionManager; event FeatureCreated(bytes32 name); /** * @notice Throws if the wallet is locked. */ modifier onlyWhenUnlocked(address _wallet) { require(!lockStorage.isLocked(_wallet), "BF: wallet locked"); _; } /** * @notice Throws if the sender is not the VersionManager. */ modifier onlyVersionManager() { require(msg.sender == address(versionManager), "BF: caller must be VersionManager"); _; } /** * @notice Throws if the sender is not the owner of the target wallet. */ modifier onlyWalletOwner(address _wallet) { require(isOwner(_wallet, msg.sender), "BF: must be wallet owner"); _; } /** * @notice Throws if the sender is not an authorised feature of the target wallet. */ modifier onlyWalletFeature(address _wallet) { require(versionManager.isFeatureAuthorised(_wallet, msg.sender), "BF: must be a wallet feature"); _; } /** * @notice Throws if the sender is not the owner of the target wallet or the feature itself. */ modifier onlyWalletOwnerOrFeature(address _wallet) { // Wrapping in an internal method reduces deployment cost by avoiding duplication of inlined code verifyOwnerOrAuthorisedFeature(_wallet, msg.sender); _; } constructor( ILockStorage _lockStorage, IVersionManager _versionManager, bytes32 _name ) public { lockStorage = _lockStorage; versionManager = _versionManager; emit FeatureCreated(_name); } /** * @inheritdoc IFeature */ function recoverToken(address _token) external virtual override { uint total = ERC20(_token).balanceOf(address(this)); _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector, address(versionManager), total)); } /** * @notice Inits the feature for a wallet by doing nothing. * @dev !! Overriding methods need make sure `init()` can only be called by the VersionManager !! * @param _wallet The wallet. */ function init(address _wallet) external virtual override {} /** * @inheritdoc IFeature */ function getRequiredSignatures(address, bytes calldata) external virtual view override returns (uint256, OwnerSignature) { revert("BF: disabled method"); } /** * @inheritdoc IFeature */ function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) {} /** * @inheritdoc IFeature */ function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) public override view returns (bool) { return versionManager.isFeatureAuthorised(_wallet, _feature); } /** * @notice Checks that the wallet address provided as the first parameter of _data matches _wallet * @return false if the addresses are different. */ function verifyData(address _wallet, bytes calldata _data) internal pure returns (bool) { require(_data.length >= 36, "RM: Invalid dataWallet"); address dataWallet = abi.decode(_data[4:], (address)); return dataWallet == _wallet; } /** * @notice Helper method to check if an address is the owner of a target wallet. * @param _wallet The target wallet. * @param _addr The address. */ function isOwner(address _wallet, address _addr) internal view returns (bool) { return IWallet(_wallet).owner() == _addr; } /** * @notice Verify that the caller is an authorised feature or the wallet owner. * @param _wallet The target wallet. * @param _sender The caller. */ function verifyOwnerOrAuthorisedFeature(address _wallet, address _sender) internal view { require(isFeatureAuthorisedInVersionManager(_wallet, _sender) || isOwner(_wallet, _sender), "BF: must be owner or feature"); } /** * @notice Helper method to invoke a wallet. * @param _wallet The target wallet. * @param _to The target address for the transaction. * @param _value The value of the transaction. * @param _data The data of the transaction. */ function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data) internal returns (bytes memory _res) { _res = versionManager.checkAuthorisedFeatureAndInvokeWallet(_wallet, _to, _value, _data); } } // File: modules/VersionManager.sol // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. /** * @title VersionManager * @notice Intermediate contract between features and wallets. VersionManager checks that a calling feature is * authorised for the wallet and if so, forwards the call to it. Note that VersionManager is meant to be the only * module authorised on a wallet and because some of its methods need to be called by the RelayerManager feature, * the VersionManager is both a module AND a feature. * @author Olivier VDB <[email protected]> */ contract VersionManager is IVersionManager, IModule, BaseFeature, Owned { bytes32 constant NAME = "VersionManager"; bytes4 constant internal ADD_MODULE_PREFIX = bytes4(keccak256("addModule(address,address)")); bytes4 constant internal UPGRADE_WALLET_PREFIX = bytes4(keccak256("upgradeWallet(address,uint256)")); // Last bundle version uint256 public lastVersion; // Minimum allowed version uint256 public minVersion = 1; // Current bundle version for a wallet mapping(address => uint256) public walletVersions; // [wallet] => [version] // Features per version mapping(address => mapping(uint256 => bool)) public isFeatureInVersion; // [feature][version] => bool // Features requiring initialization for a wallet mapping(uint256 => address[]) public featuresToInit; // [version] => [features] // Supported static call signatures mapping(uint256 => bytes4[]) public staticCallSignatures; // [version] => [sigs] // Features executing static calls mapping(uint256 => mapping(bytes4 => address)) public staticCallExecutors; // [version][sig] => [feature] // Authorised Storages mapping(address => bool) public isStorage; // [storage] => bool event VersionAdded(uint256 _version, address[] _features); event WalletUpgraded(address indexed _wallet, uint256 _version); // The Module Registry IModuleRegistry private registry; /* ***************** Constructor ************************* */ constructor( IModuleRegistry _registry, ILockStorage _lockStorage, IGuardianStorage _guardianStorage, ITransferStorage _transferStorage, ILimitStorage _limitStorage ) BaseFeature(_lockStorage, IVersionManager(address(this)), NAME) public { registry = _registry; // Add initial storages if(address(_lockStorage) != address(0)) { addStorage(address(_lockStorage)); } if(address(_guardianStorage) != address(0)) { addStorage(address(_guardianStorage)); } if(address(_transferStorage) != address(0)) { addStorage(address(_transferStorage)); } if(address(_limitStorage) != address(0)) { addStorage(address(_limitStorage)); } } /* ***************** onlyOwner ************************* */ /** * @inheritdoc IFeature */ function recoverToken(address _token) external override onlyOwner { uint total = ERC20(_token).balanceOf(address(this)); _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector, msg.sender, total)); } /** * @notice Lets the owner change the minimum allowed version * @param _minVersion the minimum allowed version */ function setMinVersion(uint256 _minVersion) external onlyOwner { require(_minVersion > 0 && _minVersion <= lastVersion, "VM: invalid _minVersion"); minVersion = _minVersion; } /** * @notice Lets the owner add a new version, i.e. a new bundle of features. * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * WARNING: if a feature was added to a version and later on removed from a subsequent version, * the feature may no longer be used in any future version without first being redeployed. * Otherwise, the feature could be initialized more than once. * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * @param _features the list of features included in the new version * @param _featuresToInit the subset of features that need to be initialized for a wallet */ function addVersion(address[] calldata _features, address[] calldata _featuresToInit) external onlyOwner { uint256 newVersion = ++lastVersion; for(uint256 i = 0; i < _features.length; i++) { isFeatureInVersion[_features[i]][newVersion] = true; // Store static call information to optimise its use by wallets bytes4[] memory sigs = IFeature(_features[i]).getStaticCallSignatures(); for(uint256 j = 0; j < sigs.length; j++) { staticCallSignatures[newVersion].push(sigs[j]); staticCallExecutors[newVersion][sigs[j]] = _features[i]; } } // Sanity check for(uint256 i = 0; i < _featuresToInit.length; i++) { require(isFeatureInVersion[_featuresToInit[i]][newVersion], "VM: invalid _featuresToInit"); } featuresToInit[newVersion] = _featuresToInit; emit VersionAdded(newVersion, _features); } /** * @notice Lets the owner add a storage contract * @param _storage the storage contract to add */ function addStorage(address _storage) public onlyOwner { require(!isStorage[_storage], "VM: storage already added"); isStorage[_storage] = true; } /* ***************** View Methods ************************* */ /** * @inheritdoc IVersionManager */ function isFeatureAuthorised(address _wallet, address _feature) external view override returns (bool) { // Note that the VersionManager is the only feature that isn't stored in isFeatureInVersion return _isFeatureAuthorisedForWallet(_wallet, _feature) || _feature == address(this); } /** * @inheritdoc IFeature */ function getRequiredSignatures(address /* _wallet */, bytes calldata _data) external view override returns (uint256, OwnerSignature) { bytes4 methodId = Utils.functionPrefix(_data); // This require ensures that the RelayerManager cannot be used to call a featureOnly VersionManager method // that calls a Storage or the BaseWallet for backward-compatibility reason require(methodId == UPGRADE_WALLET_PREFIX || methodId == ADD_MODULE_PREFIX, "VM: unknown method"); return (1, OwnerSignature.Required); } /* ***************** Static Call Delegation ************************* */ /** * @notice This method is used by the VersionManager's fallback (via an internal call) to determine whether * the current transaction is a staticcall or not. The method succeeds if the current transaction is a static call, * and reverts otherwise. * @dev The use of an if/else allows to encapsulate the whole logic in a single function. */ function verifyStaticCall() public { if(msg.sender != address(this)) { // first entry in the method (via an internal call) (bool success,) = address(this).call{gas: 3000}(abi.encodeWithSelector(VersionManager(0).verifyStaticCall.selector)); require(!success, "VM: not in a staticcall"); } else { // second entry in the method (via an external call) // solhint-disable-next-line no-inline-assembly assembly { log0(0, 0) } } } /** * @notice This method delegates the static call to a target feature */ fallback() external { uint256 version = walletVersions[msg.sender]; address feature = staticCallExecutors[version][msg.sig]; require(feature != address(0), "VM: static call not supported for wallet version"); verifyStaticCall(); // solhint-disable-next-line no-inline-assembly assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), feature, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 {revert(0, returndatasize())} default {return (0, returndatasize())} } } /* ***************** Wallet Upgrade ************************* */ /** * @inheritdoc IFeature */ function init(address _wallet) public override(IModule, BaseFeature) {} /** * @inheritdoc IVersionManager */ function upgradeWallet(address _wallet, uint256 _toVersion) external override onlyWhenUnlocked(_wallet) { require( // Upgrade triggered by the RelayerManager (from version v>=1 to version v'>v) _isFeatureAuthorisedForWallet(_wallet, msg.sender) || // Upgrade triggered by WalletFactory or UpgraderToVersionManager (from version v=0 to version v'>0) IWallet(_wallet).authorised(msg.sender) || // Upgrade triggered directly by the owner (from version v>=1 to version v'>v) isOwner(_wallet, msg.sender), "VM: sender may not upgrade wallet" ); uint256 fromVersion = walletVersions[_wallet]; uint256 minVersion_ = minVersion; uint256 toVersion; if(_toVersion < minVersion_ && fromVersion == 0 && IWallet(_wallet).modules() == 2) { // When the caller is the WalletFactory, we automatically change toVersion to minVersion if needed. // Note that when fromVersion == 0, the caller could be the WalletFactory or the UpgraderToVersionManager. // The WalletFactory will be the only possible caller when the wallet has only 2 authorised modules // (that number would be >= 3 for a call from the UpgraderToVersionManager) toVersion = minVersion_; } else { toVersion = _toVersion; } require(toVersion >= minVersion_ && toVersion <= lastVersion, "VM: invalid _toVersion"); require(fromVersion < toVersion, "VM: already on new version"); walletVersions[_wallet] = toVersion; // Setup static call redirection bytes4[] storage sigs = staticCallSignatures[toVersion]; for(uint256 i = 0; i < sigs.length; i++) { bytes4 sig = sigs[i]; if(IWallet(_wallet).enabled(sig) != address(this)) { IWallet(_wallet).enableStaticCall(address(this), sig); } } // Init features address[] storage featuresToInitInToVersion = featuresToInit[toVersion]; for(uint256 i = 0; i < featuresToInitInToVersion.length; i++) { address feature = featuresToInitInToVersion[i]; // We only initialize a feature that was not already initialized in the previous version if(fromVersion == 0 || !isFeatureInVersion[feature][fromVersion]) { IFeature(feature).init(_wallet); } } emit WalletUpgraded(_wallet, toVersion); } /** * @inheritdoc IModule */ function addModule(address _wallet, address _module) external override onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { require(registry.isRegisteredModule(_module), "VM: module is not registered"); IWallet(_wallet).authoriseModule(_module, true); } /* ******* Backward Compatibility with old Storages and BaseWallet *************** */ /** * @inheritdoc IVersionManager */ function checkAuthorisedFeatureAndInvokeWallet( address _wallet, address _to, uint256 _value, bytes memory _data ) external override returns (bytes memory _res) { require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender may not invoke wallet"); bool success; (success, _res) = _wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data)); if (success && _res.length > 0) { //_res is empty if _wallet is an "old" BaseWallet that can't return output values (_res) = abi.decode(_res, (bytes)); } else if (_res.length > 0) { // solhint-disable-next-line no-inline-assembly assembly { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } else if (!success) { revert("VM: wallet invoke reverted"); } } /** * @inheritdoc IVersionManager */ function invokeStorage(address _wallet, address _storage, bytes calldata _data) external override { require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender may not invoke storage"); require(verifyData(_wallet, _data), "VM: target of _data != _wallet"); require(isStorage[_storage], "VM: invalid storage invoked"); (bool success,) = _storage.call(_data); require(success, "VM: _storage failed"); } /** * @inheritdoc IVersionManager */ function setOwner(address _wallet, address _newOwner) external override { require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender should be authorized feature"); IWallet(_wallet).setOwner(_newOwner); } /* ***************** Internal Methods ************************* */ function _isFeatureAuthorisedForWallet(address _wallet, address _feature) private view returns (bool) { return isFeatureInVersion[_feature][walletVersions[_wallet]]; } }
File 5 of 6: BaseWallet
pragma solidity ^0.6.12; // Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz> // 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 <http://www.gnu.org/licenses/>. // SPDX-License-Identifier: GPL-3.0-only /** * @title IModule * @notice Interface for a module. * A module MUST implement the addModule() method to ensure that a wallet with at least one module * can never end up in a "frozen" state. * @author Julien Niset - <[email protected]> */ interface IModule { /** * @notice Inits a module for a wallet by e.g. setting some wallet specific parameters in storage. * @param _wallet The wallet. */ function init(address _wallet) external; /** * @notice Adds a module to a wallet. Cannot execute when wallet is locked (or under recovery) * @param _wallet The target wallet. * @param _module The modules to authorise. */ function addModule(address _wallet, address _module) external; } /** * @title IWallet * @notice Interface for the BaseWallet */ interface IWallet { /** * @notice Returns the wallet owner. * @return The wallet owner address. */ function owner() external view returns (address); /** * @notice Returns the number of authorised modules. * @return The number of authorised modules. */ function modules() external view returns (uint); /** * @notice Sets a new owner for the wallet. * @param _newOwner The new owner. */ function setOwner(address _newOwner) external; /** * @notice Checks if a module is authorised on the wallet. * @param _module The module address to check. * @return `true` if the module is authorised, otherwise `false`. */ function authorised(address _module) external view returns (bool); /** * @notice Returns the module responsible for a static call redirection. * @param _sig The signature of the static call. * @return the module doing the redirection */ function enabled(bytes4 _sig) external view returns (address); /** * @notice Enables/Disables a module. * @param _module The target module. * @param _value Set to `true` to authorise the module. */ function authoriseModule(address _module, bool _value) external; /** * @notice Enables a static method by specifying the target module to which the call must be delegated. * @param _module The target module. * @param _method The static method signature. */ function enableStaticCall(address _module, bytes4 _method) external; } /** * @title BaseWallet * @notice Simple modular wallet that authorises modules to call its invoke() method. * @author Julien Niset - <[email protected]> */ contract BaseWallet is IWallet { // The implementation of the proxy address public implementation; // The owner address public override owner; // The authorised modules mapping (address => bool) public override authorised; // The enabled static calls mapping (bytes4 => address) public override enabled; // The number of modules uint public override modules; event AuthorisedModule(address indexed module, bool value); event EnabledStaticCall(address indexed module, bytes4 indexed method); event Invoked(address indexed module, address indexed target, uint indexed value, bytes data); event Received(uint indexed value, address indexed sender, bytes data); event OwnerChanged(address owner); /** * @notice Throws if the sender is not an authorised module. */ modifier moduleOnly { require(authorised[msg.sender], "BW: msg.sender not an authorized module"); _; } /** * @notice Inits the wallet by setting the owner and authorising a list of modules. * @param _owner The owner. * @param _modules The modules to authorise. */ function init(address _owner, address[] calldata _modules) external { require(owner == address(0) && modules == 0, "BW: wallet already initialised"); require(_modules.length > 0, "BW: construction requires at least 1 module"); owner = _owner; modules = _modules.length; for (uint256 i = 0; i < _modules.length; i++) { require(authorised[_modules[i]] == false, "BW: module is already added"); authorised[_modules[i]] = true; IModule(_modules[i]).init(address(this)); emit AuthorisedModule(_modules[i], true); } if (address(this).balance > 0) { emit Received(address(this).balance, address(0), ""); } } /** * @inheritdoc IWallet */ function authoriseModule(address _module, bool _value) external override moduleOnly { if (authorised[_module] != _value) { emit AuthorisedModule(_module, _value); if (_value == true) { modules += 1; authorised[_module] = true; IModule(_module).init(address(this)); } else { modules -= 1; require(modules > 0, "BW: wallet must have at least one module"); delete authorised[_module]; } } } /** * @inheritdoc IWallet */ function enableStaticCall(address _module, bytes4 _method) external override moduleOnly { require(authorised[_module], "BW: must be an authorised module for static call"); enabled[_method] = _module; emit EnabledStaticCall(_module, _method); } /** * @inheritdoc IWallet */ function setOwner(address _newOwner) external override moduleOnly { require(_newOwner != address(0), "BW: address cannot be null"); owner = _newOwner; emit OwnerChanged(_newOwner); } /** * @notice Performs a generic transaction. * @param _target The address for the transaction. * @param _value The value of the transaction. * @param _data The data of the transaction. */ function invoke(address _target, uint _value, bytes calldata _data) external moduleOnly returns (bytes memory _result) { bool success; (success, _result) = _target.call{value: _value}(_data); if (!success) { // solhint-disable-next-line no-inline-assembly assembly { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } } emit Invoked(msg.sender, _target, _value, _data); } /** * @notice This method delegates the static call to a target contract if the data corresponds * to an enabled module, or logs the call otherwise. */ fallback() external payable { address module = enabled[msg.sig]; if (module == address(0)) { emit Received(msg.value, msg.sender, msg.data); } else { require(authorised[module], "BW: must be an authorised module for static call"); // solhint-disable-next-line no-inline-assembly assembly { calldatacopy(0, 0, calldatasize()) let result := staticcall(gas(), module, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 {revert(0, returndatasize())} default {return (0, returndatasize())} } } } receive() external payable { } }
File 6 of 6: LockStorage
{"BaseWallet.sol":{"content":"// Copyright (C) 2018 Argent Labs Ltd. \u003chttps://argent.xyz\u003e\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\n// SPDX-License-Identifier: GPL-3.0-only\npragma solidity ^0.6.12;\n\nimport \"./IModule.sol\";\nimport \"./IWallet.sol\";\n\n/**\n * @title BaseWallet\n * @notice Simple modular wallet that authorises modules to call its invoke() method.\n * @author Julien Niset - \[email protected]\u003e\n */\ncontract BaseWallet is IWallet {\n\n // The implementation of the proxy\n address public implementation;\n // The owner\n address public override owner;\n // The authorised modules\n mapping (address =\u003e bool) public override authorised;\n // The enabled static calls\n mapping (bytes4 =\u003e address) public override enabled;\n // The number of modules\n uint public override modules;\n\n event AuthorisedModule(address indexed module, bool value);\n event EnabledStaticCall(address indexed module, bytes4 indexed method);\n event Invoked(address indexed module, address indexed target, uint indexed value, bytes data);\n event Received(uint indexed value, address indexed sender, bytes data);\n event OwnerChanged(address owner);\n\n /**\n * @notice Throws if the sender is not an authorised module.\n */\n modifier moduleOnly {\n require(authorised[msg.sender], \"BW: msg.sender not an authorized module\");\n _;\n }\n\n /**\n * @notice Inits the wallet by setting the owner and authorising a list of modules.\n * @param _owner The owner.\n * @param _modules The modules to authorise.\n */\n function init(address _owner, address[] calldata _modules) external {\n require(owner == address(0) \u0026\u0026 modules == 0, \"BW: wallet already initialised\");\n require(_modules.length \u003e 0, \"BW: construction requires at least 1 module\");\n owner = _owner;\n modules = _modules.length;\n for (uint256 i = 0; i \u003c _modules.length; i++) {\n require(authorised[_modules[i]] == false, \"BW: module is already added\");\n authorised[_modules[i]] = true;\n IModule(_modules[i]).init(address(this));\n emit AuthorisedModule(_modules[i], true);\n }\n if (address(this).balance \u003e 0) {\n emit Received(address(this).balance, address(0), \"\");\n }\n }\n\n /**\n * @inheritdoc IWallet\n */\n function authoriseModule(address _module, bool _value) external override moduleOnly {\n if (authorised[_module] != _value) {\n emit AuthorisedModule(_module, _value);\n if (_value == true) {\n modules += 1;\n authorised[_module] = true;\n IModule(_module).init(address(this));\n } else {\n modules -= 1;\n require(modules \u003e 0, \"BW: wallet must have at least one module\");\n delete authorised[_module];\n }\n }\n }\n\n /**\n * @inheritdoc IWallet\n */\n function enableStaticCall(address _module, bytes4 _method) external override moduleOnly {\n require(authorised[_module], \"BW: must be an authorised module for static call\");\n enabled[_method] = _module;\n emit EnabledStaticCall(_module, _method);\n }\n\n /**\n * @inheritdoc IWallet\n */\n function setOwner(address _newOwner) external override moduleOnly {\n require(_newOwner != address(0), \"BW: address cannot be null\");\n owner = _newOwner;\n emit OwnerChanged(_newOwner);\n }\n\n /**\n * @notice Performs a generic transaction.\n * @param _target The address for the transaction.\n * @param _value The value of the transaction.\n * @param _data The data of the transaction.\n */\n function invoke(address _target, uint _value, bytes calldata _data) external moduleOnly returns (bytes memory _result) {\n bool success;\n (success, _result) = _target.call{value: _value}(_data);\n if (!success) {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n returndatacopy(0, 0, returndatasize())\n revert(0, returndatasize())\n }\n }\n emit Invoked(msg.sender, _target, _value, _data);\n }\n\n /**\n * @notice This method delegates the static call to a target contract if the data corresponds\n * to an enabled module, or logs the call otherwise.\n */\n fallback() external payable {\n address module = enabled[msg.sig];\n if (module == address(0)) {\n emit Received(msg.value, msg.sender, msg.data);\n } else {\n require(authorised[module], \"BW: must be an authorised module for static call\");\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n calldatacopy(0, 0, calldatasize())\n let result := staticcall(gas(), module, 0, calldatasize(), 0, 0)\n returndatacopy(0, 0, returndatasize())\n switch result\n case 0 {revert(0, returndatasize())}\n default {return (0, returndatasize())}\n }\n }\n }\n\n receive() external payable {\n }\n}"},"ILockStorage.sol":{"content":"// Copyright (C) 2018 Argent Labs Ltd. \u003chttps://argent.xyz\u003e\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\n// SPDX-License-Identifier: GPL-3.0-only\npragma solidity \u003e=0.5.4 \u003c0.7.0;\n\ninterface ILockStorage {\n function isLocked(address _wallet) external view returns (bool);\n\n function getLock(address _wallet) external view returns (uint256);\n\n function getLocker(address _wallet) external view returns (address);\n\n function setLock(address _wallet, address _locker, uint256 _releaseAfter) external;\n}"},"IModule.sol":{"content":"// Copyright (C) 2018 Argent Labs Ltd. \u003chttps://argent.xyz\u003e\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\n// SPDX-License-Identifier: GPL-3.0-only\npragma solidity \u003e=0.5.4 \u003c0.7.0;\n\n/**\n * @title IModule\n * @notice Interface for a module.\n * A module MUST implement the addModule() method to ensure that a wallet with at least one module\n * can never end up in a \"frozen\" state.\n * @author Julien Niset - \[email protected]\u003e\n */\ninterface IModule {\n /**\n * @notice Inits a module for a wallet by e.g. setting some wallet specific parameters in storage.\n * @param _wallet The wallet.\n */\n function init(address _wallet) external;\n\n /**\t\n * @notice Adds a module to a wallet. Cannot execute when wallet is locked (or under recovery)\t\n * @param _wallet The target wallet.\t\n * @param _module The modules to authorise.\t\n */\t\n function addModule(address _wallet, address _module) external;\n}"},"IWallet.sol":{"content":"// Copyright (C) 2018 Argent Labs Ltd. \u003chttps://argent.xyz\u003e\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\n// SPDX-License-Identifier: GPL-3.0-only\npragma solidity \u003e=0.5.4 \u003c0.7.0;\n\n/**\n * @title IWallet\n * @notice Interface for the BaseWallet\n */\ninterface IWallet {\n /**\n * @notice Returns the wallet owner.\n * @return The wallet owner address.\n */\n function owner() external view returns (address);\n\n /**\n * @notice Returns the number of authorised modules.\n * @return The number of authorised modules.\n */\n function modules() external view returns (uint);\n\n /**\n * @notice Sets a new owner for the wallet.\n * @param _newOwner The new owner.\n */\n function setOwner(address _newOwner) external;\n\n /**\n * @notice Checks if a module is authorised on the wallet.\n * @param _module The module address to check.\n * @return `true` if the module is authorised, otherwise `false`.\n */\n function authorised(address _module) external view returns (bool);\n\n /**\n * @notice Returns the module responsible for a static call redirection.\n * @param _sig The signature of the static call.\n * @return the module doing the redirection\n */\n function enabled(bytes4 _sig) external view returns (address);\n\n /**\n * @notice Enables/Disables a module.\n * @param _module The target module.\n * @param _value Set to `true` to authorise the module.\n */\n function authoriseModule(address _module, bool _value) external;\n\n /**\n * @notice Enables a static method by specifying the target module to which the call must be delegated.\n * @param _module The target module.\n * @param _method The static method signature.\n */\n function enableStaticCall(address _module, bytes4 _method) external;\n}"},"LockStorage.sol":{"content":"// Copyright (C) 2018 Argent Labs Ltd. \u003chttps://argent.xyz\u003e\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\npragma solidity ^0.6.12;\nimport \"./BaseWallet.sol\";\nimport \"./Storage.sol\";\nimport \"./ILockStorage.sol\";\n\n/**\n * @title LockStorage\n * @dev Contract storing the state of wallets related to guardians and lock.\n * The contract only defines basic setters and getters with no logic. Only modules authorised\n * for a wallet can modify its state.\n * @author Julien Niset - \[email protected]\u003e\n * @author Olivier Van Den Biggelaar - \[email protected]\u003e\n */\ncontract LockStorage is ILockStorage, Storage {\n\n struct LockStorageConfig {\n // the lock\u0027s release timestamp\n uint256 lock;\n // the module that set the last lock\n address locker;\n }\n \n // wallet specific storage\n mapping (address =\u003e LockStorageConfig) internal configs;\n\n // *************** External Functions ********************* //\n\n /**\n * @dev Lets an authorised module set the lock for a wallet.\n * @param _wallet The target wallet.\n * @param _locker The feature doing the lock.\n * @param _releaseAfter The epoch time at which the lock should automatically release.\n */\n function setLock(address _wallet, address _locker, uint256 _releaseAfter) external override onlyModule(_wallet) {\n configs[_wallet].lock = _releaseAfter;\n if (_releaseAfter != 0 \u0026\u0026 _locker != configs[_wallet].locker) {\n configs[_wallet].locker = _locker;\n }\n }\n\n /**\n * @dev Checks if the lock is set for a wallet.\n * @param _wallet The target wallet.\n * @return true if the lock is set for the wallet.\n */\n function isLocked(address _wallet) external view override returns (bool) {\n return configs[_wallet].lock \u003e now;\n }\n\n /**\n * @dev Gets the time at which the lock of a wallet will release.\n * @param _wallet The target wallet.\n * @return the time at which the lock of a wallet will release, or zero if there is no lock set.\n */\n function getLock(address _wallet) external view override returns (uint256) {\n return configs[_wallet].lock;\n }\n\n /**\n * @dev Gets the address of the last module that modified the lock for a wallet.\n * @param _wallet The target wallet.\n * @return the address of the last module that modified the lock for a wallet.\n */\n function getLocker(address _wallet) external view override returns (address) {\n return configs[_wallet].locker;\n }\n}"},"Storage.sol":{"content":"// Copyright (C) 2018 Argent Labs Ltd. \u003chttps://argent.xyz\u003e\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see \u003chttp://www.gnu.org/licenses/\u003e.\n\n// SPDX-License-Identifier: GPL-3.0-only\npragma solidity \u003e=0.5.4 \u003c0.7.0;\n\nimport \"./IWallet.sol\";\n\n/**\n * @title Storage\n * @notice Base contract for the storage of a wallet.\n * @author Julien Niset - \[email protected]\u003e\n */\ncontract Storage {\n\n /**\n * @notice Throws if the caller is not an authorised module.\n */\n modifier onlyModule(address _wallet) {\n require(IWallet(_wallet).authorised(msg.sender), \"TS: must be an authorized module to call this method\");\n _;\n }\n}"}}