Transaction Hash:
Block:
11395334 at Dec-05-2020 10:28:59 PM +UTC
Transaction Fee:
0.00418788405 ETH
$8.83
Gas Used:
167,181 Gas / 25.05 Gwei
Emitted Events:
| 315 |
0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50.0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365( 0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365, 0x000000000000000000000000645ba45dbe3c6942c812a46f9ee8115c89b524ec, 0x0000000000000000000000005c703f5131bb3268e0dc32913a3c0994eda215fe, 0x00000000000000000000000000000000000000000000000006f05b59d3b20000, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000000 )
|
| 316 |
TransferManager.Transfer( wallet=0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50, token=0xEeeeeEee...eeeeeEEeE, amount=500000000000000000, to=0x5c703f5131bb3268e0dc32913a3c0994eda215fe, data=0x )
|
| 317 |
Proxy.Received( value=1750000000000000, sender=0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50, data=0x )
|
| 318 |
0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50.0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365( 0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365, 0x000000000000000000000000645ba45dbe3c6942c812a46f9ee8115c89b524ec, 0x000000000000000000000000de57844f758a0a6a1910a4787ab2f7121c8978c3, 0x0000000000000000000000000000000000000000000000000006379da05b6000, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000000 )
|
| 319 |
RelayerManager.Refund( wallet=0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50, refundAddress=Proxy, refundToken=0xEeeeeEee...eeeeeEEeE, refundAmount=1750000000000000 )
|
| 320 |
RelayerManager.TransactionExecuted( wallet=0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50, success=True, returnData=0x, signedHash=3F181A51AF82313927A1CBE72D9506D21D534655D25053A3C23CC3A20D84D422 )
|
Execution Trace
RelayerManager.execute( _wallet=0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50, _feature=0x5094a8f54B12AEc540bF7cCd0Dd7B62f4FecF7f2, _data=0x2DF546F4000000000000000000000000A64915BBDFBE8E4801624AE0B60FF49CA5E13A50000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE0000000000000000000000005C703F5131BB3268E0DC32913A3C0994EDA215FE00000000000000000000000000000000000000000000000006F05B59D3B2000000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000000, _nonce=3877630885092278463673486955429273549962742206, _signatures=0xA9BA75573EE4C2433DB815935FC60D356168E420D4BB3FD99BE578B184E97C3F79E7A9FBBF645D76BB9C33A333630A71AE15AB94719BA0A1332845596B02B6B11B, _gasPrice=25000000000, _gasLimit=70000, _refundToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, _refundAddress=0xdE57844F758A0A6a1910a4787AB2F7121c8978c3 ) => ( True )
-
VersionManager.isFeatureAuthorised( _wallet=0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50, _feature=0x5094a8f54B12AEc540bF7cCd0Dd7B62f4FecF7f2 ) => ( True ) -
TransferManager.getRequiredSignatures( 0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50, 0x2DF546F4000000000000000000000000A64915BBDFBE8E4801624AE0B60FF49CA5E13A50000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE0000000000000000000000005C703F5131BB3268E0DC32913A3C0994EDA215FE00000000000000000000000000000000000000000000000006F05B59D3B2000000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000000 ) => ( 1, 1 ) -
Null: 0x000...001.3f181a51( ) 0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50.STATICCALL( )-
BaseWallet.DELEGATECALL( )
-
TransferManager.transferToken( _wallet=0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50, _token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, _to=0x5c703F5131bB3268E0dC32913a3c0994eda215Fe, _amount=500000000000000000, _data=0x )-
VersionManager.isFeatureAuthorised( _wallet=0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50, _feature=0x10A0847c2D170008dDCa7C3a688124f493630032 ) => ( True ) -
LockStorage.isLocked( _wallet=0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50 ) => ( False ) -
0x391f0e86da951c03b1183c60b195090671adea88.13f4a0ea( ) VersionManager.checkAuthorisedFeatureAndInvokeWallet( _wallet=0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50, _to=0x5c703F5131bB3268E0dC32913a3c0994eda215Fe, _value=500000000000000000, _data=0x ) => ( _res=0x )0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50.8f6f0332( )BaseWallet.invoke( _target=0x5c703F5131bB3268E0dC32913a3c0994eda215Fe, _value=500000000000000000, _data=0x ) => ( _result=0x )- ETH 0.5
0x5c703f5131bb3268e0dc32913a3c0994eda215fe.CALL( )
- ETH 0.5
-
-
0x045b32efa0d97a681cc415f1b37c972ad7299a55.13565b2c( ) VersionManager.invokeStorage( _wallet=0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50, _storage=0x045B32efA0D97a681Cc415f1B37C972Ad7299a55, _data=0x5AE5BC52000000000000000000000000A64915BBDFBE8E4801624AE0B60FF49CA5E13A500000000000000000000000000000000000000000000000000006379DA05B6000000000000000000000000000000000000000000000000000000000005FCD5B2B )0x045b32efa0d97a681cc415f1b37c972ad7299a55.5ae5bc52( )0xa64915bbdfbe8e4801624ae0b60ff49ca5e13a50.d6eb1bbf( )-
BaseWallet.authorised( 0x645BA45dBe3c6942c812A46f9EE8115C89B524EC ) => ( True )
-
VersionManager.checkAuthorisedFeatureAndInvokeWallet( _wallet=0xA64915BBdFBe8e4801624AE0b60Ff49cA5e13A50, _to=0xdE57844F758A0A6a1910a4787AB2F7121c8978c3, _value=1750000000000000, _data=0x ) => ( _res=0x )
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: TransferManager
File 3 of 6: Proxy
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 - <julien@argent.xyz>, Olivier VDB - <olivier@argent.xyz>
*/
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 - <olivier@argent.xyz>
*/
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 - <julien@argent.xyz>, Olivier VDB - <olivier@argent.xyz>
*/
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 - <julien@argent.xyz>
*/
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 <julien@argent.xyz>, Olivier VDB <olivier@argent.xyz>
*/
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: 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 - <julien@argent.xyz>
*/
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 - <julien@argent.xyz>, Olivier VDB - <olivier@argent.xyz>
*/
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 - <olivier@argent.xyz>
*/
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 - <julien@argent.xyz>, Olivier VDB - <olivier@argent.xyz>
*/
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 - <olivier@argent.xyz>
*/
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 - <julien@argent.xyz>
*/
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 3 of 6: Proxy
// 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;
/**
* @title Proxy
* @dev Basic proxy that delegates all calls to a fixed implementing contract.
* The implementing contract cannot be upgraded.
* @author Julien Niset - <julien@argent.xyz>
*/
contract Proxy {
address implementation;
event Received(uint indexed value, address indexed sender, bytes data);
constructor(address _implementation) public {
implementation = _implementation;
}
function() external payable {
if (msg.data.length == 0 && msg.value > 0) {
emit Received(msg.value, msg.sender, msg.data);
} else {
// solium-disable-next-line security/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())}
}
}
}
}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 - <julien@argent.xyz>
*/
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 - <julien@argent.xyz>
*/
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 - <julien@argent.xyz>, Olivier VDB - <olivier@argent.xyz>
*/
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 - <olivier@argent.xyz>
*/
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 - <julien@argent.xyz>, Olivier VDB - <olivier@argent.xyz>
*/
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 <olivier@argent.xyz>
*/
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
// 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;
/**
* @title Module
* @dev 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 - <julien@argent.xyz>
*/
interface Module {
/**
* @dev Inits a module for a wallet by e.g. setting some wallet specific parameters in storage.
* @param _wallet The wallet.
*/
function init(BaseWallet _wallet) external;
/**
* @dev Adds a module to a wallet.
* @param _wallet The target wallet.
* @param _module The modules to authorise.
*/
function addModule(BaseWallet _wallet, Module _module) external;
/**
* @dev Utility method to recover any ERC20 token that was sent to the
* module by mistake.
* @param _token The token to recover.
*/
function recoverToken(address _token) external;
}
/**
* @title BaseWallet
* @dev Simple modular wallet that authorises modules to call its invoke() method.
* @author Julien Niset - <julien@argent.xyz>
*/
contract BaseWallet {
// The implementation of the proxy
address public implementation;
// The owner
address public owner;
// The authorised modules
mapping (address => bool) public authorised;
// The enabled static calls
mapping (bytes4 => address) public enabled;
// The number of modules
uint public 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);
/**
* @dev Throws if the sender is not an authorised module.
*/
modifier moduleOnly {
require(authorised[msg.sender], "BW: msg.sender not an authorized module");
_;
}
/**
* @dev 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;
Module(_modules[i]).init(this);
emit AuthorisedModule(_modules[i], true);
}
if (address(this).balance > 0) {
emit Received(address(this).balance, address(0), "");
}
}
/**
* @dev 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 moduleOnly {
if (authorised[_module] != _value) {
emit AuthorisedModule(_module, _value);
if (_value == true) {
modules += 1;
authorised[_module] = true;
Module(_module).init(this);
} else {
modules -= 1;
require(modules > 0, "BW: wallet must have at least one module");
delete authorised[_module];
}
}
}
/**
* @dev 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 moduleOnly {
require(authorised[_module], "BW: must be an authorised module for static call");
enabled[_method] = _module;
emit EnabledStaticCall(_module, _method);
}
/**
* @dev Sets a new owner for the wallet.
* @param _newOwner The new owner.
*/
function setOwner(address _newOwner) external moduleOnly {
require(_newOwner != address(0), "BW: address cannot be null");
owner = _newOwner;
emit OwnerChanged(_newOwner);
}
/**
* @dev 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;
// solium-disable-next-line security/no-call-value
(success, _result) = _target.call.value(_value)(_data);
if (!success) {
// solium-disable-next-line security/no-inline-assembly
assembly {
returndatacopy(0, 0, returndatasize)
revert(0, returndatasize)
}
}
emit Invoked(msg.sender, _target, _value, _data);
}
/**
* @dev This method makes it possible for the wallet to comply to interfaces expecting the wallet to
* implement specific static methods. It delegates the static call to a target contract if the data corresponds
* to an enabled method, or logs the call otherwise.
*/
function() external payable {
if (msg.data.length > 0) {
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");
// solium-disable-next-line security/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())}
}
}
}
}
}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 - \u003cjulien@argent.xyz\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 - \u003cjulien@argent.xyz\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 - \u003cjulien@argent.xyz\u003e\n * @author Olivier Van Den Biggelaar - \u003colivier@argent.xyz\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 - \u003cjulien@argent.xyz\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}"}}