Overview
S Balance
S Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 10 from a total of 10 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Create Authorize... | 6837749 | 5 days ago | IN | 0 S | 0.01982018 | ||||
Create Authorize... | 6837348 | 5 days ago | IN | 0 S | 0.01982018 | ||||
Create Authorize... | 6837152 | 5 days ago | IN | 0 S | 0.01981958 | ||||
Create Authorize... | 6811574 | 5 days ago | IN | 0 S | 0.01982018 | ||||
Create Authorize... | 6809791 | 5 days ago | IN | 0 S | 0.01280323 | ||||
Create Authorize... | 6809594 | 5 days ago | IN | 0 S | 0.01280323 | ||||
Create Authorize... | 6805420 | 5 days ago | IN | 0 S | 0.01982018 | ||||
Create Authorize... | 6805309 | 5 days ago | IN | 0 S | 0.01981958 | ||||
Create Authorize... | 6803441 | 5 days ago | IN | 0 S | 0.10701515 | ||||
Create Authorize... | 6802850 | 5 days ago | IN | 0 S | 0.01981842 |
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
ArgusAccountHelper
Compiler Version
v0.8.26+commit.8a97fa7a
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "../../interfaces/IVersion.sol"; import "../CoboFactory.sol"; import "./ArgusAuthorizerHelper.sol"; contract ArgusAccountHelper is ArgusAuthorizerHelper, IVersion { bytes32 public constant NAME = "ArgusAccountHelper"; uint256 public constant VERSION = 1; event ArgusInitialized(address indexed cobosafe, address indexed safe, address indexed factory); function initArgus(CoboFactory factory, bytes32 coboSafeAccountSalt) external { address safe = address(this); // 1. Create and enable CoboSafe. CoboSafeAccount coboSafe = CoboSafeAccount( payable(factory.create2AndRecord("CoboSafeAccount", coboSafeAccountSalt)) ); coboSafe.initialize(safe); IGnosisSafe(safe).enableModule(address(coboSafe)); // 2. Set roleManager. FlatRoleManager roleManager = FlatRoleManager(factory.create("FlatRoleManager")); roleManager.initialize(safe); coboSafe.setRoleManager(address(roleManager)); // 3. Set authorizer BaseAuthorizer authorizer = BaseAuthorizer(factory.create("ArgusRootAuthorizer")); authorizer.initialize(safe, address(coboSafe), address(coboSafe)); coboSafe.setAuthorizer(address(authorizer)); emit ArgusInitialized(address(coboSafe), safe, address(factory)); } function grantRoles(address coboSafeAddress, bytes32[] calldata roles, address[] calldata delegates) external { // 1. Add delegates to CoboSafe. CoboSafeAccount coboSafe = CoboSafeAccount(payable(coboSafeAddress)); coboSafe.addDelegates(delegates); // 2. Grant role/delegate in roleManager. FlatRoleManager roleManager = FlatRoleManager(coboSafe.roleManager()); roleManager.grantRoles(roles, delegates); } function revokeRoles(address coboSafeAddress, bytes32[] calldata roles, address[] calldata delegates) external { // 1. Revoke role/delegate for roleManager. CoboSafeAccount coboSafe = CoboSafeAccount(payable(coboSafeAddress)); FlatRoleManager roleManager = FlatRoleManager(coboSafe.roleManager()); roleManager.revokeRoles(roles, delegates); } function createAuthorizer( CoboFactory factory, address coboSafeAddress, bytes32 authorizerName, bytes32 tag ) public returns (address) { address safe = address(this); // 1. Get ArgusRootAuthorizer. CoboSafeAccount coboSafe = CoboSafeAccount(payable(coboSafeAddress)); ArgusRootAuthorizer rootAuthorizer = ArgusRootAuthorizer(coboSafe.authorizer()); // 2. Create authorizer and add to root authorizer set BaseAuthorizer authorizer = BaseAuthorizer(factory.create2(authorizerName, tag)); authorizer.initialize(safe, address(rootAuthorizer)); authorizer.setTag(tag); return address(authorizer); } function addAuthorizer( address coboSafeAddress, address authorizerAddress, bool isDelegateCall, bytes32[] calldata roles ) public { // 1. Get ArgusRootAuthorizer. CoboSafeAccount coboSafe = CoboSafeAccount(payable(coboSafeAddress)); ArgusRootAuthorizer rootAuthorizer = ArgusRootAuthorizer(coboSafe.authorizer()); // 2. Add authorizer to root authorizer set for (uint256 i = 0; i < roles.length; i++) { rootAuthorizer.addAuthorizer(isDelegateCall, roles[i], authorizerAddress); } } function removeAuthorizer( address coboSafeAddress, address authorizerAddress, bool isDelegateCall, bytes32[] calldata roles ) external { // 1. Get ArgusRootAuthorizer. CoboSafeAccount coboSafe = CoboSafeAccount(payable(coboSafeAddress)); ArgusRootAuthorizer rootAuthorizer = ArgusRootAuthorizer(coboSafe.authorizer()); // 2. Remove authorizer from root authorizer set for (uint256 i = 0; i < roles.length; i++) { rootAuthorizer.removeAuthorizer(isDelegateCall, roles[i], authorizerAddress); } } function addFuncAuthorizer( CoboFactory factory, address coboSafeAddress, bool isDelegateCall, bytes32[] calldata roles, address[] calldata _contracts, string[][] calldata funcLists, bytes32 tag ) external { // 1. create FuncAuthorizer address authorizerAddress = createAuthorizer(factory, coboSafeAddress, "FuncAuthorizer", tag); // 2. Set params setFuncAuthorizerParams(authorizerAddress, _contracts, funcLists); // 3. Add authorizer to root authorizer set addAuthorizer(coboSafeAddress, authorizerAddress, isDelegateCall, roles); } function addTransferAuthorizer( CoboFactory factory, address coboSafeAddress, bool isDelegateCall, bytes32[] calldata roles, TransferAuthorizer.TokenReceiver[] calldata tokenReceivers, bytes32 tag ) external { // 1. create TransferAuthorizer address authorizerAddress = createAuthorizer(factory, coboSafeAddress, "TransferAuthorizer", tag); // 2. Set params setTransferAuthorizerParams(authorizerAddress, tokenReceivers); // 3. Add authorizer to root authorizer set addAuthorizer(coboSafeAddress, authorizerAddress, isDelegateCall, roles); } function addDexAuthorizer( CoboFactory factory, address coboSafeAddress, bytes32 dexAuthorizerName, bool isDelegateCall, bytes32[] calldata roles, address[] calldata _swapInTokens, address[] calldata _swapOutTokens, bytes32 tag ) external { // 1. create DexAuthorizer address authorizerAddress = createAuthorizer(factory, coboSafeAddress, dexAuthorizerName, tag); // 2. Set params setDexAuthorizerParams(authorizerAddress, _swapInTokens, _swapOutTokens); // 3. Add authorizer to root authorizer set addAuthorizer(coboSafeAddress, authorizerAddress, isDelegateCall, roles); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; interface IVersion { function NAME() external view returns (bytes32 name); function VERSION() external view returns (uint256 version); }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "@openzeppelin/contracts/proxy/Clones.sol"; import "./base/BaseOwnable.sol"; /// @title CoboFactory - A contract factory referenced by bytes32 name. /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @notice Mostly used to manage proxy logic contract. But also ok to manage non-proxy contracts. /// @dev Contracts to add should extend IVersion interface and implement the `NAME()` function. contract CoboFactory is BaseOwnable { bytes32 public constant NAME = "CoboFactory"; uint256 public constant VERSION = 1; bytes32[] public names; // The last one added. mapping(bytes32 => address) public latestImplementations; // Name => All added contracts. mapping(bytes32 => address[]) public implementations; // deployer => name => proxy contract list // This is expensive. Query ProxyCreated event in SubGraph is a better solution. mapping(address => mapping(bytes32 => address[])) public records; event ProxyCreated(address indexed deployer, bytes32 indexed name, address indexed implementation, address proxy); event ImplementationAdded(bytes32 indexed name, address indexed implementation); constructor(address _owner) BaseOwnable(_owner) {} function _getLatestImplStrict(bytes32 name) internal view returns (address impl) { impl = getLatestImplementation(name); require(impl != address(0), "No implementation"); } /// View functions. function getLatestImplementation(bytes32 name) public view returns (address impl) { impl = latestImplementations[name]; } function getAllImplementations(bytes32 name) external view returns (address[] memory impls) { impls = implementations[name]; } function getAllNames() external view returns (bytes32[] memory _names) { _names = names; } /// @dev For etherscan view. function getNameString(uint i) public view returns (string memory _name) { _name = string(abi.encodePacked(names[i])); } function getAllNameStrings() external view returns (string[] memory _names) { _names = new string[](names.length); for (uint i = 0; i < names.length; ++i) { _names[i] = getNameString(i); } } function getLastRecord(address deployer, bytes32 name) external view returns (address proxy) { address[] storage record = records[deployer][name]; if (record.length == 0) return address(0); proxy = record[record.length - 1]; } function getRecordSize(address deployer, bytes32 name) external view returns (uint256 size) { address[] storage record = records[deployer][name]; size = record.length; } function getAllRecord(address deployer, bytes32 name) external view returns (address[] memory proxies) { return records[deployer][name]; } function getRecords( address deployer, bytes32 name, uint256 start, uint256 end ) external view returns (address[] memory proxies) { address[] storage record = records[deployer][name]; uint256 size = record.length; if (end > size) end = size; require(end > start, "end > start"); proxies = new address[](end - start); for (uint i = start; i < end; ++i) { proxies[i - start] = record[i]; } } function getCreate2Address(address creator, bytes32 name, bytes32 salt) external view returns (address instance) { address implementation = getLatestImplementation(name); if (implementation == address(0)) return address(0); salt = keccak256(abi.encode(creator, salt)); return Clones.predictDeterministicAddress(implementation, salt); } /// External functions. /// @dev Create EIP 1167 proxy. function create(bytes32 name) public returns (address instance) { address implementation = _getLatestImplStrict(name); instance = Clones.clone(implementation); emit ProxyCreated(msg.sender, name, implementation, instance); } /// @dev Create EIP 1167 proxy with create2. function create2(bytes32 name, bytes32 salt) public returns (address instance) { address implementation = _getLatestImplStrict(name); // Add msg.sender to the salt so no address collissions will occur between different users. salt = keccak256(abi.encode(msg.sender, salt)); instance = Clones.cloneDeterministic(implementation, salt); emit ProxyCreated(msg.sender, name, implementation, instance); } /// @notice Create and record the creation in the contract. function createAndRecord(bytes32 name) external returns (address instance) { instance = create(name); records[msg.sender][name].push(instance); } function create2AndRecord(bytes32 name, bytes32 salt) public returns (address instance) { instance = create2(name, salt); records[msg.sender][name].push(instance); } /// @notice Register a logic contract to the factory. Only the owner is allowed. function addImplementation(address impl) external onlyOwner { bytes32 name = IVersion(impl).NAME(); // If new name found, add to `names`. if (latestImplementations[name] == address(0)) { names.push(name); } latestImplementations[name] = impl; implementations[name].push(impl); emit ImplementationAdded(name, impl); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "../CoboSafeAccount.sol"; import "../role/FlatRoleManager.sol"; import "../auth/ArgusRootAuthorizer.sol"; import "../auth/FuncAuthorizer.sol"; import "../auth/TransferAuthorizer.sol"; import "../auth/DEXBaseACL.sol"; abstract contract ArgusAuthorizerHelper { function setFuncAuthorizerParams( address authorizerAddress, address[] calldata _contracts, string[][] calldata funcLists ) public { if (_contracts.length == 0) return; require(_contracts.length == funcLists.length, "Length differs"); FuncAuthorizer authorizer = FuncAuthorizer(authorizerAddress); for (uint i = 0; i < _contracts.length; i++) { authorizer.addContractFuncs(_contracts[i], funcLists[i]); } } function unsetFuncAuthorizerParams( address authorizerAddress, address[] calldata _contracts, string[][] calldata funcLists ) external { if (_contracts.length == 0) return; require(_contracts.length == funcLists.length, "Length differs"); FuncAuthorizer authorizer = FuncAuthorizer(authorizerAddress); for (uint i = 0; i < _contracts.length; i++) { authorizer.removeContractFuncs(_contracts[i], funcLists[i]); } } function setTransferAuthorizerParams( address authorizerAddress, TransferAuthorizer.TokenReceiver[] calldata tokenReceivers ) public { if (tokenReceivers.length == 0) return; TransferAuthorizer authorizer = TransferAuthorizer(authorizerAddress); authorizer.addTokenReceivers(tokenReceivers); } function unsetTransferAuthorizerParams( address authorizerAddress, TransferAuthorizer.TokenReceiver[] calldata tokenReceivers ) external { if (tokenReceivers.length == 0) return; TransferAuthorizer authorizer = TransferAuthorizer(authorizerAddress); authorizer.removeTokenReceivers(tokenReceivers); } function setDexAuthorizerParams( address authorizerAddress, address[] calldata _swapInTokens, address[] calldata _swapOutTokens ) public { DEXBaseACL authorizer = DEXBaseACL(authorizerAddress); if (_swapInTokens.length > 0) { authorizer.addSwapInTokens(_swapInTokens); } if (_swapOutTokens.length > 0) { authorizer.addSwapOutTokens(_swapOutTokens); } } function unsetDexAuthorizerParams( address authorizerAddress, address[] calldata _swapInTokens, address[] calldata _swapOutTokens ) external { DEXBaseACL authorizer = DEXBaseACL(authorizerAddress); if (_swapInTokens.length > 0) { authorizer.removeSwapInTokens(_swapInTokens); } if (_swapOutTokens.length > 0) { authorizer.removeSwapOutTokens(_swapOutTokens); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/Clones.sol) pragma solidity ^0.8.20; /** * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for * deploying minimal proxy contracts, also known as "clones". * * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies * > a minimal bytecode implementation that delegates all calls to a known, fixed address. * * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the * deterministic method. */ library Clones { /** * @dev A clone instance deployment failed. */ error ERC1167FailedCreateClone(); /** * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. * * This function uses the create opcode, which should never revert. */ function clone(address implementation) internal returns (address instance) { /// @solidity memory-safe-assembly assembly { // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes // of the `implementation` address with the bytecode before the address. mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) instance := create(0, 0x09, 0x37) } if (instance == address(0)) { revert ERC1167FailedCreateClone(); } } /** * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. * * This function uses the create2 opcode and a `salt` to deterministically deploy * the clone. Using the same `implementation` and `salt` multiple time will revert, since * the clones cannot be deployed twice at the same address. */ function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) { /// @solidity memory-safe-assembly assembly { // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes // of the `implementation` address with the bytecode before the address. mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) instance := create2(0, 0x09, 0x37, salt) } if (instance == address(0)) { revert ERC1167FailedCreateClone(); } } /** * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. */ function predictDeterministicAddress( address implementation, bytes32 salt, address deployer ) internal pure returns (address predicted) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(add(ptr, 0x38), deployer) mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) mstore(add(ptr, 0x14), implementation) mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73) mstore(add(ptr, 0x58), salt) mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37)) predicted := keccak256(add(ptr, 0x43), 0x55) } } /** * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. */ function predictDeterministicAddress( address implementation, bytes32 salt ) internal view returns (address predicted) { return predictDeterministicAddress(implementation, salt, address(this)); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "../Errors.sol"; import "./BaseVersion.sol"; /// @title BaseOwnable - Simple ownership access control contract. /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @dev Can be used in both proxy and non-proxy mode. abstract contract BaseOwnable is BaseVersion { address public owner; address public pendingOwner; bool private initialized = false; event PendingOwnerSet(address indexed to); event NewOwnerSet(address indexed owner); modifier onlyOwner() { require(owner == msg.sender, Errors.CALLER_IS_NOT_OWNER); _; } /// @dev `owner` is set by argument, thus the owner can any address. /// When used in non-proxy mode, `initialize` can not be called /// after deployment. constructor(address _owner) { initialize(_owner); } /// @dev When used in proxy mode, `initialize` can be called by anyone /// to claim the ownership. /// This function can be called only once. function initialize(address _owner) public { require(!initialized, "Already initialized"); _setOwner(_owner); initialized = true; } /// @notice User should ensure the corrent owner address set, or the /// ownership may be transferred to blackhole. It is recommended to /// take a safer way with setPendingOwner() + acceptOwner(). function transferOwnership(address newOwner) external onlyOwner { require(newOwner != address(0), "New Owner is zero"); _setOwner(newOwner); } /// @notice The original owner calls `setPendingOwner(newOwner)` and the new /// owner calls `acceptOwner()` to take the ownership. function setPendingOwner(address to) external onlyOwner { pendingOwner = to; emit PendingOwnerSet(pendingOwner); } function acceptOwner() external { require(msg.sender == pendingOwner); _setOwner(pendingOwner); } /// @notice Make the contract immutable. function renounceOwnership() external onlyOwner { _setOwner(address(0)); } // Internal functions /// @dev Clear pendingOwner to prevent from reclaiming the ownership. function _setOwner(address _owner) internal { owner = _owner; pendingOwner = address(0); emit NewOwnerSet(owner); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "./base/BaseAccount.sol"; contract Enum { enum Operation { Call, DelegateCall } } interface IGnosisSafe { /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data function execTransactionFromModuleReturnData( address to, uint256 value, bytes memory data, Enum.Operation operation ) external returns (bool success, bytes memory returnData); function enableModule(address module) external; function isModuleEnabled(address module) external view returns (bool); } /// @title CoboSafeAccount - A GnosisSafe module that implements customized access control /// @author Cobo Safe Dev Team https://www.cobo.com/ contract CoboSafeAccount is BaseAccount { using TxFlags for uint256; bytes32 public constant NAME = "CoboSafeAccount"; uint256 public constant VERSION = 2; constructor(address _owner) BaseAccount(_owner) {} /// @notice The Safe of the CoboSafeAccount. function safe() public view returns (address) { return owner; } /// @dev Execute the transaction from the Safe. function _executeTransaction( TransactionData memory transaction ) internal override returns (TransactionResult memory result) { // execute the transaction from Gnosis Safe, note this call will bypass // Safe owners confirmation. (result.success, result.data) = IGnosisSafe(payable(safe())).execTransactionFromModuleReturnData( transaction.to, transaction.value, transaction.data, transaction.flag.isDelegateCall() ? Enum.Operation.DelegateCall : Enum.Operation.Call ); } /// @dev The account address is the Safe address. function _getAccountAddress() internal view override returns (address account) { account = safe(); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../../interfaces/IRoleManager.sol"; import "../base/BaseOwnable.sol"; /// @title TransferAuthorizer - Manages delegate-role mapping. /// @author Cobo Safe Dev Team https://www.cobo.com/ contract FlatRoleManager is IFlatRoleManager, BaseOwnable { bytes32 public constant NAME = "FlatRoleManager"; uint256 public constant VERSION = 1; using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.AddressSet; event DelegateAdded(address indexed delegate, address indexed sender); event DelegateRemoved(address indexed delegate, address indexed sender); event RoleAdded(bytes32 indexed role, address indexed sender); event RoleGranted(bytes32 indexed role, address indexed delegate, address indexed sender); event RoleRevoked(bytes32 indexed role, address indexed delegate, address indexed sender); EnumerableSet.AddressSet delegates; EnumerableSet.Bytes32Set roles; /// @dev mapping from `delegate` address => `role` set; mapping(address => EnumerableSet.Bytes32Set) delegateToRoles; constructor(address _owner) BaseOwnable(_owner) {} /// @notice Add new roles without delegates assigned. function addRoles(bytes32[] calldata _roles) external onlyOwner { for (uint256 i = 0; i < _roles.length; i++) { if (roles.add(_roles[i])) { emit RoleAdded(_roles[i], msg.sender); } } } /// @notice Grant roles to delegates. Roles and delegates should be one-to-one. function grantRoles(bytes32[] calldata _roles, address[] calldata _delegates) external onlyOwner { require( _roles.length > 0 && _roles.length == _delegates.length, "FlatRoleManager: Invalid _roles or _delegates" ); for (uint256 i = 0; i < _roles.length; i++) { if (!delegateToRoles[_delegates[i]].add(_roles[i])) { // If already bound, skip. continue; } // In case when role is not added. if (roles.add(_roles[i])) { // Only fired when new one is added. emit RoleAdded(_roles[i], msg.sender); } // Emit `DelegateAdded` before `RoleGranted` to allow // subgraph event handler to process in sensible order. if (delegates.add(_delegates[i])) { emit DelegateAdded(_delegates[i], msg.sender); } emit RoleGranted(_roles[i], _delegates[i], msg.sender); } } /// @notice Revoke roles from delegates. Roles and delegates should be one-to-one. function revokeRoles(bytes32[] calldata _roles, address[] calldata _delegates) external onlyOwner { require( _roles.length > 0 && _roles.length == _delegates.length, "FlatRoleManager: Invalid _roles or _delegates" ); for (uint256 i = 0; i < _roles.length; i++) { if (!delegateToRoles[_delegates[i]].remove(_roles[i])) { continue; } // Ensure `RoleRevoked` is fired before `DelegateRemoved` // so that the event handlers in subgraphs are triggered in the // right order. emit RoleRevoked(_roles[i], _delegates[i], msg.sender); if (delegateToRoles[_delegates[i]].length() == 0) { delegates.remove(_delegates[i]); emit DelegateRemoved(_delegates[i], msg.sender); } } } /// @notice Get all the roles owned by the delegate function getRoles(address delegate) external view returns (bytes32[] memory) { return delegateToRoles[delegate].values(); } /// @notice Check if the delegate owns the role. function hasRole(address delegate, bytes32 role) external view returns (bool) { return delegateToRoles[delegate].contains(role); } /// @notice Get the entire delegates list in the account. function getDelegates() external view returns (address[] memory) { return delegates.values(); } /// @notice Get the entire roles list in the account. function getAllRoles() external view returns (bytes32[] memory) { return roles.values(); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../base/BaseAuthorizer.sol"; /// @title ArgusRootAuthorizer - Default root authorizers for Argus platform. /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @notice ArgusRootAuthorizer is a authorizer manager which dispatch the correct /// sub authorizer according to role of delegate and call type. /// Hint is supported here so user can get the hint, the correct authorizer /// in this case, off-chain (this can be expensive on-chain) and preform /// on-chain transaction to save gas. contract ArgusRootAuthorizer is BaseAuthorizer, IAuthorizerSupportingHint { using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; using TxFlags for uint256; using AuthFlags for uint256; bytes32 public constant NAME = "ArgusRootAuthorizer"; uint256 public constant VERSION = 1; bytes32 public constant override TYPE = AuthType.SET; /// @dev This changes when authorizer adds. uint256 private _unionFlag; // Roles in the authorizer. Only used for enumeration. EnumerableSet.Bytes32Set roles; // `isDelegateCall` => `Role` => `Authorizer address set` // true for delegatecall, false for call. mapping(bool => mapping(bytes32 => EnumerableSet.AddressSet)) internal authorizerSet; // Authorizers who implement process handler (with flag `HAS_POST_PROC_MASK` or `HAS_POST_PROC_MASK`) // will added into `processSet` and will be invoked unconditionally at each tx. mapping(bool => EnumerableSet.AddressSet) internal processSet; /// Events. event NewAuthorizerAdded(bool indexed isDelegateCall, bytes32 indexed role, address indexed authorizer); event NewProcessAdded(bool indexed isDelegateCall, address indexed authorizer); event AuthorizerRemoved(bool indexed isDelegateCall, bytes32 indexed role, address indexed authorizer); event ProcessRemoved(bool indexed isDelegateCall, address indexed authorizer); constructor(address _owner, address _caller, address _account) BaseAuthorizer(_owner, _caller) { // We need role manager. account = _account; } /// @dev pack/unpack should match. function _packHint(bytes32 role, address auth, bytes memory subHint) internal pure returns (bytes memory hint) { return abi.encodePacked(abi.encode(role, auth), subHint); } function _unpackHint(bytes calldata hint) internal pure returns (bytes32 role, address auth, bytes memory subHint) { (role, auth) = abi.decode(hint[0:64], (bytes32, address)); subHint = hint[64:]; } /// @dev Catch error of sub authorizers to prevent the case when one authorizer fails reverts the entire /// check chain process. function _safePreExecCheck( address auth, TransactionData calldata transaction ) internal returns (AuthorizerReturnData memory preData) { try IAuthorizer(auth).preExecCheck(transaction) returns (AuthorizerReturnData memory _preData) { return _preData; } catch Error(string memory reason) { preData.result = AuthResult.FAILED; preData.message = reason; } catch (bytes memory reason) { preData.result = AuthResult.FAILED; preData.message = string(reason); } } function _safePostExecCheck( address auth, TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData memory preData ) internal returns (AuthorizerReturnData memory postData) { try IAuthorizer(auth).postExecCheck(transaction, callResult, preData) returns ( AuthorizerReturnData memory _postData ) { return _postData; } catch Error(string memory reason) { postData.result = AuthResult.FAILED; postData.message = reason; } catch (bytes memory reason) { postData.result = AuthResult.FAILED; postData.message = string(reason); } } function _safeCollectHint( address auth, AuthorizerReturnData memory preData, AuthorizerReturnData memory postData ) internal returns (bytes memory subHint) { try IAuthorizerSupportingHint(auth).collectHint(preData, postData) returns (bytes memory _subHint) { return _subHint; } catch { return subHint; } } /// @dev preExecCheck and postExecCheck use extractly the same hint thus /// the same sub authorizer is called. function _preExecCheckWithHint( TransactionData calldata transaction ) internal returns (AuthorizerReturnData memory authData) { (bytes32 role, address auth, bytes memory subHint) = _unpackHint(transaction.hint); uint256 _flag = IAuthorizer(auth).flag(); // The authorizer from hint should have either PreCheck or PostCheck. require(_flag.isValid(), Errors.INVALID_AUTHORIZER_FLAG); if (!_flag.hasPreCheck()) { // If pre check handler not exist, default success. authData.result = AuthResult.SUCCESS; return authData; } // Important: Validate the hint. // (1) The role from hint should be validated. require(_hasRole(transaction, role), Errors.INVALID_HINT); // (2) The authorizer from hint should have been registered with the role. bool isDelegateCall = transaction.flag.isDelegateCall(); require(authorizerSet[isDelegateCall][role].contains(auth), Errors.INVALID_HINT); // Cut the hint to sub hint. TransactionData memory txn = transaction; txn.hint = subHint; // In hint path, this should never revert so `_safePreExecCheck()` is not used here. return IAuthorizer(auth).preExecCheck(txn); } function _postExecCheckWithHint( TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData calldata preData ) internal returns (AuthorizerReturnData memory authData) { (bytes32 role, address auth, bytes memory subHint) = _unpackHint(transaction.hint); uint256 _flag = IAuthorizer(auth).flag(); require(_flag.isValid(), Errors.INVALID_AUTHORIZER_FLAG); if (!_flag.hasPostCheck()) { // If post check handler not exist, default success. authData.result = AuthResult.SUCCESS; return authData; } // Important: Validate the hint. // (1) The role from hint should be validated. require(_hasRole(transaction, role), Errors.INVALID_HINT); // (2) The authorizer from hint should have been registered with the role. bool isDelegateCall = transaction.flag.isDelegateCall(); require(authorizerSet[isDelegateCall][role].contains(auth), Errors.INVALID_HINT); TransactionData memory txn = transaction; txn.hint = subHint; return IAuthorizer(auth).postExecCheck(txn, callResult, preData); } struct PreCheckData { bytes32 role; address authorizer; AuthorizerReturnData authData; } // This is very expensive on-chain. // Should only used to collect hint off-chain. PreCheckData[] internal preCheckDataCache; function _preExecCheck( TransactionData calldata transaction ) internal override returns (AuthorizerReturnData memory authData) { if (transaction.hint.length > 0) { return _preExecCheckWithHint(transaction); } authData.result = AuthResult.FAILED; bytes32[] memory txRoles = _getRoles(transaction); uint256 roleLength = txRoles.length; if (roleLength == 0) { authData.message = Errors.EMPTY_ROLE_SET; return authData; } bool isDelegateCall = transaction.flag.isDelegateCall(); for (uint256 i = 0; i < roleLength; ++i) { bytes32 role = txRoles[i]; EnumerableSet.AddressSet storage authSet = authorizerSet[isDelegateCall][role]; uint256 length = authSet.length(); // Run all pre checks and record auth results. for (uint256 j = 0; j < length; ++j) { address auth = authSet.at(j); AuthorizerReturnData memory preData = _safePreExecCheck(auth, transaction); if (preData.result == AuthResult.SUCCESS) { authData.result = AuthResult.SUCCESS; // Only save success results. preCheckDataCache.push(PreCheckData(role, auth, preData)); } } } if (authData.result == AuthResult.SUCCESS) { // Temporary data for post checker to collect hint. authData.data = abi.encode(preCheckDataCache); } else { authData.message = Errors.ALL_AUTH_FAILED; } delete preCheckDataCache; // gas refund. } function _postExecCheck( TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData calldata preData ) internal override returns (AuthorizerReturnData memory postData) { if (transaction.hint.length > 0) { return _postExecCheckWithHint(transaction, callResult, preData); } // Get pre check results from preData. PreCheckData[] memory preResults = abi.decode(preData.data, (PreCheckData[])); uint256 length = preResults.length; // We should have reverted in preExecCheck. But safer is better. require(length > 0, Errors.INVALID_HINT_COLLECTED); bool isDelegateCall = transaction.flag.isDelegateCall(); for (uint256 i = 0; i < length; ++i) { bytes32 role = preResults[i].role; address authAddress = preResults[i].authorizer; require(authorizerSet[isDelegateCall][role].contains(authAddress), Errors.INVALID_HINT_COLLECTED); // Run post check. AuthorizerReturnData memory preCheckData = preResults[i].authData; postData = _safePostExecCheck(authAddress, transaction, callResult, preCheckData); // If pre and post both succeeded, we pass. if (postData.result == AuthResult.SUCCESS) { // Collect hint of sub authorizer if needed. bytes memory subHint; if (IAuthorizer(authAddress).flag().supportHint()) { subHint = _safeCollectHint(authAddress, preCheckData, postData); } postData.data = _packHint(role, authAddress, subHint); return postData; } } postData.result = AuthResult.FAILED; postData.message = Errors.ALL_AUTH_FAILED; } function collectHint( AuthorizerReturnData calldata preAuthData, AuthorizerReturnData calldata postAuthData ) public view returns (bytes memory hint) { // Use post data as hint. hint = postAuthData.data; } /// @dev All sub preExecProcess / postExecProcess handlers are supposed be called. function _preExecProcess(TransactionData calldata transaction) internal virtual override { if (!_unionFlag.hasPreProcess()) return; bool isDelegateCall = transaction.flag.isDelegateCall(); EnumerableSet.AddressSet storage procSet = processSet[isDelegateCall]; uint256 length = procSet.length(); for (uint256 i = 0; i < length; i++) { IAuthorizer auth = IAuthorizer(procSet.at(i)); if (auth.flag().hasPreProcess()) { // Ignore reverts. try auth.preExecProcess(transaction) {} catch {} } } } function _postExecProcess( TransactionData calldata transaction, TransactionResult calldata callResult ) internal virtual override { if (!_unionFlag.hasPostProcess()) return; bool isDelegateCall = transaction.flag.isDelegateCall(); EnumerableSet.AddressSet storage procSet = processSet[isDelegateCall]; uint256 length = procSet.length(); for (uint256 i = 0; i < length; i++) { IAuthorizer auth = IAuthorizer(procSet.at(i)); if (auth.flag().hasPostProcess()) { // Ignore reverts. try auth.postExecProcess(transaction, callResult) {} catch {} } } } /// External / Public funtions. function addAuthorizer(bool isDelegateCall, bytes32 role, address authorizer) external onlyOwner { uint256 _flag = IAuthorizer(authorizer).flag(); roles.add(role); if (authorizerSet[isDelegateCall][role].add(authorizer)) { emit NewAuthorizerAdded(isDelegateCall, role, authorizer); // Collect flag. _unionFlag |= _flag; if (_flag.hasPreProcess() || _flag.hasPostProcess()) { // An authorizer with process handler can NOT be installed twice as this cause // confusion when running process handler twice in one transaction. require(processSet[isDelegateCall].add(authorizer), Errors.SAME_PROCESS_TWICE); emit NewProcessAdded(isDelegateCall, authorizer); } } } function removeAuthorizer(bool isDelegateCall, bytes32 role, address authorizer) external onlyOwner { uint256 _flag = IAuthorizer(authorizer).flag(); if (authorizerSet[isDelegateCall][role].remove(authorizer)) { emit AuthorizerRemoved(isDelegateCall, role, authorizer); if (_flag.hasPreProcess() || _flag.hasPostProcess()) { // It is ok to remove here as we has checked duplication in `addAuthorizer()`. if (processSet[isDelegateCall].remove(authorizer)) { emit ProcessRemoved(isDelegateCall, authorizer); if (processSet[isDelegateCall].length() == 0 && processSet[!isDelegateCall].length() == 0) { _unionFlag -= (_unionFlag & (AuthFlags.HAS_PRE_PROC_MASK | AuthFlags.HAS_POST_PROC_MASK)); } } } } } /// External view funtions. function flag() external view returns (uint256) { return _unionFlag | AuthFlags.SUPPORT_HINT_MASK; } function authorizerSize(bool isDelegateCall, bytes32 role) external view returns (uint256) { return authorizerSet[isDelegateCall][role].length(); } function hasAuthorizer(bool isDelegateCall, bytes32 role, address auth) external view returns (bool) { return authorizerSet[isDelegateCall][role].contains(auth); } function getAuthorizer(bool isDelegateCall, bytes32 role, uint256 i) external view returns (address) { return authorizerSet[isDelegateCall][role].at(i); } /// @dev View function allow user to specify the range in case we have very big set /// which can exhaust the gas of block limit when enumerating the entire list. function getAuthorizers( bool isDelegateCall, bytes32 role, uint256 start, uint256 end ) external view returns (address[] memory auths) { uint256 authorizerSetSize = authorizerSet[isDelegateCall][role].length(); if (end > authorizerSetSize) end = authorizerSetSize; auths = new address[](end - start); for (uint256 i = 0; i < end - start; i++) { auths[i] = authorizerSet[isDelegateCall][role].at(start + i); } } function getAllAuthorizers(bool isDelegateCall, bytes32 role) external view returns (address[] memory) { return authorizerSet[isDelegateCall][role].values(); } function getAllRoles() external view returns (bytes32[] memory) { return roles.values(); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../base/BaseAuthorizer.sol"; /// @title FuncAuthorizer - Manages contract, method pairs which can be accessed by delegates. /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @notice FuncAuthorizer only checks selector. Use ACL if function arguments check is needed. contract FuncAuthorizer is BaseAuthorizer { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.AddressSet; bytes32 public constant NAME = "FuncAuthorizer"; uint256 public constant VERSION = 1; uint256 public constant flag = AuthFlags.HAS_PRE_CHECK_MASK; bytes32 public constant override TYPE = AuthType.FUNC; /// @dev Tracks the set of contract address. EnumerableSet.AddressSet contractSet; /// @dev `contract address` => `function selectors` mapping(address => EnumerableSet.Bytes32Set) allowContractToFuncs; /// Events event AddContractFunc(address indexed _contract, string func, address indexed sender); event AddContractFuncSig(address indexed _contract, bytes4 indexed funcSig, address indexed sender); event RemoveContractFunc(address indexed _contract, string func, address indexed sender); event RemoveContractFuncSig(address indexed _contract, bytes4 indexed funcSig, address indexed sender); constructor(address _owner, address _caller) BaseAuthorizer(_owner, _caller) {} function _preExecCheck( TransactionData calldata transaction ) internal view override returns (AuthorizerReturnData memory authData) { // If calldata size is less than a selector, deny it. // Use TransferAuthorizer to check ETH transfer. if (transaction.data.length < 4) { authData.result = AuthResult.FAILED; authData.message = "invalid data length"; return authData; } bytes4 selector = _getSelector(transaction.data); if (_isAllowedSelector(transaction.to, selector)) { authData.result = AuthResult.SUCCESS; } else { authData.result = AuthResult.FAILED; authData.message = "function not allowed"; } } function _getSelector(bytes calldata data) internal pure returns (bytes4 selector) { assembly { selector := calldataload(data.offset) } } function _isAllowedSelector(address target, bytes4 selector) internal view returns (bool) { return allowContractToFuncs[target].contains(selector); } /// @dev Default success. function _postExecCheck( TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData calldata preData ) internal view override returns (AuthorizerReturnData memory authData) { authData.result = AuthResult.SUCCESS; } /// @notice Add contract and related function signature list. The function signature should be /// canonicalized removing argument names and blanks chars. /// ref: https://docs.soliditylang.org/en/v0.8.19/abi-spec.html#function-selector /// @dev keccak256 hash is calcuated and only 4 bytes selector is stored to reduce storage usage. function addContractFuncs(address _contract, string[] calldata funcList) external onlyOwner { require(funcList.length > 0, "empty funcList"); for (uint256 index = 0; index < funcList.length; index++) { bytes4 funcSelector = bytes4(keccak256(bytes(funcList[index]))); bytes32 funcSelector32 = bytes32(funcSelector); if (allowContractToFuncs[_contract].add(funcSelector32)) { emit AddContractFunc(_contract, funcList[index], msg.sender); emit AddContractFuncSig(_contract, funcSelector, msg.sender); } } contractSet.add(_contract); } /// @notice Remove contract and its function signature list from access list. function removeContractFuncs(address _contract, string[] calldata funcList) external onlyOwner { require(funcList.length > 0, "empty funcList"); for (uint256 index = 0; index < funcList.length; index++) { bytes4 funcSelector = bytes4(keccak256(bytes(funcList[index]))); bytes32 funcSelector32 = bytes32(funcSelector); if (allowContractToFuncs[_contract].remove(funcSelector32)) { emit RemoveContractFunc(_contract, funcList[index], msg.sender); emit RemoveContractFuncSig(_contract, funcSelector, msg.sender); } } if (allowContractToFuncs[_contract].length() == 0) { contractSet.remove(_contract); } } /// @notice Similar to `addContractFuncs()` but bytes4 selector is used. /// @dev keccak256 hash should be performed off-chain. function addContractFuncsSig(address _contract, bytes4[] calldata funcSigList) external onlyOwner { require(funcSigList.length > 0, "empty funcList"); for (uint256 index = 0; index < funcSigList.length; index++) { bytes32 funcSelector32 = bytes32(funcSigList[index]); if (allowContractToFuncs[_contract].add(funcSelector32)) { emit AddContractFuncSig(_contract, funcSigList[index], msg.sender); } } contractSet.add(_contract); } /// @notice Remove contract and its function selector list from access list. function removeContractFuncsSig(address _contract, bytes4[] calldata funcSigList) external onlyOwner { require(funcSigList.length > 0, "empty funcList"); for (uint256 index = 0; index < funcSigList.length; index++) { bytes32 funcSelector32 = bytes32(funcSigList[index]); if (allowContractToFuncs[_contract].remove(funcSelector32)) { emit RemoveContractFuncSig(_contract, funcSigList[index], msg.sender); } } if (allowContractToFuncs[_contract].length() == 0) { contractSet.remove(_contract); } } /// @notice Get all the contracts ever associated with any role /// @return list of contract addresses function getAllContracts() public view returns (address[] memory) { return contractSet.values(); } /// @notice Given a contract, list all the function selectors of this contract associated with a role /// @param _contract the contract /// @return list of function selectors in the contract ever associated with a role function getFuncsByContract(address _contract) public view returns (bytes32[] memory) { return allowContractToFuncs[_contract].values(); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../base/BaseACL.sol"; /// @title TransferAuthorizer - Manages ERC20/ETH transfer permissons. /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @notice This checks token-receiver pairs, no amount is restricted. contract TransferAuthorizer is BaseAuthorizer { bytes32 public constant NAME = "TransferAuthorizer"; uint256 public constant VERSION = 2; address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; bytes32 public constant override TYPE = AuthType.TRANSFER; uint256 public constant flag = AuthFlags.HAS_PRE_CHECK_MASK; // function transfer(address recipient, uint256 amount) bytes4 constant TRANSFER_SELECTOR = 0xa9059cbb; using EnumerableSet for EnumerableSet.AddressSet; EnumerableSet.AddressSet tokenSet; mapping(address => EnumerableSet.AddressSet) tokenToReceivers; event TokenAdded(address indexed token); event TokenRemoved(address indexed token); event TokenReceiverAdded(address indexed token, address indexed receiver); event TokenReceiverRemoved(address indexed token, address indexed receiver); struct TokenReceiver { address token; address receiver; } constructor(address _owner, address _caller) BaseAuthorizer(_owner, _caller) {} /// @notice Add token-receiver pairs. Use 0xee..ee for native ETH. function addTokenReceivers(TokenReceiver[] calldata tokenReceivers) external onlyOwner { for (uint i = 0; i < tokenReceivers.length; i++) { address token = tokenReceivers[i].token; address receiver = tokenReceivers[i].receiver; if (tokenSet.add(token)) { emit TokenAdded(token); } if (tokenToReceivers[token].add(receiver)) { emit TokenReceiverAdded(token, receiver); } } } function removeTokenReceivers(TokenReceiver[] calldata tokenReceivers) external onlyOwner { for (uint i = 0; i < tokenReceivers.length; i++) { address token = tokenReceivers[i].token; address receiver = tokenReceivers[i].receiver; if (tokenToReceivers[token].remove(receiver)) { emit TokenReceiverRemoved(token, receiver); if (tokenToReceivers[tokenReceivers[i].token].length() == 0) { if (tokenSet.remove(token)) { emit TokenRemoved(token); } } } } } // View functions. function getAllToken() external view returns (address[] memory) { return tokenSet.values(); } /// @dev View function allow user to specify the range in case we have very big token set /// which can exhaust the gas of block limit. function getTokens(uint256 start, uint256 end) external view returns (address[] memory) { uint256 size = tokenSet.length(); if (end > size) end = size; require(start < end, "start >= end"); address[] memory _tokens = new address[](end - start); for (uint i = 0; i < end - start; i++) { _tokens[i] = tokenSet.at(start + i); } return _tokens; } function getTokenReceivers(address token) external view returns (address[] memory) { return tokenToReceivers[token].values(); } function _preExecCheck( TransactionData calldata transaction ) internal virtual override returns (AuthorizerReturnData memory authData) { if ( transaction.data.length >= 68 && // 4 + 32 + 32 bytes4(transaction.data[0:4]) == TRANSFER_SELECTOR && transaction.value == 0 ) { // ETH transfer not allowed and token in white list. (address recipient /*uint256 amount*/, ) = abi.decode(transaction.data[4:], (address, uint256)); address token = transaction.to; if (tokenToReceivers[token].contains(recipient)) { authData.result = AuthResult.SUCCESS; return authData; } } else if (transaction.data.length == 0 && transaction.value > 0) { // Contract call not allowed and token in white list. address recipient = transaction.to; if (tokenToReceivers[ETH].contains(recipient)) { authData.result = AuthResult.SUCCESS; return authData; } } authData.result = AuthResult.FAILED; authData.message = "transfer not allowed"; } function _postExecCheck( TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData calldata preData ) internal virtual override returns (AuthorizerReturnData memory authData) { authData.result = AuthResult.SUCCESS; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../base/BaseACL.sol"; /// @title DEXBaseACL - ACL template for DEX. /// @author Cobo Safe Dev Team https://www.cobo.com/ abstract contract DEXBaseACL is BaseACL { using EnumerableSet for EnumerableSet.AddressSet; bytes32 public constant override TYPE = AuthType.DEX; EnumerableSet.AddressSet swapInTokenWhitelist; EnumerableSet.AddressSet swapOutTokenWhitelist; event SwapInTokenAdded(address indexed token); event SwapInTokenRemoved(address indexed token); event SwapOutTokenAdded(address indexed token); event SwapOutTokenRemoved(address indexed token); struct SwapInToken { address token; bool tokenStatus; } struct SwapOutToken { address token; bool tokenStatus; } constructor(address _owner, address _caller) BaseACL(_owner, _caller) {} // External set functions. function addSwapInTokens(address[] calldata _tokens) external onlyOwner { for (uint256 i = 0; i < _tokens.length; i++) { address token = _tokens[i]; if (swapInTokenWhitelist.add(token)) { emit SwapInTokenAdded(token); } } } function removeSwapInTokens(address[] calldata _tokens) external onlyOwner { for (uint256 i = 0; i < _tokens.length; i++) { address token = _tokens[i]; if (swapInTokenWhitelist.remove(token)) { emit SwapInTokenRemoved(token); } } } function addSwapOutTokens(address[] calldata _tokens) external onlyOwner { for (uint256 i = 0; i < _tokens.length; i++) { address token = _tokens[i]; if (swapOutTokenWhitelist.add(token)) { emit SwapOutTokenAdded(token); } } } function removeSwapOutTokens(address[] calldata _tokens) external onlyOwner { for (uint256 i = 0; i < _tokens.length; i++) { address token = _tokens[i]; if (swapOutTokenWhitelist.remove(token)) { emit SwapOutTokenRemoved(token); } } } // External view functions. function hasSwapInToken(address _token) public view returns (bool) { return swapInTokenWhitelist.contains(_token); } function getSwapInTokens() external view returns (address[] memory tokens) { return swapInTokenWhitelist.values(); } function hasSwapOutToken(address _token) public view returns (bool) { return swapOutTokenWhitelist.contains(_token); } function getSwapOutTokens() external view returns (address[] memory tokens) { return swapOutTokenWhitelist.values(); } // Internal check utility functions. function _swapInTokenCheck(address _token) internal view { require(hasSwapInToken(_token), "In token not allowed"); } function _swapOutTokenCheck(address _token) internal view { require(hasSwapOutToken(_token), "Out token not allowed"); } function _swapInOutTokenCheck(address _inToken, address _outToken) internal view { _swapInTokenCheck(_inToken); _swapOutTokenCheck(_outToken); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; /// @dev Common errors. This helps reducing the contract size. library Errors { // "E1"; // Call/Static-call failed. string constant CALL_FAILED = "E2"; // Argument's type not supported in View Variant. string constant INVALID_VIEW_ARG_SOL_TYPE = "E3"; // Invalid length for variant raw data. string constant INVALID_VARIANT_RAW_DATA = "E4"; // "E5"; // Invalid variant type. string constant INVALID_VAR_TYPE = "E6"; // Rule not exists string constant RULE_NOT_EXISTS = "E7"; // Variant name not found. string constant VAR_NAME_NOT_FOUND = "E8"; // Rule: v1/v2 solType mismatch string constant SOL_TYPE_MISMATCH = "E9"; // "E10"; // Invalid rule OP. string constant INVALID_RULE_OP = "E11"; // "E12"; // "E13"; // "E14"; // "E15"; // "E16"; // "E17"; // "E18"; // "E19"; // "E20"; // checkCmpOp: OP not support string constant CMP_OP_NOT_SUPPORT = "E21"; // checkBySolType: Invalid op for bool string constant INVALID_BOOL_OP = "E22"; // checkBySolType: Invalid op string constant CHECK_INVALID_OP = "E23"; // Invalid solidity type. string constant INVALID_SOL_TYPE = "E24"; // computeBySolType: invalid vm op string constant INVALID_VM_BOOL_OP = "E25"; // computeBySolType: invalid vm arith op string constant INVALID_VM_ARITH_OP = "E26"; // onlyCaller: Invalid caller string constant INVALID_CALLER = "E27"; // "E28"; // Side-effect is not allowed here. string constant SIDE_EFFECT_NOT_ALLOWED = "E29"; // Invalid variant count for the rule op. string constant INVALID_VAR_COUNT = "E30"; // extractCallData: Invalid op. string constant INVALID_EXTRACTOR_OP = "E31"; // extractCallData: Invalid array index. string constant INVALID_ARRAY_INDEX = "E32"; // extractCallData: No extract op. string constant NO_EXTRACT_OP = "E33"; // extractCallData: No extract path. string constant NO_EXTRACT_PATH = "E34"; // BaseOwnable: caller is not owner string constant CALLER_IS_NOT_OWNER = "E35"; // BaseOwnable: Already initialized string constant ALREADY_INITIALIZED = "E36"; // "E37"; // "E38"; // BaseACL: ACL check method should not return anything. string constant ACL_FUNC_RETURNS_NON_EMPTY = "E39"; // "E40"; // BaseAccount: Invalid delegate. string constant INVALID_DELEGATE = "E41"; // RootAuthorizer: delegateCallAuthorizer not set string constant DELEGATE_CALL_AUTH_NOT_SET = "E42"; // RootAuthorizer: callAuthorizer not set. string constant CALL_AUTH_NOT_SET = "E43"; // BaseAccount: Authorizer not set. string constant AUTHORIZER_NOT_SET = "E44"; // BaseAccount: Invalid authorizer flag. string constant INVALID_AUTHORIZER_FLAG = "E45"; // BaseAuthorizer: Authorizer paused. string constant AUTHORIZER_PAUSED = "E46"; // Authorizer set: Invalid hint. string constant INVALID_HINT = "E47"; // Authorizer set: All auth deny. string constant ALL_AUTH_FAILED = "E48"; // BaseACL: Method not allow. string constant METHOD_NOT_ALLOW = "E49"; // AuthorizerUnionSet: Invalid hint collected. string constant INVALID_HINT_COLLECTED = "E50"; // AuthorizerSet: Empty auth set string constant EMPTY_AUTH_SET = "E51"; // AuthorizerSet: hint not implement. string constant HINT_NOT_IMPLEMENT = "E52"; // RoleAuthorizer: Empty role set string constant EMPTY_ROLE_SET = "E53"; // RoleAuthorizer: No auth for the role string constant NO_AUTH_FOR_THE_ROLE = "E54"; // BaseACL: No in contract white list. string constant NOT_IN_CONTRACT_LIST = "E55"; // BaseACL: Same process not allowed to install twice. string constant SAME_PROCESS_TWICE = "E56"; // BaseAuthorizer: Account not set (then can not find roleManger) string constant ACCOUNT_NOT_SET = "E57"; // BaseAuthorizer: roleManger not set string constant ROLE_MANAGER_NOT_SET = "E58"; }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "../../interfaces/IVersion.sol"; /// @title BaseVersion - Provides version information /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @dev /// Implement NAME() and VERSION() methods according to IVersion interface. /// /// Or just: /// bytes32 public constant NAME = "<Your contract name>"; /// uint256 public constant VERSION = <Your contract version>; /// /// Change the NAME when writing new kind of contract. /// Change the VERSION when upgrading existing contract. abstract contract BaseVersion is IVersion { /// @dev Convert to `string` which looks prettier on Etherscan viewer. function _NAME() external view virtual returns (string memory) { return string(abi.encodePacked(this.NAME())); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../Types.sol"; import "./BaseOwnable.sol"; import "../../interfaces/IAuthorizer.sol"; import "../../interfaces/IAccount.sol"; /// @title BaseAccount - A basic smart contract wallet with access control supported. /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @dev Extend this and implement `_executeTransaction()` and `_getFromAddress()`. abstract contract BaseAccount is IAccount, BaseOwnable { using EnumerableSet for EnumerableSet.AddressSet; using TxFlags for uint256; using AuthFlags for uint256; address public roleManager; address public authorizer; // Simple and basic delegate check. EnumerableSet.AddressSet delegates; event RoleManagerSet(address indexed roleManager); event AuthorizerSet(address indexed authorizer); event DelegateAdded(address indexed delegate); event DelegateRemoved(address indexed delegate); event TransactionExecuted( address indexed to, bytes4 indexed selector, uint256 indexed value, TransactionData transaction ); /// @param _owner Who owns the wallet. constructor(address _owner) BaseOwnable(_owner) {} /// @dev Only used in proxy mode. Can be called only once. function initialize(address _owner, address _roleManager, address _authorizer) public { initialize(_owner); _setRoleManager(_roleManager); _setAuthorizer(_authorizer); } /// Modifiers /// @dev Only added delegates are allowed to call `execTransaction`. This provides a kind /// of catch-all rule and simple but strong protection from malicious/compromised/buggy /// authorizers which permit any operations. modifier onlyDelegate() { require(hasDelegate(msg.sender), Errors.INVALID_DELEGATE); _; } // Public/External functions. function setRoleManager(address _roleManager) external onlyOwner { _setRoleManager(_roleManager); } function setAuthorizer(address _authorizer) external onlyOwner { _setAuthorizer(_authorizer); } function addDelegate(address _delegate) external onlyOwner { _addDelegate(_delegate); } function addDelegates(address[] calldata _delegates) external onlyOwner { for (uint256 i = 0; i < _delegates.length; i++) { _addDelegate(_delegates[i]); } } function removeDelegate(address _delegate) external onlyOwner { _removeDelegate(_delegate); } function removeDelegates(address[] calldata _delegates) external onlyOwner { for (uint256 i = 0; i < _delegates.length; i++) { _removeDelegate(_delegates[i]); } } /// @notice Called by authenticated delegates to execute transaction on behalf of the wallet account. function execTransaction( CallData calldata callData ) external onlyDelegate returns (TransactionResult memory result) { TransactionData memory transaction; transaction.from = _getAccountAddress(); transaction.delegate = msg.sender; transaction.flag = callData.flag; transaction.to = callData.to; transaction.value = callData.value; transaction.data = callData.data; transaction.hint = callData.hint; transaction.extra = callData.extra; result = _executeTransactionWithCheck(transaction); emit TransactionExecuted(callData.to, bytes4(callData.data), callData.value, transaction); } /// @notice A Multicall method. /// @param callDataList `CallData` array to execute in sequence. function execTransactions( CallData[] calldata callDataList ) external onlyDelegate returns (TransactionResult[] memory resultList) { TransactionData memory transaction; transaction.from = _getAccountAddress(); transaction.delegate = msg.sender; resultList = new TransactionResult[](callDataList.length); for (uint256 i = 0; i < callDataList.length; i++) { CallData calldata callData = callDataList[i]; transaction.to = callData.to; transaction.value = callData.value; transaction.data = callData.data; transaction.flag = callData.flag; transaction.hint = callData.hint; transaction.extra = callData.extra; resultList[i] = _executeTransactionWithCheck(transaction); emit TransactionExecuted(callData.to, bytes4(callData.data), callData.value, transaction); } } /// Public/External view functions. function hasDelegate(address _delegate) public view returns (bool) { return delegates.contains(_delegate); } function getAllDelegates() external view returns (address[] memory) { return delegates.values(); } /// @notice The real address of your smart contract wallet address where /// stores your assets and sends transactions from. function getAccountAddress() external view returns (address account) { account = _getAccountAddress(); } /// Internal functions. function _addDelegate(address _delegate) internal { if (delegates.add(_delegate)) { emit DelegateAdded(_delegate); } } function _removeDelegate(address _delegate) internal { if (delegates.remove(_delegate)) { emit DelegateRemoved(_delegate); } } function _setRoleManager(address _roleManager) internal { roleManager = _roleManager; emit RoleManagerSet(_roleManager); } function _setAuthorizer(address _authorizer) internal { authorizer = _authorizer; emit AuthorizerSet(_authorizer); } /// @dev Override this if we prefer not to revert the entire transaction in // out wallet contract implementation. function _preExecCheck( TransactionData memory transaction ) internal virtual returns (AuthorizerReturnData memory authData) { authData = IAuthorizer(authorizer).preExecCheck(transaction); require(authData.result == AuthResult.SUCCESS, authData.message); } function _revertIfTxFails(TransactionResult memory callResult) internal pure { bool success = callResult.success; bytes memory data = callResult.data; if (!success) { assembly { revert(add(data, 32), data) } } } function _postExecCheck( TransactionData memory transaction, TransactionResult memory callResult, AuthorizerReturnData memory predata ) internal virtual returns (AuthorizerReturnData memory authData) { authData = IAuthorizer(authorizer).postExecCheck(transaction, callResult, predata); require(authData.result == AuthResult.SUCCESS, authData.message); } function _preExecProcess(TransactionData memory transaction) internal virtual { IAuthorizer(authorizer).preExecProcess(transaction); } function _postExecProcess( TransactionData memory transaction, TransactionResult memory callResult ) internal virtual { IAuthorizer(authorizer).postExecProcess(transaction, callResult); } function _executeTransactionWithCheck( TransactionData memory transaction ) internal virtual returns (TransactionResult memory result) { require(authorizer != address(0), Errors.AUTHORIZER_NOT_SET); uint256 flag = IAuthorizer(authorizer).flag(); bool doCollectHint = transaction.hint.length == 0; // Ensures either _preExecCheck or _postExecCheck (or both) will run. require(flag.isValid(), Errors.INVALID_AUTHORIZER_FLAG); // 1. Do pre check, revert the entire txn if failed. AuthorizerReturnData memory preData; if (doCollectHint || flag.hasPreCheck()) { // Always run _preExecCheck When collecting hint. // If not collecting hint, only run if the sub authorizer requires. preData = _preExecCheck(transaction); } // 2. Do pre process. if (flag.hasPreProcess()) _preExecProcess(transaction); // 3. Execute the transaction. result = _executeTransaction(transaction); if (!transaction.flag.allowsRevert()) _revertIfTxFails(result); // 4. Do post check, revert the entire txn if failed. AuthorizerReturnData memory postData; if (doCollectHint || flag.hasPostCheck()) { postData = _postExecCheck(transaction, result, preData); } // 5. Do post process. if (flag.hasPostProcess()) _postExecProcess(transaction, result); // 6. Collect hint if when (1) no hint provided and (2) the authorizer supports hint mode. if (doCollectHint && flag.supportHint()) { result.hint = IAuthorizerSupportingHint(authorizer).collectHint(preData, postData); } } /// @dev Instance should implement at least two `virtual` function below. /// @param transaction Transaction to execute. /// @return result `TransactionResult` which contains call status and return/revert data. function _executeTransaction( TransactionData memory transaction ) internal virtual returns (TransactionResult memory result); /// @dev The address of wallet which sends the transaction a.k.a `msg.sender` function _getAccountAddress() internal view virtual returns (address account); // To receive ETH as a wallet. receive() external payable {} }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. pragma solidity ^0.8.20; /** * @dev Library for managing * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive * types. * * Sets have the following properties: * * - Elements are added, removed, and checked for existence in constant time * (O(1)). * - Elements are enumerated in O(n). No guarantees are made on the ordering. * * ```solidity * contract Example { * // Add the library methods * using EnumerableSet for EnumerableSet.AddressSet; * * // Declare a set state variable * EnumerableSet.AddressSet private mySet; * } * ``` * * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) * and `uint256` (`UintSet`) are supported. * * [WARNING] * ==== * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure * unusable. * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. * * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an * array of EnumerableSet. * ==== */ library EnumerableSet { // To implement this library for multiple types with as little code // repetition as possible, we write it in terms of a generic Set type with // bytes32 values. // The Set implementation uses private functions, and user-facing // implementations (such as AddressSet) are just wrappers around the // underlying Set. // This means that we can only create new EnumerableSets for types that fit // in bytes32. struct Set { // Storage of set values bytes32[] _values; // Position is the index of the value in the `values` array plus 1. // Position 0 is used to mean a value is not in the set. mapping(bytes32 value => uint256) _positions; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._positions[value] = set._values.length; return true; } else { return false; } } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function _remove(Set storage set, bytes32 value) private returns (bool) { // We cache the value's position to prevent multiple reads from the same storage slot uint256 position = set._positions[value]; if (position != 0) { // Equivalent to contains(set, value) // To delete an element from the _values array in O(1), we swap the element to delete with the last one in // the array, and then remove the last element (sometimes called as 'swap and pop'). // This modifies the order of the array, as noted in {at}. uint256 valueIndex = position - 1; uint256 lastIndex = set._values.length - 1; if (valueIndex != lastIndex) { bytes32 lastValue = set._values[lastIndex]; // Move the lastValue to the index where the value to delete is set._values[valueIndex] = lastValue; // Update the tracked position of the lastValue (that was just moved) set._positions[lastValue] = position; } // Delete the slot where the moved value was stored set._values.pop(); // Delete the tracked position for the deleted slot delete set._positions[value]; return true; } else { return false; } } /** * @dev Returns true if the value is in the set. O(1). */ function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._positions[value] != 0; } /** * @dev Returns the number of values on the set. O(1). */ function _length(Set storage set) private view returns (uint256) { return set._values.length; } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function _at(Set storage set, uint256 index) private view returns (bytes32) { return set._values[index]; } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function _values(Set storage set) private view returns (bytes32[] memory) { return set._values; } // Bytes32Set struct Bytes32Set { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _add(set._inner, value); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _remove(set._inner, value); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { return _contains(set._inner, value); } /** * @dev Returns the number of values in the set. O(1). */ function length(Bytes32Set storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { return _at(set._inner, index); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { bytes32[] memory store = _values(set._inner); bytes32[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // AddressSet struct AddressSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(AddressSet storage set, address value) internal returns (bool) { return _remove(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns the number of values in the set. O(1). */ function length(AddressSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint160(uint256(_at(set._inner, index)))); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(AddressSet storage set) internal view returns (address[] memory) { bytes32[] memory store = _values(set._inner); address[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // UintSet struct UintSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(UintSet storage set, uint256 value) internal returns (bool) { return _add(set._inner, bytes32(value)); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(UintSet storage set, uint256 value) internal returns (bool) { return _remove(set._inner, bytes32(value)); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } /** * @dev Returns the number of values in the set. O(1). */ function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(UintSet storage set) internal view returns (uint256[] memory) { bytes32[] memory store = _values(set._inner); uint256[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "../src/Types.sol"; interface IRoleManager { function getRoles(address delegate) external view returns (bytes32[] memory); function hasRole(address delegate, bytes32 role) external view returns (bool); } interface IFlatRoleManager is IRoleManager { function addRoles(bytes32[] calldata roles) external; function grantRoles(bytes32[] calldata roles, address[] calldata delegates) external; function revokeRoles(bytes32[] calldata roles, address[] calldata delegates) external; function getDelegates() external view returns (address[] memory); function getAllRoles() external view returns (bytes32[] memory); }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "./BaseOwnable.sol"; import "../Errors.sol"; import "../../interfaces/IAuthorizer.sol"; import "../../interfaces/IAccount.sol"; import "../../interfaces/IRoleManager.sol"; /// @title BaseAuthorizer - A basic pausable authorizer with caller restriction. /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @dev Base contract to extend to implement specific authorizer. abstract contract BaseAuthorizer is IAuthorizer, BaseOwnable { /// @dev Override such constants while extending BaseAuthorizer. bool public paused = false; // Often used for off-chain system. // Each contract instance has its own value. bytes32 public tag = ""; // The caller which is able to call this contract's pre/postExecProcess // and pre/postExecCheck having side-effect. // It is usually the account or the parent authorizer(set) on higher level. address public caller; // This is the account this authorizer works for. // Currently only used to lookup `roleManager`. // If not used it is OK to keep it unset. address public account; event CallerSet(address indexed caller); event AccountSet(address indexed account); event TagSet(bytes32 indexed tag); event PausedSet(bool indexed status); constructor(address _owner, address _caller) BaseOwnable(_owner) { caller = _caller; } function initialize(address _owner, address _caller) public { initialize(_owner); caller = _caller; emit CallerSet(_caller); } function initialize(address _owner, address _caller, address _account) public { initialize(_owner, _caller); account = _account; emit AccountSet(_account); } modifier onlyCaller() virtual { require(msg.sender == caller, Errors.INVALID_CALLER); _; } /// @notice Change the caller. /// @param _caller the caller which calls the authorizer. function setCaller(address _caller) external onlyOwner { require(_caller != address(0), "Invalid caller"); caller = _caller; emit CallerSet(_caller); } /// @notice Change the account. /// @param _account the account which the authorizer get role manager from. function setAccount(address _account) external onlyOwner { require(_account != address(0), "Invalid account"); account = _account; emit AccountSet(_account); } /// @notice Change the tag for the contract instance. /// @dev For off-chain index. /// @param _tag the tag function setTag(bytes32 _tag) external onlyOwner { tag = _tag; emit TagSet(_tag); } /// @notice Set the pause status. Authorizer just denies all when paused. /// @param _paused the paused status: true or false. function setPaused(bool _paused) external onlyOwner { paused = _paused; emit PausedSet(_paused); } /// @dev `onlyCaller` check is forced on pre/post Check/Process handlers /// to prevent attackers from polluting our data by calling this directly. /// @notice Check if the transaction can be executed. /// @return authData Return check status, error message and other data. function preExecCheck( TransactionData calldata transaction ) external virtual onlyCaller returns (AuthorizerReturnData memory authData) { if (paused) { authData.result = AuthResult.FAILED; authData.message = Errors.AUTHORIZER_PAUSED; } else { authData = _preExecCheck(transaction); } } /// @notice Check after transaction execution. /// @param callResult Transaction call status and return data. function postExecCheck( TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData calldata preData ) external virtual onlyCaller returns (AuthorizerReturnData memory authData) { if (paused) { authData.result = AuthResult.FAILED; authData.message = Errors.AUTHORIZER_PAUSED; } else { authData = _postExecCheck(transaction, callResult, preData); } } /// @dev Perform actions before the transaction execution. function preExecProcess(TransactionData calldata transaction) external virtual onlyCaller { if (!paused) _preExecProcess(transaction); } /// @dev Perform actions after the transaction execution. function postExecProcess( TransactionData calldata transaction, TransactionResult calldata callResult ) external virtual onlyCaller { if (!paused) _postExecProcess(transaction, callResult); } /// @dev Extract the roles of the delegate. If no roleManager set return empty lists. function _getRoleManager() internal view returns (address roleManager) { require(account != address(0), Errors.ACCOUNT_NOT_SET); roleManager = IAccount(account).roleManager(); require(roleManager != address(0), Errors.ROLE_MANAGER_NOT_SET); } function _getRoles(TransactionData calldata transaction) internal view returns (bytes32[] memory roles) { address roleManager = _getRoleManager(); roles = IRoleManager(roleManager).getRoles(transaction.delegate); } /// @dev Call `roleManager` to validate the role of delegate. function _hasRole(TransactionData calldata transaction, bytes32 role) internal view returns (bool) { address roleManager = _getRoleManager(); return IRoleManager(roleManager).hasRole(transaction.delegate, role); } /// @dev Override these functions to while extending this contract. function _preExecCheck( TransactionData calldata transaction ) internal virtual returns (AuthorizerReturnData memory authData) {} function _postExecCheck( TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData calldata preData ) internal virtual returns (AuthorizerReturnData memory) {} function _preExecProcess(TransactionData calldata transaction) internal virtual {} function _postExecProcess( TransactionData calldata transaction, TransactionResult calldata callResult ) internal virtual {} /// @dev Override this if you implement new type of authorizer. function TYPE() external view virtual returns (bytes32) { return AuthType.COMMON; } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "./BaseAuthorizer.sol"; /// @title BaseACL - Basic ACL template which uses the call-self trick to perform function and parameters check. /// @author Cobo Safe Dev Team https://www.cobo.com/ /// @dev Steps to extend this: /// 1. Set the NAME, VERSION, TYPE. /// 2. Write ACL functions according the target contract. /// 3. Add a constructor. eg: /// `constructor(address _owner, address _caller) BaseACL(_owner, _caller) {}` /// 4. Override `contracts()` to only target contracts that you checks. Transactions //// whose `to` address is not in the list will revert. /// 5. (Optional) If state changing operation in the checking method is required, /// override `_preExecCheck()` to change `staticcall` to `call`. /// /// NOTE for ACL developers: /// 1. The checking functions can be defined extractly the same as the target method /// to control thus developers do not bother to write a lot `abi.decode` code. /// 2. Checking funtions should NOT contain return value, use `require` to perform check. /// 3. BaseACL may serve for multiple target contracts. /// - Implement contracts() to manage the target contracts set. /// - Use `onlyContract` modifier or check `_txn().to` in checking functions. /// 4. `onlyOwner` modifier should be used for customized setter functions. abstract contract BaseACL is BaseAuthorizer { using EnumerableSet for EnumerableSet.Bytes32Set; /// @dev Set such constants in sub contract. // bytes32 public constant NAME = "BaseACL"; // bytes32 public constant override TYPE = "ACLType"; // uint256 public constant VERSION = 0; /// Only preExecCheck is used in BaseACL and hint is not supported. uint256 public constant flag = AuthFlags.HAS_PRE_CHECK_MASK; constructor(address _owner, address _caller) BaseAuthorizer(_owner, _caller) {} /// Internal functions. function _parseReturnData( bool success, bytes memory revertData ) internal pure returns (AuthorizerReturnData memory authData) { if (success) { // ACL checking functions should not return any bytes which differs from normal view functions. require(revertData.length == 0, Errors.ACL_FUNC_RETURNS_NON_EMPTY); authData.result = AuthResult.SUCCESS; } else { if (revertData.length < 68) { // 4(Error sig) + 32(offset) + 32(length) authData.message = string(revertData); } else { assembly { // Slice the sighash. revertData := add(revertData, 0x04) } authData.message = abi.decode(revertData, (string)); } } } function _contractCheck(TransactionData calldata transaction) internal virtual returns (bool result) { // This works as a catch-all check. Sample but safer. address to = transaction.to; address[] memory _contracts = contracts(); for (uint i = 0; i < _contracts.length; i++) { if (to == _contracts[i]) return true; } return false; } function _packTxn(TransactionData calldata transaction) internal pure virtual returns (bytes memory) { bytes memory txnData = abi.encode(transaction); bytes memory callDataSize = abi.encode(transaction.data.length); return abi.encodePacked(transaction.data, txnData, callDataSize); } function _unpackTxn() internal pure virtual returns (TransactionData memory transaction) { uint256 end = msg.data.length; uint256 callDataSize = abi.decode(msg.data[end - 32:end], (uint256)); transaction = abi.decode(msg.data[callDataSize:], (TransactionData)); } // @dev Only valid in self-call checking functions. function _txn() internal pure virtual returns (TransactionData memory transaction) { return _unpackTxn(); } function _preExecCheck( TransactionData calldata transaction ) internal virtual override returns (AuthorizerReturnData memory authData) { if (!_contractCheck(transaction)) { authData.result = AuthResult.FAILED; authData.message = Errors.NOT_IN_CONTRACT_LIST; return authData; } (bool success, bytes memory revertData) = address(this).staticcall(_packTxn(transaction)); return _parseReturnData(success, revertData); } function _postExecCheck( TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData calldata preData ) internal virtual override returns (AuthorizerReturnData memory authData) { authData.result = AuthResult.SUCCESS; } // Internal view functions. // Utilities for checking functions. function _checkRecipient(address _recipient) internal view { require(_recipient == _txn().from, "Invalid recipient"); } function _checkContract(address _contract) internal view { require(_contract == _txn().to, "Invalid contract"); } // Modifiers. modifier onlyContract(address _contract) { _checkContract(_contract); _; } /// External functions /// @dev Implement your own access control checking functions here. // example: // function transfer(address to, uint256 amount) // onlyContract(USDT_ADDR) // external view // { // require(amount > 0 & amount < 10000, "amount not in range"); // } /// @dev Override this cause it is used by `_preExecCheck`. /// @notice Target contracts this BaseACL controls. function contracts() public view virtual returns (address[] memory _contracts) {} fallback() external virtual { revert(Errors.METHOD_NOT_ALLOW); } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; struct CallData { uint256 flag; // 0x1 delegate call, 0x0 call. address to; uint256 value; bytes data; // calldata bytes hint; bytes extra; // for future support: signatures etc. } struct TransactionData { address from; // `msg.sender` who performs the transaction a.k.a wallet address. address delegate; // Delegate who calls executeTransactions(). // Same as CallData uint256 flag; // 0x1 delegate call, 0x0 call. address to; uint256 value; bytes data; // calldata bytes hint; bytes extra; } /// @dev Use enum instead of bool in case of when other status, like PENDING, /// is needed in the future. enum AuthResult { FAILED, SUCCESS } struct AuthorizerReturnData { AuthResult result; string message; bytes data; // Authorizer return data. usually used for hint purpose. } struct TransactionResult { bool success; // Call status. bytes data; // Return/Revert data. bytes hint; } library TxFlags { uint256 internal constant DELEGATE_CALL_MASK = 0x1; // 1 for delegatecall, 0 for call uint256 internal constant ALLOW_REVERT_MASK = 0x2; // 1 for allow, 0 for not function isDelegateCall(uint256 flag) internal pure returns (bool) { return flag & DELEGATE_CALL_MASK > 0; } function allowsRevert(uint256 flag) internal pure returns (bool) { return flag & ALLOW_REVERT_MASK > 0; } } library AuthType { bytes32 internal constant FUNC = "FunctionType"; bytes32 internal constant TRANSFER = "TransferType"; bytes32 internal constant DEX = "DexType"; bytes32 internal constant LENDING = "LendingType"; bytes32 internal constant COMMON = "CommonType"; bytes32 internal constant SET = "SetType"; bytes32 internal constant VM = "VM"; } library AuthFlags { uint256 internal constant HAS_PRE_CHECK_MASK = 0x1; uint256 internal constant HAS_POST_CHECK_MASK = 0x2; uint256 internal constant HAS_PRE_PROC_MASK = 0x4; uint256 internal constant HAS_POST_PROC_MASK = 0x8; uint256 internal constant SUPPORT_HINT_MASK = 0x40; uint256 internal constant FULL_MODE = HAS_PRE_CHECK_MASK | HAS_POST_CHECK_MASK | HAS_PRE_PROC_MASK | HAS_POST_PROC_MASK; function isValid(uint256 flag) internal pure returns (bool) { // At least one check handler is activated. return hasPreCheck(flag) || hasPostCheck(flag); } function hasPreCheck(uint256 flag) internal pure returns (bool) { return flag & HAS_PRE_CHECK_MASK > 0; } function hasPostCheck(uint256 flag) internal pure returns (bool) { return flag & HAS_POST_CHECK_MASK > 0; } function hasPreProcess(uint256 flag) internal pure returns (bool) { return flag & HAS_PRE_PROC_MASK > 0; } function hasPostProcess(uint256 flag) internal pure returns (bool) { return flag & HAS_POST_PROC_MASK > 0; } function supportHint(uint256 flag) internal pure returns (bool) { return flag & SUPPORT_HINT_MASK > 0; } } // For Rule VM. // For each VariantType, an extractor should be implement. enum VariantType { INVALID, // Mark for delete. EXTRACT_CALLDATA, // extract calldata by path bytes. NAME, // name for user-defined variant. RAW, // encoded solidity values. VIEW, // staticcall view non-side-effect function and get return value. CALL, // call state changing function and get returned value. RULE, // rule expression. ANY } // How the data should be decoded. enum SolidityType { _invalid, // Mark for delete. _any, _bytes, _bool, ///// START 1 ///// Generated by gen_rulelib.py (start) _address, _uint256, _int256, ///// Generated by gen_rulelib.py (end) ///// END 1 _end } // A common operand in rule. struct Variant { VariantType varType; SolidityType solType; bytes data; } library VarName { bytes5 internal constant TEMP = "temp."; function isTemp(bytes32 name) internal pure returns (bool) { return bytes5(name) == TEMP; } } // OpCode for rule expression which returns v0. enum OP { INVALID, // One opnd. VAR, // v1 NOT, // !v1 // Two opnds. // checkBySolType() which returns boolean. EQ, // v1 == v2 NE, // v1 != v2 GT, // v1 > v2 GE, // v1 >= v2 LT, // v1 < v2 LE, // v1 <= v2 IN, // v1 in [...] NOTIN, // v1 not in [...] // computeBySolType() which returns bytes (with same solType) AND, // v1 & v2 OR, // v1 | v2 ADD, // v1 + v2 SUB, // v1 - v2 MUL, // v1 * v2 DIV, // v1 / v2 MOD, // v1 % v2 // Three opnds. IF, // v1? v2: v3 // Side-effect ones. ASSIGN, // v1 := v2 VM, // rule list bytes. NOP // as end. } struct Rule { OP op; Variant[] vars; }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "../src/Types.sol"; interface IAuthorizer { function flag() external view returns (uint256 authFlags); function setCaller(address _caller) external; function preExecCheck(TransactionData calldata transaction) external returns (AuthorizerReturnData memory authData); function postExecCheck( TransactionData calldata transaction, TransactionResult calldata callResult, AuthorizerReturnData calldata preAuthData ) external returns (AuthorizerReturnData memory authData); function preExecProcess(TransactionData calldata transaction) external; function postExecProcess(TransactionData calldata transaction, TransactionResult calldata callResult) external; } interface IAuthorizerSupportingHint is IAuthorizer { // When IAuthorizer(auth).flag().supportHint() == true; function collectHint( AuthorizerReturnData calldata preAuthData, AuthorizerReturnData calldata postAuthData ) external view returns (bytes memory hint); }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.19; import "../src/Types.sol"; interface IAccount { function execTransaction(CallData calldata callData) external returns (TransactionResult memory result); function execTransactions( CallData[] calldata callDataList ) external returns (TransactionResult[] memory resultList); function setAuthorizer(address _authorizer) external; function setRoleManager(address _roleManager) external; function addDelegate(address _delegate) external; function addDelegates(address[] calldata _delegates) external; /// @dev Sub instance should override this to set `from` for transaction /// @return account The address for the contract wallet, also the /// `msg.sender` address which send the transaction. function getAccountAddress() external view returns (address account); function roleManager() external view returns (address _roleManager); function authorizer() external view returns (address _authorizer); }
{ "remappings": [ "openzeppelin/=lib/openzeppelin-contracts/", "forge-std/=lib/forge-std/src/", "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/", "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", "openzeppelin-contracts/=lib/openzeppelin-contracts/" ], "optimizer": { "enabled": true, "runs": 200 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "cancun", "viaIR": true, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"cobosafe","type":"address"},{"indexed":true,"internalType":"address","name":"safe","type":"address"},{"indexed":true,"internalType":"address","name":"factory","type":"address"}],"name":"ArgusInitialized","type":"event"},{"inputs":[],"name":"NAME","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"coboSafeAddress","type":"address"},{"internalType":"address","name":"authorizerAddress","type":"address"},{"internalType":"bool","name":"isDelegateCall","type":"bool"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"}],"name":"addAuthorizer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract CoboFactory","name":"factory","type":"address"},{"internalType":"address","name":"coboSafeAddress","type":"address"},{"internalType":"bytes32","name":"dexAuthorizerName","type":"bytes32"},{"internalType":"bool","name":"isDelegateCall","type":"bool"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"},{"internalType":"address[]","name":"_swapInTokens","type":"address[]"},{"internalType":"address[]","name":"_swapOutTokens","type":"address[]"},{"internalType":"bytes32","name":"tag","type":"bytes32"}],"name":"addDexAuthorizer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract CoboFactory","name":"factory","type":"address"},{"internalType":"address","name":"coboSafeAddress","type":"address"},{"internalType":"bool","name":"isDelegateCall","type":"bool"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"},{"internalType":"address[]","name":"_contracts","type":"address[]"},{"internalType":"string[][]","name":"funcLists","type":"string[][]"},{"internalType":"bytes32","name":"tag","type":"bytes32"}],"name":"addFuncAuthorizer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract CoboFactory","name":"factory","type":"address"},{"internalType":"address","name":"coboSafeAddress","type":"address"},{"internalType":"bool","name":"isDelegateCall","type":"bool"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"receiver","type":"address"}],"internalType":"struct TransferAuthorizer.TokenReceiver[]","name":"tokenReceivers","type":"tuple[]"},{"internalType":"bytes32","name":"tag","type":"bytes32"}],"name":"addTransferAuthorizer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract CoboFactory","name":"factory","type":"address"},{"internalType":"address","name":"coboSafeAddress","type":"address"},{"internalType":"bytes32","name":"authorizerName","type":"bytes32"},{"internalType":"bytes32","name":"tag","type":"bytes32"}],"name":"createAuthorizer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"coboSafeAddress","type":"address"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"},{"internalType":"address[]","name":"delegates","type":"address[]"}],"name":"grantRoles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract CoboFactory","name":"factory","type":"address"},{"internalType":"bytes32","name":"coboSafeAccountSalt","type":"bytes32"}],"name":"initArgus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"coboSafeAddress","type":"address"},{"internalType":"address","name":"authorizerAddress","type":"address"},{"internalType":"bool","name":"isDelegateCall","type":"bool"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"}],"name":"removeAuthorizer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"coboSafeAddress","type":"address"},{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"},{"internalType":"address[]","name":"delegates","type":"address[]"}],"name":"revokeRoles","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizerAddress","type":"address"},{"internalType":"address[]","name":"_swapInTokens","type":"address[]"},{"internalType":"address[]","name":"_swapOutTokens","type":"address[]"}],"name":"setDexAuthorizerParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizerAddress","type":"address"},{"internalType":"address[]","name":"_contracts","type":"address[]"},{"internalType":"string[][]","name":"funcLists","type":"string[][]"}],"name":"setFuncAuthorizerParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizerAddress","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"receiver","type":"address"}],"internalType":"struct TransferAuthorizer.TokenReceiver[]","name":"tokenReceivers","type":"tuple[]"}],"name":"setTransferAuthorizerParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizerAddress","type":"address"},{"internalType":"address[]","name":"_swapInTokens","type":"address[]"},{"internalType":"address[]","name":"_swapOutTokens","type":"address[]"}],"name":"unsetDexAuthorizerParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizerAddress","type":"address"},{"internalType":"address[]","name":"_contracts","type":"address[]"},{"internalType":"string[][]","name":"funcLists","type":"string[][]"}],"name":"unsetFuncAuthorizerParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizerAddress","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"receiver","type":"address"}],"internalType":"struct TransferAuthorizer.TokenReceiver[]","name":"tokenReceivers","type":"tuple[]"}],"name":"unsetTransferAuthorizerParams","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Contract Creation Code
6080806040523460155761176d908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f803560e01c806293cd5f14610dc95780630387f11e14610d145780630e3f387314610cf85780631f76a7cc14610cb2578063469983531461096b5780634e328da91461077857806352b0dd96146106ab5780635f3313f71461069357806374c0b436146105f75780637ea2916a146105df5780637fa71fb4146104c557806380adc084146104aa5780638cb974351461048f5780639ed2c86114610245578063a3f4df7e14610214578063bb8e0efa146100f25763ffa1ad74146100d4575f80fd5b346100ef57806003193601126100ef57602060405160018152f35b80fd5b50346100ef57600460208261010636610f36565b60405163d09edf3160e01b815291979296939490939192839182906001600160a01b03165afa9081156102095784916101da575b506001600160a01b031690835b818110610152578480f35b61015d818389610fd3565b35833b156101d657604051636000cfbb60e01b8152871515600482015260248101919091526001600160a01b0385166044820152858160648183885af19081156101cb5786916101b2575b5050600101610147565b816101bc9161104f565b6101c757845f6101a8565b8480fd5b6040513d88823e3d90fd5b8580fd5b6101fc915060203d602011610202575b6101f4818361104f565b810190611282565b5f61013a565b503d6101ea565b6040513d86823e3d90fd5b50346100ef57806003193601126100ef576040517120b933bab9a0b1b1b7bab73a2432b63832b960711b8152602090f35b50346100ef5760e03660031901126100ef5761025f610ea2565b610267610de3565b90610270610eb8565b906064356001600160401b0381116101c757610290903690600401610e0d565b9290916084356001600160401b03811161048b576102b2903690600401610e0d565b959060a4356001600160401b038111610487576102d3903690600401610e0d565b60405163d09edf3160e01b8152949092908a9060c435906020886004816001600160a01b038b165afa978815610435578398610463575b5060405163148f54c960e21b81526d233ab731a0baba3437b934bd32b960911b600482015260248101839052906020908290604490829087906001600160a01b03165af1908115610435578391610444575b506001600160a01b031696873b156104405760405163485cc95560e01b81523060048201526001600160a01b0390911660248201528281604481838c5af1908115610435578391610420575b5050863b1561041c576040519063b695e2ef60e01b825260048201528181602481838b5af18015610411576103f2575b5050906103ef986103ea939286611136565b61165c565b80f35b81610400919594939561104f565b61040d579091895f6103d8565b8980fd5b6040513d84823e3d90fd5b5080fd5b8161042a9161104f565b61041c57815f6103a8565b6040513d85823e3d90fd5b8280fd5b61045d915060203d602011610202576101f4818361104f565b5f61035c565b8391985061047f602091823d8411610202576101f4818361104f565b98915061030a565b8880fd5b8680fd5b50346100ef576103ef6104a136610f36565b9392909261165c565b50346100ef576103ef6104bc36610e3d565b93929092611578565b50346100ef57806104d536610e3d565b939091906001600160a01b0316803b156101d65760405163eb70037360e01b81526020600482015286818061050e602482018a89611405565b038183865af19081156105d45787916105bf575b505060206004916040519283809262435da560e01b82525afa9081156101cb5786916105a0575b506001600160a01b031690813b156101d65785809461057e60405197889687958694630e54e58d60e01b8652600486016114fb565b03925af180156104115761058f5750f35b816105999161104f565b6100ef5780f35b6105b9915060203d602011610202576101f4818361104f565b5f610549565b816105c99161104f565b6101d657855f610522565b6040513d89823e3d90fd5b50346100ef576103ef6105f136610ef7565b9161153e565b50346100ef57806004602061060b36610e3d565b60405162435da560e01b81529096939591949293909283919082906001600160a01b03165afa9081156101cb578691610674575b506001600160a01b031690813b156101d65785809461057e60405197889687958694631c27e22d60e01b8652600486016114fb565b61068d915060203d602011610202576101f4818361104f565b5f61063f565b50346100ef576103ef6106a536610ef7565b9161149f565b50346100ef57806106bb36610e3d565b6001600160a01b039094169392909180610713575b5050816106dc57505050f35b823b1561070e5761057e928492836040518096819582946342905ba960e01b8452602060048501526024840191611405565b505050fd5b843b156101d657604051630250606560e41b8152602060048201529186918391829161074491602484019190611405565b038183885af190811561076d578591156106d057816107629161104f565b61070e57835f6106d0565b6040513d87823e3d90fd5b50346100ef5760c03660031901126100ef57610792610ea2565b61079a610de3565b906107a3610eb8565b906064356001600160401b0381116101c7576107c3903690600401610e0d565b9290916084356001600160401b03811161048b576107e5903690600401610ec7565b95908760a4356040519463d09edf3160e01b865260208660048160018060a01b0389165afa958615610435578396610947575b5060405163148f54c960e21b8152712a3930b739b332b920baba3437b934bd32b960711b600482015260248101839052906020908290604490829087906001600160a01b03165af1908115610435578391610928575b506001600160a01b031694853b156104405760405163485cc95560e01b81523060048201526001600160a01b0390911660248201528281604481838a5af1908115610435578391610913575b5050843b1561041c576040519063b695e2ef60e01b82526004820152818160248183895af18015610411576108fa575b50506103ef966103ea918461149f565b816109049161104f565b61090f57875f6108ea565b8780fd5b8161091d9161104f565b61041c57815f6108ba565b610941915060203d602011610202576101f4818361104f565b5f61086e565b83919650610963602091823d8411610202576101f4818361104f565b969150610818565b5034610c8f576040366003190112610c8f576001600160a01b0361098d610ea2565b16604051633b97d4ed60e11b81526e10dbd89bd4d859995058d8dbdd5b9d608a1b600482015260243560248201526020816044815f865af1908115610c84575f91610c93575b506001600160a01b0316803b15610c8f5760405163189acdbd60e31b81523060048201525f8160248183865af18015610c8457610c6f575b50303b15610440578260405163610b592560e01b8152826004820152818160248183305af1801561041157610c5a575b506040516339b4546760e11b81526e233630ba2937b632a6b0b730b3b2b960891b600482015260208160248185885af1908115610411578291610c3b575b506001600160a01b0316803b1561041c5760405163189acdbd60e31b8152306004820152828160248183865af1908115610435578391610c26575b5050823b1561041c576040519063f1d588c560e01b82526004820152818160248183875af1801561041157610c11575b506040516339b4546760e11b81527220b933bab9a937b7ba20baba3437b934bd32b960691b600482015260208160248185885af1908115610411578291610bf2575b506001600160a01b0316803b1561041c5760405163c0c53b8b60e01b8152306004820152836024820152836044820152828160648183865af1908115610435578391610bdd575b5050823b1561041c576040519063058a628f60e01b82526004820152818160248183875af1801561041157610bc8575b505030907fd98315a38819f85a0914498fdc92737e16297453017a69ca222331e83644e7398480a480f35b81610bd29161104f565b61044057825f610b9d565b81610be79161104f565b61041c57815f610b6d565b610c0b915060203d602011610202576101f4818361104f565b5f610b26565b81610c1b9161104f565b61044057825f610ae4565b81610c309161104f565b61041c57815f610ab4565b610c54915060203d602011610202576101f4818361104f565b5f610a79565b81610c649161104f565b61044057825f610a3b565b610c7c9193505f9061104f565b5f915f610a0b565b6040513d5f823e3d90fd5b5f80fd5b610cac915060203d602011610202576101f4818361104f565b5f6109d3565b34610c8f576080366003190112610c8f576020610ce6610cd0610ea2565b610cd8610de3565b9060643591604435916112a1565b6040516001600160a01b039091168152f35b34610c8f57610d12610d0936610e3d565b939290926111e2565b005b34610c8f57610100366003190112610c8f57610d2e610ea2565b610d36610de3565b606435908115158203610c8f576084356001600160401b038111610c8f57610d62903690600401610e0d565b92909160a4356001600160401b038111610c8f57610d84903690600401610e0d565b60c4929192356001600160401b038111610c8f57610d12976103ea92610db1610dc2933690600401610e0d565b93909260e4359087604435916112a1565b9586611578565b34610c8f57610d12610dda36610e3d565b93929092611136565b602435906001600160a01b0382168203610c8f57565b35906001600160a01b0382168203610c8f57565b9181601f84011215610c8f578235916001600160401b038311610c8f576020808501948460051b010111610c8f57565b906060600319830112610c8f576004356001600160a01b0381168103610c8f57916024356001600160401b038111610c8f5781610e7c91600401610e0d565b92909291604435906001600160401b038211610c8f57610e9e91600401610e0d565b9091565b600435906001600160a01b0382168203610c8f57565b604435908115158203610c8f57565b9181601f84011215610c8f578235916001600160401b038311610c8f576020808501948460061b010111610c8f57565b906040600319830112610c8f576004356001600160a01b0381168103610c8f5791602435906001600160401b038211610c8f57610e9e91600401610ec7565b906080600319830112610c8f576004356001600160a01b0381168103610c8f57916024356001600160a01b0381168103610c8f57916044358015158103610c8f5791606435906001600160401b038211610c8f57610e9e91600401610e0d565b15610f9d57565b60405162461bcd60e51b815260206004820152600e60248201526d4c656e677468206469666665727360901b6044820152606490fd5b9190811015610fe35760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160a01b0381168103610c8f5790565b9190811015610fe35760051b81013590601e1981360301821215610c8f5701908135916001600160401b038311610c8f576020018260051b36038113610c8f579190565b90601f801991011681019081106001600160401b0382111761107057604052565b634e487b7160e01b5f52604160045260245ffd5b928091604085019060018060a01b031685526040602086015252606083019060608160051b85010193835f91601e1982360301905b8484106110ca575050505050505090565b90919293949596605f19828203018752873583811215610c8f57840190602082359201916001600160401b038111610c8f578036038313610c8f576020828280600196849695859652848401375f828201840152601f01601f19160101990197019594019291906110b9565b939092919384156111db5761114c828614610f96565b6001600160a01b0316935f5b81811061116757505050505050565b61117a611175828488610fd3565b610ff7565b9061118681858761100b565b9290883b15610c8f575f916111af60405195869384936388e299e360e01b855260048501611084565b0381838b5af1918215610c84576001926111cb575b5001611158565b5f6111d59161104f565b5f6111c4565b5050505050565b939092919384156111db576111f8828614610f96565b6001600160a01b0316935f5b81811061121357505050505050565b611221611175828488610fd3565b9061122d81858761100b565b9290883b15610c8f575f91611256604051958693849363476ed36d60e01b855260048501611084565b0381838b5af1918215610c8457600192611272575b5001611204565b5f61127c9161104f565b5f61126b565b90816020910312610c8f57516001600160a01b0381168103610c8f5790565b60405163d09edf3160e01b8152929391602090849060049082906001600160a01b03165afa938415610c84575f935f956113dc575b5060405163148f54c960e21b815260048101919091526024810183905290602090829060449082905f906001600160a01b03165af1908115610c84575f916113bd575b506001600160a01b031692833b15610c8f5760405163485cc95560e01b81523060048201526001600160a01b0390911660248201525f8160448183885af18015610c84576113a8575b50823b1561041c576040519063b695e2ef60e01b82526004820152818160248183875af180156104115761139557505090565b6113a082809261104f565b6100ef575090565b6113b59192505f9061104f565b5f905f611362565b6113d6915060203d602011610202576101f4818361104f565b5f611319565b60209195509160446113fb5f94843d8611610202576101f4818361104f565b96925050916112d6565b916020908281520191905f5b81811061141e5750505090565b909192602080600192838060a01b0361143688610df9565b168152019401929101611411565b60208082528101839052604001915f5b8181106114615750505090565b909192604080600192838060a01b0361147988610df9565b168152838060a01b0361148e60208901610df9565b166020820152019401929101611454565b82156114f6576001600160a01b031691823b15610c8f576114d9925f9283604051809681958294636c6be21360e01b845260048401611444565b03925af18015610c84576114ea5750565b5f6114f49161104f565b565b505050565b60408082528101839052909391906001600160fb1b038211610c8f5761153b9460609260051b809184840137810190602083828403019101520191611405565b90565b82156114f6576001600160a01b031691823b15610c8f576114d9925f9283604051809681958294633a6451e760e21b845260048401611444565b5f946001600160a01b0390911692909190806115ff575b50508061159d575b50505050565b813b156115fb579183916115d09383604051809681958294632131db1f60e21b8452602060048501526024840191611405565b03925af18015610411576115e6575b8080611597565b6115f182809261104f565b6100ef57806115df565b8380fd5b833b15610c8f5760405163935532bb60e01b815260206004820152915f918391829161163091602484019190611405565b038183875af18015610c8457611647575b8061158f565b6116549194505f9061104f565b5f925f611641565b60405163d09edf3160e01b815290602090829060049082906001600160a01b03165afa908115610c84575f91611718575b506001600160a01b0316935f5b8181106116a957505050505050565b6116b4818387610fd3565b3590863b15610c8f5760405163f974cf0960e01b8152851515600482015260248101929092526001600160a01b03841660448301525f82606481838b5af1918215610c8457600192611708575b500161169a565b5f6117129161104f565b5f611701565b611731915060203d602011610202576101f4818361104f565b5f61168d56fea2646970667358221220677bf828985609e6c177e5d9c83eeda87c38571a6584b79862ec7a9a4b1da6b164736f6c634300081a0033
Deployed Bytecode
0x60806040526004361015610011575f80fd5b5f803560e01c806293cd5f14610dc95780630387f11e14610d145780630e3f387314610cf85780631f76a7cc14610cb2578063469983531461096b5780634e328da91461077857806352b0dd96146106ab5780635f3313f71461069357806374c0b436146105f75780637ea2916a146105df5780637fa71fb4146104c557806380adc084146104aa5780638cb974351461048f5780639ed2c86114610245578063a3f4df7e14610214578063bb8e0efa146100f25763ffa1ad74146100d4575f80fd5b346100ef57806003193601126100ef57602060405160018152f35b80fd5b50346100ef57600460208261010636610f36565b60405163d09edf3160e01b815291979296939490939192839182906001600160a01b03165afa9081156102095784916101da575b506001600160a01b031690835b818110610152578480f35b61015d818389610fd3565b35833b156101d657604051636000cfbb60e01b8152871515600482015260248101919091526001600160a01b0385166044820152858160648183885af19081156101cb5786916101b2575b5050600101610147565b816101bc9161104f565b6101c757845f6101a8565b8480fd5b6040513d88823e3d90fd5b8580fd5b6101fc915060203d602011610202575b6101f4818361104f565b810190611282565b5f61013a565b503d6101ea565b6040513d86823e3d90fd5b50346100ef57806003193601126100ef576040517120b933bab9a0b1b1b7bab73a2432b63832b960711b8152602090f35b50346100ef5760e03660031901126100ef5761025f610ea2565b610267610de3565b90610270610eb8565b906064356001600160401b0381116101c757610290903690600401610e0d565b9290916084356001600160401b03811161048b576102b2903690600401610e0d565b959060a4356001600160401b038111610487576102d3903690600401610e0d565b60405163d09edf3160e01b8152949092908a9060c435906020886004816001600160a01b038b165afa978815610435578398610463575b5060405163148f54c960e21b81526d233ab731a0baba3437b934bd32b960911b600482015260248101839052906020908290604490829087906001600160a01b03165af1908115610435578391610444575b506001600160a01b031696873b156104405760405163485cc95560e01b81523060048201526001600160a01b0390911660248201528281604481838c5af1908115610435578391610420575b5050863b1561041c576040519063b695e2ef60e01b825260048201528181602481838b5af18015610411576103f2575b5050906103ef986103ea939286611136565b61165c565b80f35b81610400919594939561104f565b61040d579091895f6103d8565b8980fd5b6040513d84823e3d90fd5b5080fd5b8161042a9161104f565b61041c57815f6103a8565b6040513d85823e3d90fd5b8280fd5b61045d915060203d602011610202576101f4818361104f565b5f61035c565b8391985061047f602091823d8411610202576101f4818361104f565b98915061030a565b8880fd5b8680fd5b50346100ef576103ef6104a136610f36565b9392909261165c565b50346100ef576103ef6104bc36610e3d565b93929092611578565b50346100ef57806104d536610e3d565b939091906001600160a01b0316803b156101d65760405163eb70037360e01b81526020600482015286818061050e602482018a89611405565b038183865af19081156105d45787916105bf575b505060206004916040519283809262435da560e01b82525afa9081156101cb5786916105a0575b506001600160a01b031690813b156101d65785809461057e60405197889687958694630e54e58d60e01b8652600486016114fb565b03925af180156104115761058f5750f35b816105999161104f565b6100ef5780f35b6105b9915060203d602011610202576101f4818361104f565b5f610549565b816105c99161104f565b6101d657855f610522565b6040513d89823e3d90fd5b50346100ef576103ef6105f136610ef7565b9161153e565b50346100ef57806004602061060b36610e3d565b60405162435da560e01b81529096939591949293909283919082906001600160a01b03165afa9081156101cb578691610674575b506001600160a01b031690813b156101d65785809461057e60405197889687958694631c27e22d60e01b8652600486016114fb565b61068d915060203d602011610202576101f4818361104f565b5f61063f565b50346100ef576103ef6106a536610ef7565b9161149f565b50346100ef57806106bb36610e3d565b6001600160a01b039094169392909180610713575b5050816106dc57505050f35b823b1561070e5761057e928492836040518096819582946342905ba960e01b8452602060048501526024840191611405565b505050fd5b843b156101d657604051630250606560e41b8152602060048201529186918391829161074491602484019190611405565b038183885af190811561076d578591156106d057816107629161104f565b61070e57835f6106d0565b6040513d87823e3d90fd5b50346100ef5760c03660031901126100ef57610792610ea2565b61079a610de3565b906107a3610eb8565b906064356001600160401b0381116101c7576107c3903690600401610e0d565b9290916084356001600160401b03811161048b576107e5903690600401610ec7565b95908760a4356040519463d09edf3160e01b865260208660048160018060a01b0389165afa958615610435578396610947575b5060405163148f54c960e21b8152712a3930b739b332b920baba3437b934bd32b960711b600482015260248101839052906020908290604490829087906001600160a01b03165af1908115610435578391610928575b506001600160a01b031694853b156104405760405163485cc95560e01b81523060048201526001600160a01b0390911660248201528281604481838a5af1908115610435578391610913575b5050843b1561041c576040519063b695e2ef60e01b82526004820152818160248183895af18015610411576108fa575b50506103ef966103ea918461149f565b816109049161104f565b61090f57875f6108ea565b8780fd5b8161091d9161104f565b61041c57815f6108ba565b610941915060203d602011610202576101f4818361104f565b5f61086e565b83919650610963602091823d8411610202576101f4818361104f565b969150610818565b5034610c8f576040366003190112610c8f576001600160a01b0361098d610ea2565b16604051633b97d4ed60e11b81526e10dbd89bd4d859995058d8dbdd5b9d608a1b600482015260243560248201526020816044815f865af1908115610c84575f91610c93575b506001600160a01b0316803b15610c8f5760405163189acdbd60e31b81523060048201525f8160248183865af18015610c8457610c6f575b50303b15610440578260405163610b592560e01b8152826004820152818160248183305af1801561041157610c5a575b506040516339b4546760e11b81526e233630ba2937b632a6b0b730b3b2b960891b600482015260208160248185885af1908115610411578291610c3b575b506001600160a01b0316803b1561041c5760405163189acdbd60e31b8152306004820152828160248183865af1908115610435578391610c26575b5050823b1561041c576040519063f1d588c560e01b82526004820152818160248183875af1801561041157610c11575b506040516339b4546760e11b81527220b933bab9a937b7ba20baba3437b934bd32b960691b600482015260208160248185885af1908115610411578291610bf2575b506001600160a01b0316803b1561041c5760405163c0c53b8b60e01b8152306004820152836024820152836044820152828160648183865af1908115610435578391610bdd575b5050823b1561041c576040519063058a628f60e01b82526004820152818160248183875af1801561041157610bc8575b505030907fd98315a38819f85a0914498fdc92737e16297453017a69ca222331e83644e7398480a480f35b81610bd29161104f565b61044057825f610b9d565b81610be79161104f565b61041c57815f610b6d565b610c0b915060203d602011610202576101f4818361104f565b5f610b26565b81610c1b9161104f565b61044057825f610ae4565b81610c309161104f565b61041c57815f610ab4565b610c54915060203d602011610202576101f4818361104f565b5f610a79565b81610c649161104f565b61044057825f610a3b565b610c7c9193505f9061104f565b5f915f610a0b565b6040513d5f823e3d90fd5b5f80fd5b610cac915060203d602011610202576101f4818361104f565b5f6109d3565b34610c8f576080366003190112610c8f576020610ce6610cd0610ea2565b610cd8610de3565b9060643591604435916112a1565b6040516001600160a01b039091168152f35b34610c8f57610d12610d0936610e3d565b939290926111e2565b005b34610c8f57610100366003190112610c8f57610d2e610ea2565b610d36610de3565b606435908115158203610c8f576084356001600160401b038111610c8f57610d62903690600401610e0d565b92909160a4356001600160401b038111610c8f57610d84903690600401610e0d565b60c4929192356001600160401b038111610c8f57610d12976103ea92610db1610dc2933690600401610e0d565b93909260e4359087604435916112a1565b9586611578565b34610c8f57610d12610dda36610e3d565b93929092611136565b602435906001600160a01b0382168203610c8f57565b35906001600160a01b0382168203610c8f57565b9181601f84011215610c8f578235916001600160401b038311610c8f576020808501948460051b010111610c8f57565b906060600319830112610c8f576004356001600160a01b0381168103610c8f57916024356001600160401b038111610c8f5781610e7c91600401610e0d565b92909291604435906001600160401b038211610c8f57610e9e91600401610e0d565b9091565b600435906001600160a01b0382168203610c8f57565b604435908115158203610c8f57565b9181601f84011215610c8f578235916001600160401b038311610c8f576020808501948460061b010111610c8f57565b906040600319830112610c8f576004356001600160a01b0381168103610c8f5791602435906001600160401b038211610c8f57610e9e91600401610ec7565b906080600319830112610c8f576004356001600160a01b0381168103610c8f57916024356001600160a01b0381168103610c8f57916044358015158103610c8f5791606435906001600160401b038211610c8f57610e9e91600401610e0d565b15610f9d57565b60405162461bcd60e51b815260206004820152600e60248201526d4c656e677468206469666665727360901b6044820152606490fd5b9190811015610fe35760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160a01b0381168103610c8f5790565b9190811015610fe35760051b81013590601e1981360301821215610c8f5701908135916001600160401b038311610c8f576020018260051b36038113610c8f579190565b90601f801991011681019081106001600160401b0382111761107057604052565b634e487b7160e01b5f52604160045260245ffd5b928091604085019060018060a01b031685526040602086015252606083019060608160051b85010193835f91601e1982360301905b8484106110ca575050505050505090565b90919293949596605f19828203018752873583811215610c8f57840190602082359201916001600160401b038111610c8f578036038313610c8f576020828280600196849695859652848401375f828201840152601f01601f19160101990197019594019291906110b9565b939092919384156111db5761114c828614610f96565b6001600160a01b0316935f5b81811061116757505050505050565b61117a611175828488610fd3565b610ff7565b9061118681858761100b565b9290883b15610c8f575f916111af60405195869384936388e299e360e01b855260048501611084565b0381838b5af1918215610c84576001926111cb575b5001611158565b5f6111d59161104f565b5f6111c4565b5050505050565b939092919384156111db576111f8828614610f96565b6001600160a01b0316935f5b81811061121357505050505050565b611221611175828488610fd3565b9061122d81858761100b565b9290883b15610c8f575f91611256604051958693849363476ed36d60e01b855260048501611084565b0381838b5af1918215610c8457600192611272575b5001611204565b5f61127c9161104f565b5f61126b565b90816020910312610c8f57516001600160a01b0381168103610c8f5790565b60405163d09edf3160e01b8152929391602090849060049082906001600160a01b03165afa938415610c84575f935f956113dc575b5060405163148f54c960e21b815260048101919091526024810183905290602090829060449082905f906001600160a01b03165af1908115610c84575f916113bd575b506001600160a01b031692833b15610c8f5760405163485cc95560e01b81523060048201526001600160a01b0390911660248201525f8160448183885af18015610c84576113a8575b50823b1561041c576040519063b695e2ef60e01b82526004820152818160248183875af180156104115761139557505090565b6113a082809261104f565b6100ef575090565b6113b59192505f9061104f565b5f905f611362565b6113d6915060203d602011610202576101f4818361104f565b5f611319565b60209195509160446113fb5f94843d8611610202576101f4818361104f565b96925050916112d6565b916020908281520191905f5b81811061141e5750505090565b909192602080600192838060a01b0361143688610df9565b168152019401929101611411565b60208082528101839052604001915f5b8181106114615750505090565b909192604080600192838060a01b0361147988610df9565b168152838060a01b0361148e60208901610df9565b166020820152019401929101611454565b82156114f6576001600160a01b031691823b15610c8f576114d9925f9283604051809681958294636c6be21360e01b845260048401611444565b03925af18015610c84576114ea5750565b5f6114f49161104f565b565b505050565b60408082528101839052909391906001600160fb1b038211610c8f5761153b9460609260051b809184840137810190602083828403019101520191611405565b90565b82156114f6576001600160a01b031691823b15610c8f576114d9925f9283604051809681958294633a6451e760e21b845260048401611444565b5f946001600160a01b0390911692909190806115ff575b50508061159d575b50505050565b813b156115fb579183916115d09383604051809681958294632131db1f60e21b8452602060048501526024840191611405565b03925af18015610411576115e6575b8080611597565b6115f182809261104f565b6100ef57806115df565b8380fd5b833b15610c8f5760405163935532bb60e01b815260206004820152915f918391829161163091602484019190611405565b038183875af18015610c8457611647575b8061158f565b6116549194505f9061104f565b5f925f611641565b60405163d09edf3160e01b815290602090829060049082906001600160a01b03165afa908115610c84575f91611718575b506001600160a01b0316935f5b8181106116a957505050505050565b6116b4818387610fd3565b3590863b15610c8f5760405163f974cf0960e01b8152851515600482015260248101929092526001600160a01b03841660448301525f82606481838b5af1918215610c8457600192611708575b500161169a565b5f6117129161104f565b5f611701565b611731915060203d602011610202576101f4818361104f565b5f61168d56fea2646970667358221220677bf828985609e6c177e5d9c83eeda87c38571a6584b79862ec7a9a4b1da6b164736f6c634300081a0033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.