Transaction Hash:
Block:
17490290 at Jun-16-2023 05:22:35 AM +UTC
Transaction Fee:
0.002425278365083926 ETH
$5.05
Gas Used:
160,029 Gas / 15.155242894 Gwei
Emitted Events:
| 226 |
0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.0xd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb( 0xd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb, 0x0000000000000000000000009d58779365b067d5d3fcc6e92d237acd06f1e6a1, 0x01ffc9a700000000000000000000000000000000000000000000000000000000 )
|
| 227 |
0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.0xd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb( 0xd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb, 0x0000000000000000000000009d58779365b067d5d3fcc6e92d237acd06f1e6a1, 0xf23a6e6100000000000000000000000000000000000000000000000000000000 )
|
| 228 |
0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.0xd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb( 0xd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb, 0x0000000000000000000000009d58779365b067d5d3fcc6e92d237acd06f1e6a1, 0xbc197c8100000000000000000000000000000000000000000000000000000000 )
|
| 229 |
0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365( 0x7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365, 0x0000000000000000000000009d58779365b067d5d3fcc6e92d237acd06f1e6a1, 0x000000000000000000000000482579f93dc13e6b434e38b5a0447ca543d88a46, 0x0000000000000000000000000000000000000000000000000008af252a3f57fe, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000000 )
|
| 230 |
ArgentModule.Refund( wallet=0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813, refundAddress=0x482579f93dc13e6b434e38b5a0447ca543d88a46, refundToken=0x00000000...000000000, refundAmount=2444373971130366 )
|
| 231 |
ArgentModule.TransactionExecuted( wallet=0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813, success=True, returnData=0x, signedHash=018E2A7AFDBAB090E75D8C4D25B0A11F916211B8C2E70FCE0C66C1AD58BA335F )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x1cED2cef...F3B12D06f | (Argent: Relayer 8) |
4.51238703758687712 Eth
Nonce: 63277
|
4.509961759221793194 Eth
Nonce: 63278
| 0.002425278365083926 | |
| 0x246c416A...EDf36e813 | 0.0268164762218035 Eth | 0.024372102250673134 Eth | 0.002444373971130366 | ||
| 0x482579F9...543D88A46 | 31.856251858216183137 Eth | 31.858696232187313503 Eth | 0.002444373971130366 | ||
| 0x9D587793...d06F1e6a1 | (Argent: Argent Module) | ||||
|
0xe688b84b...2CDB71127
Miner
| (Fee Recipient: 0xe68...127) | 382.691445054054153413 Eth | 382.691464257534153413 Eth | 0.00001920348 |
Execution Trace
ArgentModule.execute( _wallet=0x246c416A4Fd2eB77D34cB87e64B4e2eEDf36e813, _data=0x59B4958A000000000000000000000000246C416A4FD2EB77D34CB87E64B4E2EEDF36E813, _nonce=5951636939051253877190362799243173801065443731, _signatures=0x42F941AA060DAD71A31379E59454366D6354D32E83CBDD2FECF835852C2262E8481166803C86F5FEFD25061CBDBED8F8B4A5FBA149CEA77BDD2C17821471A0A51C, _gasPrice=16550000000, _gasLimit=163330, _refundToken=0x0000000000000000000000000000000000000000, _refundAddress=0x482579F93dC13e6B434E38b5a0447ca543D88A46 ) => ( True )
-
Null: 0x000...001.018e2a7a( ) 0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.STATICCALL( )-
BaseWallet.DELEGATECALL( )
-
ArgentModule.enableERC1155TokenReceiver( _wallet=0x246c416A4Fd2eB77D34cB87e64B4e2eEDf36e813 )0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.13da30b2( )-
BaseWallet.enableStaticCall( _module=0x9D58779365B067D5D3fCc6e92d237aCd06F1e6a1, _method=System.Byte[] )
-
0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.13da30b2( )-
BaseWallet.enableStaticCall( _module=0x9D58779365B067D5D3fCc6e92d237aCd06F1e6a1, _method=System.Byte[] )
-
0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.13da30b2( )-
BaseWallet.enableStaticCall( _module=0x9D58779365B067D5D3fCc6e92d237aCd06F1e6a1, _method=System.Byte[] )
-
-
DappRegistry.isAuthorised( _wallet=0x246c416A4Fd2eB77D34cB87e64B4e2eEDf36e813, _spender=0x482579F93dC13e6B434E38b5a0447ca543D88A46, _to=0x0000000000000000000000000000000000000000, _data=0x ) => ( True ) 0x246c416a4fd2eb77d34cb87e64b4e2eedf36e813.8f6f0332( )BaseWallet.invoke( _target=0x482579F93dC13e6B434E38b5a0447ca543D88A46, _value=2444373971130366, _data=0x ) => ( _result=0x )- ETH 0.002444373971130366
0x482579f93dc13e6b434e38b5a0447ca543d88a46.CALL( )
- ETH 0.002444373971130366
File 1 of 3: ArgentModule
File 2 of 3: BaseWallet
File 3 of 3: DappRegistry
// Copyright (C) 2021 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.8.3;
interface IAuthoriser {
function isAuthorised(address _sender, address _spender, address _to, bytes calldata _data) external view returns (bool);
function areAuthorised(
address _spender,
address[] calldata _spenders,
address[] calldata _to,
bytes[] calldata _data
)
external
view
returns (bool);
}// 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.9.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);
}// 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.9.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);
}// 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.9.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) 2021 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.8.3;
import "./common/Utils.sol";
import "./common/BaseModule.sol";
import "./RelayerManager.sol";
import "./SecurityManager.sol";
import "./TransactionManager.sol";
/**
* @title ArgentModule
* @notice Single module for the Argent wallet.
* @author Julien Niset - <julien@argent.xyz>
*/
contract ArgentModule is BaseModule, RelayerManager, SecurityManager, TransactionManager {
bytes32 constant public NAME = "ArgentModule";
constructor (
IModuleRegistry _registry,
IGuardianStorage _guardianStorage,
ITransferStorage _userWhitelist,
IAuthoriser _authoriser,
address _uniswapRouter,
uint256 _securityPeriod,
uint256 _securityWindow,
uint256 _recoveryPeriod,
uint256 _lockPeriod
)
BaseModule(_registry, _guardianStorage, _userWhitelist, _authoriser, NAME)
SecurityManager(_recoveryPeriod, _securityPeriod, _securityWindow, _lockPeriod)
TransactionManager(_securityPeriod)
RelayerManager(_uniswapRouter)
{
}
/**
* @inheritdoc IModule
*/
function init(address _wallet) external override onlyWallet(_wallet) {
enableDefaultStaticCalls(_wallet);
}
/**
* @inheritdoc IModule
*/
function addModule(address _wallet, address _module) external override onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
require(registry.isRegisteredModule(_module), "AM: module is not registered");
IWallet(_wallet).authoriseModule(_module, true);
}
/**
* @inheritdoc RelayerManager
*/
function getRequiredSignatures(address _wallet, bytes calldata _data) public view override returns (uint256, OwnerSignature) {
bytes4 methodId = Utils.functionPrefix(_data);
if (methodId == TransactionManager.multiCall.selector ||
methodId == TransactionManager.addToWhitelist.selector ||
methodId == TransactionManager.removeFromWhitelist.selector ||
methodId == TransactionManager.enableERC1155TokenReceiver.selector ||
methodId == TransactionManager.clearSession.selector ||
methodId == ArgentModule.addModule.selector ||
methodId == SecurityManager.addGuardian.selector ||
methodId == SecurityManager.revokeGuardian.selector ||
methodId == SecurityManager.cancelGuardianAddition.selector ||
methodId == SecurityManager.cancelGuardianRevokation.selector)
{
// owner
return (1, OwnerSignature.Required);
}
if (methodId == TransactionManager.multiCallWithSession.selector) {
return (1, OwnerSignature.Session);
}
if (methodId == SecurityManager.executeRecovery.selector) {
// majority of guardians
uint numberOfSignaturesRequired = _majorityOfGuardians(_wallet);
require(numberOfSignaturesRequired > 0, "AM: no guardians set on wallet");
return (numberOfSignaturesRequired, OwnerSignature.Disallowed);
}
if (methodId == SecurityManager.cancelRecovery.selector) {
// majority of (owner + guardians)
uint numberOfSignaturesRequired = Utils.ceil(recoveryConfigs[_wallet].guardianCount + 1, 2);
return (numberOfSignaturesRequired, OwnerSignature.Optional);
}
if (methodId == TransactionManager.multiCallWithGuardians.selector ||
methodId == TransactionManager.multiCallWithGuardiansAndStartSession.selector ||
methodId == SecurityManager.transferOwnership.selector)
{
// owner + majority of guardians
uint majorityGuardians = _majorityOfGuardians(_wallet);
uint numberOfSignaturesRequired = majorityGuardians + 1;
return (numberOfSignaturesRequired, OwnerSignature.Required);
}
if (methodId == SecurityManager.finalizeRecovery.selector ||
methodId == SecurityManager.confirmGuardianAddition.selector ||
methodId == SecurityManager.confirmGuardianRevokation.selector)
{
// anyone
return (0, OwnerSignature.Anyone);
}
if (methodId == SecurityManager.lock.selector || methodId == SecurityManager.unlock.selector) {
// any guardian
return (1, OwnerSignature.Disallowed);
}
revert("SM: unknown method");
}
function _majorityOfGuardians(address _wallet) internal view returns (uint) {
return Utils.ceil(guardianStorage.guardianCount(_wallet), 2);
}
}// 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.8.3;
import "@openzeppelin/contracts/utils/math/Math.sol";
import "./common/Utils.sol";
import "./common/BaseModule.sol";
import "./common/SimpleOracle.sol";
import "../infrastructure/storage/IGuardianStorage.sol";
/**
* @title RelayerManager
* @notice Abstract Module to execute transactions signed by ETH-less accounts and sent by a relayer.
* @author Julien Niset <julien@argent.xyz>, Olivier VDB <olivier@argent.xyz>
*/
abstract contract RelayerManager is BaseModule, SimpleOracle {
uint256 constant internal BLOCKBOUND = 10000;
mapping (address => RelayerConfig) internal relayer;
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);
// *************** Constructor ************************ //
constructor(address _uniswapRouter) SimpleOracle(_uniswapRouter) {
}
/* ***************** External methods ************************* */
/**
* @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) public view virtual returns (uint256, OwnerSignature);
/**
* @notice Executes a relayed transaction.
* @param _wallet The target wallet.
* @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 max gas price (in token) to use for the gas refund.
* @param _gasLimit The max 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,
bytes calldata _data,
uint256 _nonce,
bytes calldata _signatures,
uint256 _gasPrice,
uint256 _gasLimit,
address _refundToken,
address _refundAddress
)
external
returns (bool)
{
// initial gas = 21k + non_zero_bytes * 16 + zero_bytes * 4
// ~= 21k + calldata.length * [1/3 * 16 + 2/3 * 4]
uint256 startGas = gasleft() + 21000 + msg.data.length * 8;
require(startGas >= _gasLimit, "RM: not enough gas provided");
require(verifyData(_wallet, _data), "RM: Target of _data != _wallet");
require(!_isLocked(_wallet) || _gasPrice == 0, "RM: Locked wallet refund");
StackExtension memory stack;
(stack.requiredSignatures, stack.ownerSignatureRequirement) = 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),
0,
_data,
_nonce,
_gasPrice,
_gasLimit,
_refundToken,
_refundAddress);
require(checkAndUpdateUniqueness(
_wallet,
_nonce,
stack.signHash,
stack.requiredSignatures,
stack.ownerSignatureRequirement), "RM: Duplicate request");
if (stack.ownerSignatureRequirement == OwnerSignature.Session) {
require(validateSession(_wallet, stack.signHash, _signatures), "RM: Invalid session");
} else {
require(validateSignatures(_wallet, stack.signHash, _signatures, stack.ownerSignatureRequirement), "RM: Invalid signatures");
}
(stack.success, stack.returnData) = address(this).call(_data);
refund(
_wallet,
startGas,
_gasPrice,
_gasLimit,
_refundToken,
_refundAddress,
stack.requiredSignatures,
stack.ownerSignatureRequirement);
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];
}
/**
* @notice Gets the last stored session for a wallet.
* @param _wallet The target wallet.
*/
function getSession(address _wallet) external view returns (address key, uint64 expires) {
return (sessions[_wallet].key, sessions[_wallet].expires);
}
/* ***************** 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 _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 max gas price (in token) to use for the gas refund.
* @param _gasLimit The max 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,
uint256 _value,
bytes memory _data,
uint256 _nonce,
uint256 _gasPrice,
uint256 _gasLimit,
address _refundToken,
address _refundAddress
)
internal
view
returns (bytes32)
{
return keccak256(
abi.encodePacked(
"\\x19Ethereum Signed Message:\
32",
keccak256(abi.encodePacked(
bytes1(0x19),
bytes1(0),
_from,
_value,
_data,
block.chainid,
_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 or a session key 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 || ownerSignatureRequirement == OwnerSignature.Session)) {
// 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.
* @param _wallet The target wallet.
* @param _signHash The signed hash representing the relayed transaction.
* @param _signatures The signatures as a concatenated bytes array.
* @param _option An OwnerSignature 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) = Utils.isGuardianOrGuardianSigner(guardians, signer);
if (!isGuardian) {
return false;
}
}
return true;
}
/**
* @notice Validates the signature provided when a session key was used.
* @param _wallet The target wallet.
* @param _signHash The signed hash representing the relayed transaction.
* @param _signatures The signatures as a concatenated bytes array.
* @return A boolean indicating whether the signature is valid.
*/
function validateSession(address _wallet, bytes32 _signHash, bytes calldata _signatures) internal view returns (bool) {
Session memory session = sessions[_wallet];
address signer = Utils.recoverSigner(_signHash, _signatures, 0);
return (signer == session.key && session.expires >= block.timestamp);
}
/**
* @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 max gas price (in token) for the refund.
* @param _gasLimit The max 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.
* @param _option An OwnerSignature enum indicating the signature requirement.
*/
function refund(
address _wallet,
uint _startGas,
uint _gasPrice,
uint _gasLimit,
address _refundToken,
address _refundAddress,
uint256 _requiredSignatures,
OwnerSignature _option
)
internal
{
// Only refund when the owner is one of the signers or a session key was used
if (_gasPrice > 0 && (_option == OwnerSignature.Required || _option == OwnerSignature.Session)) {
address refundAddress = _refundAddress == address(0) ? msg.sender : _refundAddress;
if (_requiredSignatures == 1 && _option == OwnerSignature.Required) {
// refundAddress must be whitelisted/authorised
if (!authoriser.isAuthorised(_wallet, refundAddress, address(0), EMPTY_BYTES)) {
uint whitelistAfter = userWhitelist.getWhitelist(_wallet, refundAddress);
require(whitelistAfter > 0 && whitelistAfter < block.timestamp, "RM: refund not authorised");
}
}
uint256 refundAmount;
if (_refundToken == ETH_TOKEN) {
// 23k as an upper bound to cover the rest of refund logic
uint256 gasConsumed = _startGas - gasleft() + 23000;
refundAmount = Math.min(gasConsumed, _gasLimit) * (Math.min(_gasPrice, tx.gasprice));
invokeWallet(_wallet, refundAddress, refundAmount, EMPTY_BYTES);
} else {
// 37.5k as an upper bound to cover the rest of refund logic
uint256 gasConsumed = _startGas - gasleft() + 37500;
uint256 tokenGasPrice = inToken(_refundToken, tx.gasprice);
refundAmount = Math.min(gasConsumed, _gasLimit) * (Math.min(_gasPrice, tokenGasPrice));
bytes memory methodData = abi.encodeWithSelector(ERC20.transfer.selector, 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 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;
}
}// 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.8.3;
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./common/Utils.sol";
import "./common/BaseModule.sol";
import "../wallet/IWallet.sol";
/**
* @title SecurityManager
* @notice Abstract module implementing the key security features of the wallet: guardians, lock and recovery.
* @author Julien Niset - <julien@argent.xyz>
* @author Olivier Van Den Biggelaar - <olivier@argent.xyz>
*/
abstract contract SecurityManager is BaseModule {
struct RecoveryConfig {
address recovery;
uint64 executeAfter;
uint32 guardianCount;
}
struct GuardianManagerConfig {
// The time at which a guardian addition or revokation will be confirmable by the owner
mapping (bytes32 => uint256) pending;
}
// Wallet specific storage for recovery
mapping (address => RecoveryConfig) internal recoveryConfigs;
// Wallet specific storage for pending guardian addition/revokation
mapping (address => GuardianManagerConfig) internal guardianConfigs;
// Recovery period
uint256 internal immutable recoveryPeriod;
// Lock period
uint256 internal immutable lockPeriod;
// The security period to add/remove guardians
uint256 internal immutable securityPeriod;
// The security window
uint256 internal immutable securityWindow;
// *************** Events *************************** //
event RecoveryExecuted(address indexed wallet, address indexed _recovery, uint64 executeAfter);
event RecoveryFinalized(address indexed wallet, address indexed _recovery);
event RecoveryCanceled(address indexed wallet, address indexed _recovery);
event OwnershipTransfered(address indexed wallet, address indexed _newOwner);
event Locked(address indexed wallet, uint64 releaseAfter);
event Unlocked(address indexed wallet);
event GuardianAdditionRequested(address indexed wallet, address indexed guardian, uint256 executeAfter);
event GuardianRevokationRequested(address indexed wallet, address indexed guardian, uint256 executeAfter);
event GuardianAdditionCancelled(address indexed wallet, address indexed guardian);
event GuardianRevokationCancelled(address indexed wallet, address indexed guardian);
event GuardianAdded(address indexed wallet, address indexed guardian);
event GuardianRevoked(address indexed wallet, address indexed guardian);
// *************** Modifiers ************************ //
/**
* @notice Throws if there is no ongoing recovery procedure.
*/
modifier onlyWhenRecovery(address _wallet) {
require(recoveryConfigs[_wallet].executeAfter > 0, "SM: no ongoing recovery");
_;
}
/**
* @notice Throws if there is an ongoing recovery procedure.
*/
modifier notWhenRecovery(address _wallet) {
require(recoveryConfigs[_wallet].executeAfter == 0, "SM: ongoing recovery");
_;
}
/**
* @notice Throws if the caller is not a guardian for the wallet or the module itself.
*/
modifier onlyGuardianOrSelf(address _wallet) {
require(_isSelf(msg.sender) || isGuardian(_wallet, msg.sender), "SM: must be guardian/self");
_;
}
// *************** Constructor ************************ //
constructor(
uint256 _recoveryPeriod,
uint256 _securityPeriod,
uint256 _securityWindow,
uint256 _lockPeriod
) {
// For the wallet to be secure we must have recoveryPeriod >= securityPeriod + securityWindow
// where securityPeriod and securityWindow are the security parameters of adding/removing guardians.
require(_lockPeriod >= _recoveryPeriod, "SM: insecure lock period");
require(_recoveryPeriod >= _securityPeriod + _securityWindow, "SM: insecure security periods");
recoveryPeriod = _recoveryPeriod;
lockPeriod = _lockPeriod;
securityWindow = _securityWindow;
securityPeriod = _securityPeriod;
}
// *************** External functions ************************ //
// *************** Recovery functions ************************ //
/**
* @notice Lets the guardians start the execution of the recovery procedure.
* Once triggered the recovery is pending for the security period before it can be finalised.
* Must be confirmed by N guardians, where N = ceil(Nb Guardians / 2).
* @param _wallet The target wallet.
* @param _recovery The address to which ownership should be transferred.
*/
function executeRecovery(address _wallet, address _recovery) external onlySelf() notWhenRecovery(_wallet) {
validateNewOwner(_wallet, _recovery);
uint64 executeAfter = uint64(block.timestamp + recoveryPeriod);
recoveryConfigs[_wallet] = RecoveryConfig(_recovery, executeAfter, uint32(guardianStorage.guardianCount(_wallet)));
_setLock(_wallet, block.timestamp + lockPeriod, SecurityManager.executeRecovery.selector);
emit RecoveryExecuted(_wallet, _recovery, executeAfter);
}
/**
* @notice Finalizes an ongoing recovery procedure if the security period is over.
* The method is public and callable by anyone to enable orchestration.
* @param _wallet The target wallet.
*/
function finalizeRecovery(address _wallet) external onlyWhenRecovery(_wallet) {
RecoveryConfig storage config = recoveryConfigs[_wallet];
require(uint64(block.timestamp) > config.executeAfter, "SM: ongoing recovery period");
address recoveryOwner = config.recovery;
delete recoveryConfigs[_wallet];
_clearSession(_wallet);
IWallet(_wallet).setOwner(recoveryOwner);
_setLock(_wallet, 0, bytes4(0));
emit RecoveryFinalized(_wallet, recoveryOwner);
}
/**
* @notice Lets the owner cancel an ongoing recovery procedure.
* Must be confirmed by N guardians, where N = ceil(Nb Guardian at executeRecovery + 1) / 2) - 1.
* @param _wallet The target wallet.
*/
function cancelRecovery(address _wallet) external onlySelf() onlyWhenRecovery(_wallet) {
address recoveryOwner = recoveryConfigs[_wallet].recovery;
delete recoveryConfigs[_wallet];
_setLock(_wallet, 0, bytes4(0));
emit RecoveryCanceled(_wallet, recoveryOwner);
}
/**
* @notice Lets the owner transfer the wallet ownership. This is executed immediately.
* @param _wallet The target wallet.
* @param _newOwner The address to which ownership should be transferred.
*/
function transferOwnership(address _wallet, address _newOwner) external onlySelf() onlyWhenUnlocked(_wallet) {
validateNewOwner(_wallet, _newOwner);
IWallet(_wallet).setOwner(_newOwner);
emit OwnershipTransfered(_wallet, _newOwner);
}
/**
* @notice Gets the details of the ongoing recovery procedure if any.
* @param _wallet The target wallet.
*/
function getRecovery(address _wallet) external view returns(address _address, uint64 _executeAfter, uint32 _guardianCount) {
RecoveryConfig storage config = recoveryConfigs[_wallet];
return (config.recovery, config.executeAfter, config.guardianCount);
}
// *************** Lock functions ************************ //
/**
* @notice Lets a guardian lock a wallet.
* @param _wallet The target wallet.
*/
function lock(address _wallet) external onlyGuardianOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
_setLock(_wallet, block.timestamp + lockPeriod, SecurityManager.lock.selector);
emit Locked(_wallet, uint64(block.timestamp + lockPeriod));
}
/**
* @notice Lets a guardian unlock a locked wallet.
* @param _wallet The target wallet.
*/
function unlock(address _wallet) external onlyGuardianOrSelf(_wallet) onlyWhenLocked(_wallet) {
require(locks[_wallet].locker == SecurityManager.lock.selector, "SM: cannot unlock");
_setLock(_wallet, 0, bytes4(0));
emit Unlocked(_wallet);
}
/**
* @notice Returns the release time of a wallet lock or 0 if the wallet is unlocked.
* @param _wallet The target wallet.
* @return _releaseAfter The epoch time at which the lock will release (in seconds).
*/
function getLock(address _wallet) external view returns(uint64 _releaseAfter) {
return _isLocked(_wallet) ? locks[_wallet].release : 0;
}
/**
* @notice Checks if a wallet is locked.
* @param _wallet The target wallet.
* @return _isLocked `true` if the wallet is locked otherwise `false`.
*/
function isLocked(address _wallet) external view returns (bool) {
return _isLocked(_wallet);
}
// *************** Guardian functions ************************ //
/**
* @notice Lets the owner add a guardian to its wallet.
* The first guardian is added immediately. All following additions must be confirmed
* by calling the confirmGuardianAddition() method.
* @param _wallet The target wallet.
* @param _guardian The guardian to add.
*/
function addGuardian(address _wallet, address _guardian) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
require(!_isOwner(_wallet, _guardian), "SM: guardian cannot be owner");
require(!isGuardian(_wallet, _guardian), "SM: duplicate guardian");
// Guardians must either be an EOA or a contract with an owner()
// method that returns an address with a 25000 gas stipend.
// Note that this test is not meant to be strict and can be bypassed by custom malicious contracts.
(bool success,) = _guardian.call{gas: 25000}(abi.encodeWithSignature("owner()"));
require(success, "SM: must be EOA/Argent wallet");
bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition"));
GuardianManagerConfig storage config = guardianConfigs[_wallet];
require(
config.pending[id] == 0 || block.timestamp > config.pending[id] + securityWindow,
"SM: duplicate pending addition");
config.pending[id] = block.timestamp + securityPeriod;
emit GuardianAdditionRequested(_wallet, _guardian, block.timestamp + securityPeriod);
}
/**
* @notice Confirms the pending addition of a guardian to a wallet.
* The method must be called during the confirmation window and can be called by anyone to enable orchestration.
* @param _wallet The target wallet.
* @param _guardian The guardian.
*/
function confirmGuardianAddition(address _wallet, address _guardian) external onlyWhenUnlocked(_wallet) {
bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition"));
GuardianManagerConfig storage config = guardianConfigs[_wallet];
require(config.pending[id] > 0, "SM: unknown pending addition");
require(config.pending[id] < block.timestamp, "SM: pending addition not over");
require(block.timestamp < config.pending[id] + securityWindow, "SM: pending addition expired");
guardianStorage.addGuardian(_wallet, _guardian);
emit GuardianAdded(_wallet, _guardian);
delete config.pending[id];
}
/**
* @notice Lets the owner cancel a pending guardian addition.
* @param _wallet The target wallet.
* @param _guardian The guardian.
*/
function cancelGuardianAddition(address _wallet, address _guardian) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition"));
GuardianManagerConfig storage config = guardianConfigs[_wallet];
require(config.pending[id] > 0, "SM: unknown pending addition");
delete config.pending[id];
emit GuardianAdditionCancelled(_wallet, _guardian);
}
/**
* @notice Lets the owner revoke a guardian from its wallet.
* @dev Revokation must be confirmed by calling the confirmGuardianRevokation() method.
* @param _wallet The target wallet.
* @param _guardian The guardian to revoke.
*/
function revokeGuardian(address _wallet, address _guardian) external onlyWalletOwnerOrSelf(_wallet) {
require(isGuardian(_wallet, _guardian), "SM: must be existing guardian");
bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation"));
GuardianManagerConfig storage config = guardianConfigs[_wallet];
require(
config.pending[id] == 0 || block.timestamp > config.pending[id] + securityWindow,
"SM: duplicate pending revoke"); // TODO need to allow if confirmation window passed
config.pending[id] = block.timestamp + securityPeriod;
emit GuardianRevokationRequested(_wallet, _guardian, block.timestamp + securityPeriod);
}
/**
* @notice Confirms the pending revokation of a guardian to a wallet.
* The method must be called during the confirmation window and can be called by anyone to enable orchestration.
* @param _wallet The target wallet.
* @param _guardian The guardian.
*/
function confirmGuardianRevokation(address _wallet, address _guardian) external {
bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation"));
GuardianManagerConfig storage config = guardianConfigs[_wallet];
require(config.pending[id] > 0, "SM: unknown pending revoke");
require(config.pending[id] < block.timestamp, "SM: pending revoke not over");
require(block.timestamp < config.pending[id] + securityWindow, "SM: pending revoke expired");
guardianStorage.revokeGuardian(_wallet, _guardian);
emit GuardianRevoked(_wallet, _guardian);
delete config.pending[id];
}
/**
* @notice Lets the owner cancel a pending guardian revokation.
* @param _wallet The target wallet.
* @param _guardian The guardian.
*/
function cancelGuardianRevokation(address _wallet, address _guardian) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation"));
GuardianManagerConfig storage config = guardianConfigs[_wallet];
require(config.pending[id] > 0, "SM: unknown pending revoke");
delete config.pending[id];
emit GuardianRevokationCancelled(_wallet, _guardian);
}
/**
* @notice Checks if an address is a guardian for a wallet.
* @param _wallet The target wallet.
* @param _guardian The address to check.
* @return _isGuardian `true` if the address is a guardian for the wallet otherwise `false`.
*/
function isGuardian(address _wallet, address _guardian) public view returns (bool _isGuardian) {
return guardianStorage.isGuardian(_wallet, _guardian);
}
/**
* @notice Checks if an address is a guardian or an account authorised to sign on behalf of a smart-contract guardian.
* @param _wallet The target wallet.
* @param _guardian the address to test
* @return _isGuardian `true` if the address is a guardian for the wallet otherwise `false`.
*/
function isGuardianOrGuardianSigner(address _wallet, address _guardian) external view returns (bool _isGuardian) {
(_isGuardian, ) = Utils.isGuardianOrGuardianSigner(guardianStorage.getGuardians(_wallet), _guardian);
}
/**
* @notice Counts the number of active guardians for a wallet.
* @param _wallet The target wallet.
* @return _count The number of active guardians for a wallet.
*/
function guardianCount(address _wallet) external view returns (uint256 _count) {
return guardianStorage.guardianCount(_wallet);
}
/**
* @notice Get the active guardians for a wallet.
* @param _wallet The target wallet.
* @return _guardians the active guardians for a wallet.
*/
function getGuardians(address _wallet) external view returns (address[] memory _guardians) {
return guardianStorage.getGuardians(_wallet);
}
// *************** Internal Functions ********************* //
function validateNewOwner(address _wallet, address _newOwner) internal view {
require(_newOwner != address(0), "SM: new owner cannot be null");
require(!isGuardian(_wallet, _newOwner), "SM: new owner cannot be guardian");
}
function _setLock(address _wallet, uint256 _releaseAfter, bytes4 _locker) internal {
locks[_wallet] = Lock(SafeCast.toUint64(_releaseAfter), _locker);
}
}// 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.8.3;
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./common/Utils.sol";
import "./common/BaseModule.sol";
import "../../lib_0.5/other/ERC20.sol";
/**
* @title TransactionManager
* @notice Module to execute transactions in sequence to e.g. transfer tokens (ETH, ERC20, ERC721, ERC1155) or call third-party contracts.
* @author Julien Niset - <julien@argent.xyz>
*/
abstract contract TransactionManager is BaseModule {
// Static calls
bytes4 private constant ERC1271_IS_VALID_SIGNATURE = bytes4(keccak256("isValidSignature(bytes32,bytes)"));
bytes4 private constant ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
bytes4 private constant ERC1155_RECEIVED = bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
bytes4 private constant ERC1155_BATCH_RECEIVED = bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"));
bytes4 private constant ERC165_INTERFACE = bytes4(keccak256("supportsInterface(bytes4)"));
struct Call {
address to;
uint256 value;
bytes data;
}
// The time delay for adding a trusted contact
uint256 internal immutable whitelistPeriod;
// *************** Events *************************** //
event AddedToWhitelist(address indexed wallet, address indexed target, uint64 whitelistAfter);
event RemovedFromWhitelist(address indexed wallet, address indexed target);
event SessionCreated(address indexed wallet, address sessionKey, uint64 expires);
event SessionCleared(address indexed wallet, address sessionKey);
// *************** Constructor ************************ //
constructor(uint256 _whitelistPeriod) {
whitelistPeriod = _whitelistPeriod;
}
// *************** External functions ************************ //
/**
* @notice Makes the target wallet execute a sequence of transactions authorised by the wallet owner.
* The method reverts if any of the inner transactions reverts.
* The method reverts if any of the inner transaction is not to a trusted contact or an authorised dapp.
* @param _wallet The target wallet.
* @param _transactions The sequence of transactions.
*/
function multiCall(
address _wallet,
Call[] calldata _transactions
)
external
onlySelf()
onlyWhenUnlocked(_wallet)
returns (bytes[] memory)
{
bytes[] memory results = new bytes[](_transactions.length);
for(uint i = 0; i < _transactions.length; i++) {
address spender = Utils.recoverSpender(_transactions[i].to, _transactions[i].data);
require(
(_transactions[i].value == 0 || spender == _transactions[i].to) &&
(isWhitelisted(_wallet, spender) || authoriser.isAuthorised(_wallet, spender, _transactions[i].to, _transactions[i].data)),
"TM: call not authorised");
results[i] = invokeWallet(_wallet, _transactions[i].to, _transactions[i].value, _transactions[i].data);
}
return results;
}
/**
* @notice Makes the target wallet execute a sequence of transactions authorised by a session key.
* The method reverts if any of the inner transactions reverts.
* @param _wallet The target wallet.
* @param _transactions The sequence of transactions.
*/
function multiCallWithSession(
address _wallet,
Call[] calldata _transactions
)
external
onlySelf()
onlyWhenUnlocked(_wallet)
returns (bytes[] memory)
{
return multiCallWithApproval(_wallet, _transactions);
}
/**
* @notice Makes the target wallet execute a sequence of transactions approved by a majority of guardians.
* The method reverts if any of the inner transactions reverts.
* @param _wallet The target wallet.
* @param _transactions The sequence of transactions.
*/
function multiCallWithGuardians(
address _wallet,
Call[] calldata _transactions
)
external
onlySelf()
onlyWhenUnlocked(_wallet)
returns (bytes[] memory)
{
return multiCallWithApproval(_wallet, _transactions);
}
/**
* @notice Makes the target wallet execute a sequence of transactions approved by a majority of guardians.
* The method reverts if any of the inner transactions reverts.
* Upon success a new session is started.
* @param _wallet The target wallet.
* @param _transactions The sequence of transactions.
*/
function multiCallWithGuardiansAndStartSession(
address _wallet,
Call[] calldata _transactions,
address _sessionUser,
uint64 _duration
)
external
onlySelf()
onlyWhenUnlocked(_wallet)
returns (bytes[] memory)
{
startSession(_wallet, _sessionUser, _duration);
return multiCallWithApproval(_wallet, _transactions);
}
/**
* @notice Clears the active session of a wallet if any.
* @param _wallet The target wallet.
*/
function clearSession(address _wallet) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
emit SessionCleared(_wallet, sessions[_wallet].key);
_clearSession(_wallet);
}
/**
* @notice Adds an address to the list of trusted contacts.
* @param _wallet The target wallet.
* @param _target The address to add.
*/
function addToWhitelist(address _wallet, address _target) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
require(_target != _wallet, "TM: Cannot whitelist wallet");
require(!registry.isRegisteredModule(_target), "TM: Cannot whitelist module");
require(!isWhitelisted(_wallet, _target), "TM: target already whitelisted");
uint256 whitelistAfter = block.timestamp + whitelistPeriod;
setWhitelist(_wallet, _target, whitelistAfter);
emit AddedToWhitelist(_wallet, _target, uint64(whitelistAfter));
}
/**
* @notice Removes an address from the list of trusted contacts.
* @param _wallet The target wallet.
* @param _target The address to remove.
*/
function removeFromWhitelist(address _wallet, address _target) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
setWhitelist(_wallet, _target, 0);
emit RemovedFromWhitelist(_wallet, _target);
}
/**
* @notice Checks if an address is a trusted contact for a wallet.
* @param _wallet The target wallet.
* @param _target The address.
* @return _isWhitelisted true if the address is a trusted contact.
*/
function isWhitelisted(address _wallet, address _target) public view returns (bool _isWhitelisted) {
uint whitelistAfter = userWhitelist.getWhitelist(_wallet, _target);
return whitelistAfter > 0 && whitelistAfter < block.timestamp;
}
/*
* @notice Enable the static calls required to make the wallet compatible with the ERC1155TokenReceiver
* interface (see https://eips.ethereum.org/EIPS/eip-1155#erc-1155-token-receiver). This method only
* needs to be called for wallets deployed in version lower or equal to 2.4.0 as the ERC1155 static calls
* are not available by default for these versions of BaseWallet
* @param _wallet The target wallet.
*/
function enableERC1155TokenReceiver(address _wallet) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) {
IWallet(_wallet).enableStaticCall(address(this), ERC165_INTERFACE);
IWallet(_wallet).enableStaticCall(address(this), ERC1155_RECEIVED);
IWallet(_wallet).enableStaticCall(address(this), ERC1155_BATCH_RECEIVED);
}
/**
* @inheritdoc IModule
*/
function supportsStaticCall(bytes4 _methodId) external pure override returns (bool _isSupported) {
return _methodId == ERC1271_IS_VALID_SIGNATURE ||
_methodId == ERC721_RECEIVED ||
_methodId == ERC165_INTERFACE ||
_methodId == ERC1155_RECEIVED ||
_methodId == ERC1155_BATCH_RECEIVED;
}
/** ******************* Callbacks ************************** */
/**
* @notice Returns true if this contract implements the interface defined by
* `interfaceId` (see https://eips.ethereum.org/EIPS/eip-165).
*/
function supportsInterface(bytes4 _interfaceID) external pure returns (bool) {
return _interfaceID == ERC165_INTERFACE || _interfaceID == (ERC1155_RECEIVED ^ ERC1155_BATCH_RECEIVED);
}
/**
* @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) external 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_IS_VALID_SIGNATURE;
}
fallback() external {
bytes4 methodId = Utils.functionPrefix(msg.data);
if(methodId == ERC721_RECEIVED || methodId == ERC1155_RECEIVED || methodId == ERC1155_BATCH_RECEIVED) {
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, 0x04)
return (0, 0x20)
}
}
}
// *************** Internal Functions ********************* //
function enableDefaultStaticCalls(address _wallet) internal {
// setup the static calls that are available for free for all wallets
IWallet(_wallet).enableStaticCall(address(this), ERC1271_IS_VALID_SIGNATURE);
IWallet(_wallet).enableStaticCall(address(this), ERC721_RECEIVED);
}
function multiCallWithApproval(address _wallet, Call[] calldata _transactions) internal returns (bytes[] memory) {
bytes[] memory results = new bytes[](_transactions.length);
for(uint i = 0; i < _transactions.length; i++) {
results[i] = invokeWallet(_wallet, _transactions[i].to, _transactions[i].value, _transactions[i].data);
}
return results;
}
function startSession(address _wallet, address _sessionUser, uint64 _duration) internal {
require(_sessionUser != address(0), "TM: Invalid session user");
require(_duration > 0, "TM: Invalid session duration");
uint64 expiry = SafeCast.toUint64(block.timestamp + _duration);
sessions[_wallet] = Session(_sessionUser, expiry);
emit SessionCreated(_wallet, _sessionUser, expiry);
}
function setWhitelist(address _wallet, address _target, uint256 _whitelistAfter) internal {
userWhitelist.setWhitelist(_wallet, _target, _whitelistAfter);
}
}// 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.8.3;
import "../../wallet/IWallet.sol";
import "../../infrastructure/IModuleRegistry.sol";
import "../../infrastructure/storage/IGuardianStorage.sol";
import "../../infrastructure/IAuthoriser.sol";
import "../../infrastructure/storage/ITransferStorage.sol";
import "./IModule.sol";
import "../../../lib_0.5/other/ERC20.sol";
/**
* @title BaseModule
* @notice Base Module contract that contains methods common to all Modules.
* @author Julien Niset - <julien@argent.xyz>, Olivier VDB - <olivier@argent.xyz>
*/
abstract contract BaseModule is IModule {
// Empty calldata
bytes constant internal EMPTY_BYTES = "";
// Mock token address for ETH
address constant internal ETH_TOKEN = address(0);
// The module registry
IModuleRegistry internal immutable registry;
// The guardians storage
IGuardianStorage internal immutable guardianStorage;
// The trusted contacts storage
ITransferStorage internal immutable userWhitelist;
// The authoriser
IAuthoriser internal immutable authoriser;
event ModuleCreated(bytes32 name);
enum OwnerSignature {
Anyone, // Anyone
Required, // Owner required
Optional, // Owner and/or guardians
Disallowed, // Guardians only
Session // Session only
}
struct Session {
address key;
uint64 expires;
}
// Maps wallet to session
mapping (address => Session) internal sessions;
struct Lock {
// the lock's release timestamp
uint64 release;
// the signature of the method that set the last lock
bytes4 locker;
}
// Wallet specific lock storage
mapping (address => Lock) internal locks;
/**
* @notice Throws if the wallet is not locked.
*/
modifier onlyWhenLocked(address _wallet) {
require(_isLocked(_wallet), "BM: wallet must be locked");
_;
}
/**
* @notice Throws if the wallet is locked.
*/
modifier onlyWhenUnlocked(address _wallet) {
require(!_isLocked(_wallet), "BM: wallet locked");
_;
}
/**
* @notice Throws if the sender is not the module itself.
*/
modifier onlySelf() {
require(_isSelf(msg.sender), "BM: must be module");
_;
}
/**
* @notice Throws if the sender is not the module itself or the owner of the target wallet.
*/
modifier onlyWalletOwnerOrSelf(address _wallet) {
require(_isSelf(msg.sender) || _isOwner(_wallet, msg.sender), "BM: must be wallet owner/self");
_;
}
/**
* @dev Throws if the sender is not the target wallet of the call.
*/
modifier onlyWallet(address _wallet) {
require(msg.sender == _wallet, "BM: caller must be wallet");
_;
}
constructor(
IModuleRegistry _registry,
IGuardianStorage _guardianStorage,
ITransferStorage _userWhitelist,
IAuthoriser _authoriser,
bytes32 _name
) {
registry = _registry;
guardianStorage = _guardianStorage;
userWhitelist = _userWhitelist;
authoriser = _authoriser;
emit ModuleCreated(_name);
}
/**
* @notice Moves tokens that have been sent to the module by mistake.
* @param _token The target token.
*/
function recoverToken(address _token) external {
uint total = ERC20(_token).balanceOf(address(this));
ERC20(_token).transfer(address(registry), total);
}
function _clearSession(address _wallet) internal {
delete sessions[_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 Helper method to check if a wallet is locked.
* @param _wallet The target wallet.
*/
function _isLocked(address _wallet) internal view returns (bool) {
return locks[_wallet].release > uint64(block.timestamp);
}
/**
* @notice Helper method to check if an address is the module itself.
* @param _addr The target address.
*/
function _isSelf(address _addr) internal view returns (bool) {
return _addr == address(this);
}
/**
* @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) {
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("BM: wallet invoke reverted");
}
}
}// 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.8.3;
/**
* @title IModule
* @notice Interface for a Module.
* @author Julien Niset - <julien@argent.xyz>, Olivier VDB - <olivier@argent.xyz>
*/
interface IModule {
/**\t
* @notice Adds a module to a wallet. Cannot execute when wallet is locked (or under recovery)\t
* @param _wallet The target wallet.\t
* @param _module The modules to authorise.\t
*/\t
function addModule(address _wallet, address _module) external;
/**
* @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 Returns whether the module implements a callback for a given static call method.
* @param _methodId The method id.
*/
function supportsStaticCall(bytes4 _methodId) external view returns (bool _isSupported);
}// Copyright (C) 2021 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.8.3;
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
contract SimpleOracle {
address internal immutable weth;
address internal immutable uniswapV2Factory;
constructor(address _uniswapRouter) {
weth = IUniswapV2Router01(_uniswapRouter).WETH();
uniswapV2Factory = IUniswapV2Router01(_uniswapRouter).factory();
}
function inToken(address _token, uint256 _ethAmount) internal view returns (uint256) {
(uint256 wethReserve, uint256 tokenReserve) = getReservesForTokenPool(_token);
return _ethAmount * tokenReserve / wethReserve;
}
function getReservesForTokenPool(address _token) internal view returns (uint256 wethReserve, uint256 tokenReserve) {
if (weth < _token) {
address pair = getPairForSorted(weth, _token);
(wethReserve, tokenReserve,) = IUniswapV2Pair(pair).getReserves();
} else {
address pair = getPairForSorted(_token, weth);
(tokenReserve, wethReserve,) = IUniswapV2Pair(pair).getReserves();
}
require(wethReserve != 0 && tokenReserve != 0, "SO: no liquidity");
}
function getPairForSorted(address tokenA, address tokenB) internal virtual view returns (address pair) {
pair = address(uint160(uint256(keccak256(abi.encodePacked(
hex'ff',
uniswapV2Factory,
keccak256(abi.encodePacked(tokenA, tokenB)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f'
)))));
}
}// 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.8.3;
/**
* @title Utils
* @notice Common utility methods used by modules.
*/
library Utils {
// ERC20, ERC721 & ERC1155 transfers & approvals
bytes4 private constant ERC20_TRANSFER = bytes4(keccak256("transfer(address,uint256)"));
bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)"));
bytes4 private constant ERC721_SET_APPROVAL_FOR_ALL = bytes4(keccak256("setApprovalForAll(address,bool)"));
bytes4 private constant ERC721_TRANSFER_FROM = bytes4(keccak256("transferFrom(address,address,uint256)"));
bytes4 private constant ERC721_SAFE_TRANSFER_FROM = bytes4(keccak256("safeTransferFrom(address,address,uint256)"));
bytes4 private constant ERC721_SAFE_TRANSFER_FROM_BYTES = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)"));
bytes4 private constant ERC1155_SAFE_TRANSFER_FROM = bytes4(keccak256("safeTransferFrom(address,address,uint256,uint256,bytes)"));
bytes4 private constant OWNER_SIG = 0x8da5cb5b;
/**
* @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, "Utils: bad v value in signature");
address recoveredAddress = ecrecover(_signedHash, v, r, s);
require(recoveredAddress != address(0), "Utils: ecrecover returned 0");
return recoveredAddress;
}
/**
* @notice Helper method to recover the spender from a contract call.
* The method returns the contract unless the call is to a standard method of a ERC20/ERC721/ERC1155 token
* in which case the spender is recovered from the data.
* @param _to The target contract.
* @param _data The data payload.
*/
function recoverSpender(address _to, bytes memory _data) internal pure returns (address spender) {
if(_data.length >= 68) {
bytes4 methodId;
// solhint-disable-next-line no-inline-assembly
assembly {
methodId := mload(add(_data, 0x20))
}
if(
methodId == ERC20_TRANSFER ||
methodId == ERC20_APPROVE ||
methodId == ERC721_SET_APPROVAL_FOR_ALL)
{
// solhint-disable-next-line no-inline-assembly
assembly {
spender := mload(add(_data, 0x24))
}
return spender;
}
if(
methodId == ERC721_TRANSFER_FROM ||
methodId == ERC721_SAFE_TRANSFER_FROM ||
methodId == ERC721_SAFE_TRANSFER_FROM_BYTES ||
methodId == ERC1155_SAFE_TRANSFER_FROM)
{
// solhint-disable-next-line no-inline-assembly
assembly {
spender := mload(add(_data, 0x44))
}
return spender;
}
}
spender = _to;
}
/**
* @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, "Utils: Invalid functionPrefix");
// solhint-disable-next-line no-inline-assembly
assembly {
prefix := mload(add(_data, 0x20))
}
}
/**
* @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 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 the owner of a guardian contract.
* The method does not revert if the call to the owner() method consumes more then 25000 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);
// solhint-disable-next-line no-inline-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr,OWNER_SIG)
let result := staticcall(25000, _guardian, ptr, 0x20, ptr, 0x20)
if eq(result, 1) {
owner := mload(ptr)
}
}
return owner == _owner;
}
/**
* @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;
}
}
}
// 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.9.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.9.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);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow, so we distribute
return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such 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.
*
* Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
* all math on `uint256` and `int256` and then downcasting.
*/
library SafeCast {
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
require(value < 2**128, "SafeCast: value doesn\\'t fit in 128 bits");
return uint128(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
require(value < 2**64, "SafeCast: value doesn\\'t fit in 64 bits");
return uint64(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
require(value < 2**32, "SafeCast: value doesn\\'t fit in 32 bits");
return uint32(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
require(value < 2**16, "SafeCast: value doesn\\'t fit in 16 bits");
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*/
function toUint8(uint256 value) internal pure returns (uint8) {
require(value < 2**8, "SafeCast: value doesn\\'t fit in 8 bits");
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
require(value >= 0, "SafeCast: value must be positive");
return uint256(value);
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*
* _Available since v3.1._
*/
function toInt128(int256 value) internal pure returns (int128) {
require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\\'t fit in 128 bits");
return int128(value);
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*
* _Available since v3.1._
*/
function toInt64(int256 value) internal pure returns (int64) {
require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\\'t fit in 64 bits");
return int64(value);
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*
* _Available since v3.1._
*/
function toInt32(int256 value) internal pure returns (int32) {
require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\\'t fit in 32 bits");
return int32(value);
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*
* _Available since v3.1._
*/
function toInt16(int256 value) internal pure returns (int16) {
require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\\'t fit in 16 bits");
return int16(value);
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits.
*
* _Available since v3.1._
*/
function toInt8(int256 value) internal pure returns (int8) {
require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\\'t fit in 8 bits");
return int8(value);
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
require(value < 2**255, "SafeCast: value doesn't fit in an int256");
return int256(value);
}
}
pragma solidity >=0.5.0;
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
pragma solidity >=0.6.2;
interface IUniswapV2Router01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}
File 2 of 3: BaseWallet
pragma solidity ^0.6.12;
// Copyright (C) 2018 Argent Labs Ltd. <https://argent.xyz>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// SPDX-License-Identifier: GPL-3.0-only
/**
* @title IModule
* @notice Interface for a module.
* A module MUST implement the addModule() method to ensure that a wallet with at least one module
* can never end up in a "frozen" state.
* @author Julien Niset - <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;
}
/**
* @title IWallet
* @notice Interface for the BaseWallet
*/
interface IWallet {
/**
* @notice Returns the wallet owner.
* @return The wallet owner address.
*/
function owner() external view returns (address);
/**
* @notice Returns the number of authorised modules.
* @return The number of authorised modules.
*/
function modules() external view returns (uint);
/**
* @notice Sets a new owner for the wallet.
* @param _newOwner The new owner.
*/
function setOwner(address _newOwner) external;
/**
* @notice Checks if a module is authorised on the wallet.
* @param _module The module address to check.
* @return `true` if the module is authorised, otherwise `false`.
*/
function authorised(address _module) external view returns (bool);
/**
* @notice Returns the module responsible for a static call redirection.
* @param _sig The signature of the static call.
* @return the module doing the redirection
*/
function enabled(bytes4 _sig) external view returns (address);
/**
* @notice Enables/Disables a module.
* @param _module The target module.
* @param _value Set to `true` to authorise the module.
*/
function authoriseModule(address _module, bool _value) external;
/**
* @notice Enables a static method by specifying the target module to which the call must be delegated.
* @param _module The target module.
* @param _method The static method signature.
*/
function enableStaticCall(address _module, bytes4 _method) external;
}
/**
* @title BaseWallet
* @notice Simple modular wallet that authorises modules to call its invoke() method.
* @author Julien Niset - <julien@argent.xyz>
*/
contract BaseWallet is IWallet {
// The implementation of the proxy
address public implementation;
// The owner
address public override owner;
// The authorised modules
mapping (address => bool) public override authorised;
// The enabled static calls
mapping (bytes4 => address) public override enabled;
// The number of modules
uint public override modules;
event AuthorisedModule(address indexed module, bool value);
event EnabledStaticCall(address indexed module, bytes4 indexed method);
event Invoked(address indexed module, address indexed target, uint indexed value, bytes data);
event Received(uint indexed value, address indexed sender, bytes data);
event OwnerChanged(address owner);
/**
* @notice Throws if the sender is not an authorised module.
*/
modifier moduleOnly {
require(authorised[msg.sender], "BW: msg.sender not an authorized module");
_;
}
/**
* @notice Inits the wallet by setting the owner and authorising a list of modules.
* @param _owner The owner.
* @param _modules The modules to authorise.
*/
function init(address _owner, address[] calldata _modules) external {
require(owner == address(0) && modules == 0, "BW: wallet already initialised");
require(_modules.length > 0, "BW: construction requires at least 1 module");
owner = _owner;
modules = _modules.length;
for (uint256 i = 0; i < _modules.length; i++) {
require(authorised[_modules[i]] == false, "BW: module is already added");
authorised[_modules[i]] = true;
IModule(_modules[i]).init(address(this));
emit AuthorisedModule(_modules[i], true);
}
if (address(this).balance > 0) {
emit Received(address(this).balance, address(0), "");
}
}
/**
* @inheritdoc IWallet
*/
function authoriseModule(address _module, bool _value) external override moduleOnly {
if (authorised[_module] != _value) {
emit AuthorisedModule(_module, _value);
if (_value == true) {
modules += 1;
authorised[_module] = true;
IModule(_module).init(address(this));
} else {
modules -= 1;
require(modules > 0, "BW: wallet must have at least one module");
delete authorised[_module];
}
}
}
/**
* @inheritdoc IWallet
*/
function enableStaticCall(address _module, bytes4 _method) external override moduleOnly {
require(authorised[_module], "BW: must be an authorised module for static call");
enabled[_method] = _module;
emit EnabledStaticCall(_module, _method);
}
/**
* @inheritdoc IWallet
*/
function setOwner(address _newOwner) external override moduleOnly {
require(_newOwner != address(0), "BW: address cannot be null");
owner = _newOwner;
emit OwnerChanged(_newOwner);
}
/**
* @notice Performs a generic transaction.
* @param _target The address for the transaction.
* @param _value The value of the transaction.
* @param _data The data of the transaction.
*/
function invoke(address _target, uint _value, bytes calldata _data) external moduleOnly returns (bytes memory _result) {
bool success;
(success, _result) = _target.call{value: _value}(_data);
if (!success) {
// solhint-disable-next-line no-inline-assembly
assembly {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
emit Invoked(msg.sender, _target, _value, _data);
}
/**
* @notice This method delegates the static call to a target contract if the data corresponds
* to an enabled module, or logs the call otherwise.
*/
fallback() external payable {
address module = enabled[msg.sig];
if (module == address(0)) {
emit Received(msg.value, msg.sender, msg.data);
} else {
require(authorised[module], "BW: must be an authorised module for static call");
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, calldatasize())
let result := staticcall(gas(), module, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {revert(0, returndatasize())}
default {return (0, returndatasize())}
}
}
}
receive() external payable {
}
}File 3 of 3: DappRegistry
// Copyright (C) 2021 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.8.3;
import "./IAuthoriser.sol";
import "./dapp/IFilter.sol";
contract DappRegistry is IAuthoriser {
// The timelock period
uint64 public timelockPeriod;
// The new timelock period
uint64 public newTimelockPeriod;
// Time at which the new timelock becomes effective
uint64 public timelockPeriodChangeAfter;
// bit vector of enabled registry ids for each wallet
mapping (address => bytes32) public enabledRegistryIds; // [wallet] => [bit vector of 256 registry ids]
// authorised dapps and their filters for each registry id
mapping (uint8 => mapping (address => bytes32)) public authorisations; // [registryId] => [dapp] => [{filter:160}{validAfter:64}]
// pending authorised dapps and their filters for each registry id
mapping (uint8 => mapping (address => bytes32)) public pendingFilterUpdates; // [registryId] => [dapp] => [{filter:160}{validAfter:64}]
// owners for each registry id
mapping (uint8 => address) public registryOwners; // [registryId] => [owner]
event RegistryCreated(uint8 registryId, address registryOwner);
event OwnerChanged(uint8 registryId, address newRegistryOwner);
event TimelockChangeRequested(uint64 newTimelockPeriod);
event TimelockChanged(uint64 newTimelockPeriod);
event FilterUpdated(uint8 indexed registryId, address dapp, address filter, uint256 validAfter);
event FilterUpdateRequested(uint8 indexed registryId, address dapp, address filter, uint256 validAfter);
event DappAdded(uint8 indexed registryId, address dapp, address filter, uint256 validAfter);
event DappRemoved(uint8 indexed registryId, address dapp);
event ToggledRegistry(address indexed sender, uint8 registryId, bool enabled);
modifier onlyOwner(uint8 _registryId) {
validateOwner(_registryId);
_;
}
constructor(uint64 _timelockPeriod) {
// set the timelock period
timelockPeriod = _timelockPeriod;
// set the owner of the Argent Registry (registryId = 0)
registryOwners[0] = msg.sender;
emit RegistryCreated(0, msg.sender);
emit TimelockChanged(_timelockPeriod);
}
/********* Wallet-centered functions *************/
/**
* @notice Returns whether a registry is enabled for a wallet
* @param _wallet The wallet
* @param _registryId The registry id
*/
function isEnabledRegistry(address _wallet, uint8 _registryId) external view returns (bool isEnabled) {
uint registries = uint(enabledRegistryIds[_wallet]);
return (((registries >> _registryId) & 1) > 0) /* "is bit set for regId?" */ == (_registryId > 0) /* "not Argent registry?" */;
}
/**
* @notice Returns whether a (_spender, _to, _data) call is authorised for a wallet
* @param _wallet The wallet
* @param _spender The spender of the tokens for token approvals, or the target of the transaction otherwise
* @param _to The target of the transaction
* @param _data The calldata of the transaction
*/
function isAuthorised(address _wallet, address _spender, address _to, bytes calldata _data) public view override returns (bool) {
uint registries = uint(enabledRegistryIds[_wallet]);
// Check Argent Default Registry first. It is enabled by default, implying that a zero
// at position 0 of the `registries` bit vector means that the Argent Registry is enabled)
for(uint registryId = 0; registryId == 0 || (registries >> registryId) > 0; registryId++) {
bool isEnabled = (((registries >> registryId) & 1) > 0) /* "is bit set for regId?" */ == (registryId > 0) /* "not Argent registry?" */;
if(isEnabled) { // if registryId is enabled
uint auth = uint(authorisations[uint8(registryId)][_spender]);
uint validAfter = auth & 0xffffffffffffffff;
if (0 < validAfter && validAfter <= block.timestamp) { // if the current time is greater than the validity time
address filter = address(uint160(auth >> 64));
if(filter == address(0) || IFilter(filter).isValid(_wallet, _spender, _to, _data)) {
return true;
}
}
}
}
return false;
}
/**
* @notice Returns whether a collection of (_spender, _to, _data) calls are authorised for a wallet
* @param _wallet The wallet
* @param _spenders The spenders of the tokens for token approvals, or the targets of the transaction otherwise
* @param _to The targets of the transaction
* @param _data The calldata of the transaction
*/
function areAuthorised(
address _wallet,
address[] calldata _spenders,
address[] calldata _to,
bytes[] calldata _data
)
external
view
override
returns (bool)
{
for(uint i = 0; i < _spenders.length; i++) {
if(!isAuthorised(_wallet, _spenders[i], _to[i], _data[i])) {
return false;
}
}
return true;
}
/**
* @notice Allows a wallet to decide whether _registryId should be part of the list of enabled registries for that wallet
* @param _registryId The id of the registry to enable/disable
* @param _enabled Whether the registry should be enabled (true) or disabled (false)
*/
function toggleRegistry(uint8 _registryId, bool _enabled) external {
require(registryOwners[_registryId] != address(0), "DR: unknown registry");
uint registries = uint(enabledRegistryIds[msg.sender]);
bool current = (((registries >> _registryId) & 1) > 0) /* "is bit set for regId?" */ == (_registryId > 0) /* "not Argent registry?" */;
if(current != _enabled) {
enabledRegistryIds[msg.sender] = bytes32(registries ^ (uint(1) << _registryId)); // toggle [_registryId]^th bit
emit ToggledRegistry(msg.sender, _registryId, _enabled);
}
}
/************** Management of registry list *****************/
/**
* @notice Create a new registry. Only the owner of the Argent registry (i.e. the registry with id 0 -- hence the use of `onlyOwner(0)`)
* can create a new registry.
* @param _registryId The id of the registry to create
* @param _registryOwner The owner of that new registry
*/
function createRegistry(uint8 _registryId, address _registryOwner) external onlyOwner(0) {
require(_registryOwner != address(0), "DR: registry owner is 0");
require(registryOwners[_registryId] == address(0), "DR: duplicate registry");
registryOwners[_registryId] = _registryOwner;
emit RegistryCreated(_registryId, _registryOwner);
}
// Note: removeRegistry is not supported because that would allow the owner to replace registries that
// have already been enabled by users with a new (potentially maliciously populated) registry
/**
* @notice Lets a registry owner change the owner of the registry.
* @param _registryId The id of the registry
* @param _newRegistryOwner The new owner of the registry
*/
function changeOwner(uint8 _registryId, address _newRegistryOwner) external onlyOwner(_registryId) {
require(_newRegistryOwner != address(0), "DR: new registry owner is 0");
registryOwners[_registryId] = _newRegistryOwner;
emit OwnerChanged(_registryId, _newRegistryOwner);
}
/**
* @notice Request a change of the timelock value. Only the owner of the Argent registry (i.e. the registry with id 0 --
* hence the use of `onlyOwner(0)`) can perform that action. This action can be confirmed after the (old) timelock period.
* @param _newTimelockPeriod The new timelock period
*/
function requestTimelockChange(uint64 _newTimelockPeriod) external onlyOwner(0) {
newTimelockPeriod = _newTimelockPeriod;
timelockPeriodChangeAfter = uint64(block.timestamp) + timelockPeriod;
emit TimelockChangeRequested(_newTimelockPeriod);
}
/**
* @notice Confirm a change of the timelock value requested by `requestTimelockChange()`.
*/
function confirmTimelockChange() external {
uint64 newPeriod = newTimelockPeriod;
require(timelockPeriodChangeAfter > 0 && timelockPeriodChangeAfter <= block.timestamp, "DR: can't (yet) change timelock");
timelockPeriod = newPeriod;
newTimelockPeriod = 0;
timelockPeriodChangeAfter = 0;
emit TimelockChanged(newPeriod);
}
/************** Management of registries' content *****************/
/**
* @notice Returns the (filter, validAfter) tuple recorded for a dapp in a given registry.
* `filter` is the authorisation filter stored for the dapp (if any) and `validAfter` is the
* timestamp after which the filter becomes active.
* @param _registryId The registry id
* @param _dapp The dapp
*/
function getAuthorisation(uint8 _registryId, address _dapp) external view returns (address filter, uint64 validAfter) {
uint auth = uint(authorisations[_registryId][_dapp]);
filter = address(uint160(auth >> 64));
validAfter = uint64(auth & 0xffffffffffffffff);
}
/**
* @notice Add a new dapp to the registry with an optional filter
* @param _registryId The id of the registry to modify
* @param _dapp The address of the dapp contract to authorise.
* @param _filter The address of the filter contract to use, if any.
*/
function addDapp(uint8 _registryId, address _dapp, address _filter) external onlyOwner(_registryId) {
require(authorisations[_registryId][_dapp] == bytes32(0), "DR: dapp already added");
uint validAfter = block.timestamp + timelockPeriod;
// Store the new authorisation as {filter:160}{validAfter:64}.
authorisations[_registryId][_dapp] = bytes32((uint(uint160(_filter)) << 64) | validAfter);
emit DappAdded(_registryId, _dapp, _filter, validAfter);
}
/**
* @notice Deauthorise a dapp in a registry
* @param _registryId The id of the registry to modify
* @param _dapp The address of the dapp contract to deauthorise.
*/
function removeDapp(uint8 _registryId, address _dapp) external onlyOwner(_registryId) {
require(authorisations[_registryId][_dapp] != bytes32(0), "DR: unknown dapp");
delete authorisations[_registryId][_dapp];
delete pendingFilterUpdates[_registryId][_dapp];
emit DappRemoved(_registryId, _dapp);
}
/**
* @notice Request to change an authorisation filter for a dapp that has previously been authorised. We cannot
* immediately override the existing filter and need to store the new filter for a timelock period before being
* able to change the filter.
* @param _registryId The id of the registry to modify
* @param _dapp The address of the dapp contract to authorise.
* @param _filter The address of the new filter contract to use.
*/
function requestFilterUpdate(uint8 _registryId, address _dapp, address _filter) external onlyOwner(_registryId) {
require(authorisations[_registryId][_dapp] != bytes32(0), "DR: unknown dapp");
uint validAfter = block.timestamp + timelockPeriod;
// Store the future authorisation as {filter:160}{validAfter:64}
pendingFilterUpdates[_registryId][_dapp] = bytes32((uint(uint160(_filter)) << 64) | validAfter);
emit FilterUpdateRequested(_registryId, _dapp, _filter, validAfter);
}
/**
* @notice Confirm the filter change requested by `requestFilterUpdate`
* @param _registryId The id of the registry to modify
* @param _dapp The address of the dapp contract to authorise.
*/
function confirmFilterUpdate(uint8 _registryId, address _dapp) external {
uint newAuth = uint(pendingFilterUpdates[_registryId][_dapp]);
require(newAuth > 0, "DR: no pending filter update");
uint validAfter = newAuth & 0xffffffffffffffff;
require(validAfter <= block.timestamp, "DR: too early to confirm auth");
authorisations[_registryId][_dapp] = bytes32(newAuth);
emit FilterUpdated(_registryId, _dapp, address(uint160(newAuth >> 64)), validAfter);
delete pendingFilterUpdates[_registryId][_dapp];
}
/******** Internal Functions ***********/
function validateOwner(uint8 _registryId) internal view {
address owner = registryOwners[_registryId];
require(owner != address(0), "DR: unknown registry");
require(msg.sender == owner, "DR: sender != registry owner");
}
}// Copyright (C) 2021 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.8.3;
interface IAuthoriser {
function isAuthorised(address _sender, address _spender, address _to, bytes calldata _data) external view returns (bool);
function areAuthorised(
address _spender,
address[] calldata _spenders,
address[] calldata _to,
bytes[] calldata _data
)
external
view
returns (bool);
}// Copyright (C) 2021 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.8.3;
interface IFilter {
function isValid(address _wallet, address _spender, address _to, bytes calldata _data) external view returns (bool valid);
}