Overview
ETH Balance
0 ETH
Eth Value
$0.00Latest 25 from a total of 2,241 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Execute Transact... | 22877202 | 12 hrs ago | IN | 0 ETH | 0.00043449 | ||||
Execute Transact... | 22876728 | 13 hrs ago | IN | 0 ETH | 0.00086569 | ||||
Execute Transact... | 22876724 | 13 hrs ago | IN | 0 ETH | 0.00015903 | ||||
Execute Transact... | 22876724 | 13 hrs ago | IN | 0 ETH | 0.0005604 | ||||
Execute Transact... | 22876721 | 13 hrs ago | IN | 0 ETH | 0.0008699 | ||||
Execute Transact... | 22868982 | 39 hrs ago | IN | 0 ETH | 0.00111674 | ||||
Execute Transact... | 22868978 | 39 hrs ago | IN | 0 ETH | 0.00027566 | ||||
Execute Transact... | 22868978 | 39 hrs ago | IN | 0 ETH | 0.00181298 | ||||
Execute Transact... | 22868971 | 39 hrs ago | IN | 0 ETH | 0.00162037 | ||||
Execute Transact... | 22861227 | 2 days ago | IN | 0 ETH | 0.00021701 | ||||
Execute Transact... | 22861222 | 2 days ago | IN | 0 ETH | 0.00021411 | ||||
Execute Transact... | 22861219 | 2 days ago | IN | 0 ETH | 0.00004772 | ||||
Execute Transact... | 22861219 | 2 days ago | IN | 0 ETH | 0.0003042 | ||||
Execute Transact... | 22861217 | 2 days ago | IN | 0 ETH | 0.00005166 | ||||
Execute Transact... | 22861217 | 2 days ago | IN | 0 ETH | 0.00027598 | ||||
Execute Transact... | 22858076 | 3 days ago | IN | 0 ETH | 0.00012038 | ||||
Execute Transact... | 22853487 | 3 days ago | IN | 0 ETH | 0.00012866 | ||||
Execute Transact... | 22853484 | 3 days ago | IN | 0 ETH | 0.00003439 | ||||
Execute Transact... | 22853484 | 3 days ago | IN | 0 ETH | 0.00021749 | ||||
Execute Transact... | 22853480 | 3 days ago | IN | 0 ETH | 0.000039 | ||||
Execute Transact... | 22853480 | 3 days ago | IN | 0 ETH | 0.00020887 | ||||
Execute Transact... | 22845726 | 4 days ago | IN | 0 ETH | 0.00023043 | ||||
Execute Transact... | 22845723 | 4 days ago | IN | 0 ETH | 0.00035703 | ||||
Execute Transact... | 22845718 | 4 days ago | IN | 0 ETH | 0.00036351 | ||||
Execute Transact... | 22845073 | 4 days ago | IN | 0 ETH | 0.00037642 |
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Method | Block |
From
|
To
|
|||
---|---|---|---|---|---|---|---|
0x6101a060 | 18726524 | 580 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Contract Name:
ExecutorPlugin
Compiler Version
v0.8.19+commit.7dd6d404
Optimization Enabled:
Yes with 20000 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {ReentrancyGuard} from "openzeppelin-contracts/security/ReentrancyGuard.sol"; import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol"; import {EIP712} from "solady/utils/EIP712.sol"; import {ISafeWallet} from "interfaces/external/ISafeWallet.sol"; import {AddressProviderService} from "src/core/AddressProviderService.sol"; import {TransactionValidator} from "src/core/TransactionValidator.sol"; import {ExecutorRegistry} from "src/core/registries/ExecutorRegistry.sol"; import {SafeHelper} from "src/libraries/SafeHelper.sol"; import {TypeHashHelper} from "src/libraries/TypeHashHelper.sol"; import {Types} from "interfaces/Types.sol"; /** * @title ExecutorPlugin * @author Brahma.fi * @notice Responsible for executing transactions on safes with module permissions * @dev ExecutorPlugin needs to be enabled as a module on the safe */ contract ExecutorPlugin is AddressProviderService, ReentrancyGuard, EIP712 { error InvalidExecutor(); error InvalidSignature(); error ModuleExecutionFailed(); /** * @notice datatype for execution requests * @param exec txn params * @param account address of safe to execute txn on * @param executor address that initiated execution request * @param executorSignature executor's signature for execution * @param validatorSignature validator's signature for execution * @dev Signature formats: * executorSignature = executor's signatures (arbitrary bytes length) * validatorSignature = abi.encodePacked(policySignature, length, expiryEpoch) * where: * policySignature = validity signature signed by validator (arbitrary bytes length) * length = length of `policySignature` (4 bytes) * expiryEpoch = expiry timestamp (4 bytes) */ struct ExecutionRequest { Types.Executable exec; address account; address executor; bytes executorSignature; bytes validatorSignature; } /// @notice EIP712 domain name string private constant _NAME = "ExecutorPlugin"; /// @notice EIP712 domain version string private constant _VERSION = "1.0"; /// @notice mapping of account to nonce of executors mapping(address account => mapping(address executor => uint256 nonce)) public executorNonce; constructor(address _addressProvider) AddressProviderService(_addressProvider) {} /** * @notice Enables executors to raise execution requests that will be executed via a module transaction * @dev The Executors are expected to sign the EIP712 digest generated from following struct: TypeHashHelper.ExecutionParams * @param execRequest params for execution request * @return returnData of txn */ function executeTransaction(ExecutionRequest calldata execRequest) external nonReentrant returns (bytes memory) { address transactionValidator = AddressProviderService._getAuthorizedAddress(_TRANSACTION_VALIDATOR_HASH); _validateExecutionRequest(execRequest, transactionValidator); bytes memory txnResult = _executeTxnAsModule(execRequest.account, execRequest.exec); TransactionValidator(transactionValidator).validatePostExecutorTransaction(msg.sender, execRequest.account); return txnResult; } /** * @notice internal helper to execute transaction on a safe as a module * @dev executes txn as a module on `_account` * @param _account address of safe to execute on * @param _executable params of txn to execute * @return returnData of txn */ function _executeTxnAsModule(address _account, Types.Executable memory _executable) internal returns (bytes memory) { (bool success, bytes memory txnResult) = ISafeWallet(_account).execTransactionFromModuleReturnData( _executable.target, _executable.value, _executable.data, SafeHelper._parseOperationEnum(_executable.callType) ); if (!success) revert ModuleExecutionFailed(); return txnResult; } /** * @notice internal helper to validate the execution request * @dev - validates executor's signature and checks if the executor is valid for given account. * - validates policy * @param execRequest params for execution request */ function _validateExecutionRequest(ExecutionRequest calldata execRequest, address transactionValidator) internal { if (!ExecutorRegistry(executorRegistry).isExecutor(execRequest.account, execRequest.executor)) { revert InvalidExecutor(); } // Empty Signature check for EOA executor if (execRequest.executor.code.length == 0 && execRequest.executorSignature.length == 0) { // Executor is an EOA and no executor signature is provided revert InvalidSignature(); } // Build execution struct hash bytes32 _executionStructHash = TypeHashHelper._buildExecutionParamsHash( TypeHashHelper.ExecutionParams({ to: execRequest.exec.target, value: execRequest.exec.value, data: execRequest.exec.data, operation: uint8(SafeHelper._parseOperationEnum(execRequest.exec.callType)), account: execRequest.account, executor: execRequest.executor, nonce: executorNonce[execRequest.account][execRequest.executor]++, safeTxGas: 0, baseGas: 0, gasPrice: 0, gasToken: address(0), refundReceiver: address(0) }) ); // Build EIP712 digest for execution struct hash bytes32 _transactionDigest = _hashTypedData(_executionStructHash); // Validate executor signature if ( !SignatureCheckerLib.isValidSignatureNow( execRequest.executor, _transactionDigest, execRequest.executorSignature ) ) { revert InvalidExecutor(); } // Validate policy signature TransactionValidator(transactionValidator).validatePreExecutorTransaction( msg.sender, execRequest.account, _executionStructHash, execRequest.validatorSignature ); } /** * @notice Internal helper to get EIP712 domain name and version * @return name domainName * @return version domainVersion */ function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { return (_NAME, _VERSION); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == _ENTERED; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Signature verification helper that supports both ECDSA signatures from EOAs /// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol) /// /// @dev Note: /// - The signature checking functions use the ecrecover precompile (0x1). /// - The `bytes memory signature` variants use the identity precompile (0x4) /// to copy memory internally. /// - Unlike ECDSA signatures, contract signatures are revocable. /// /// WARNING! Do NOT use signatures as unique identifiers. /// Please use EIP712 with a nonce included in the digest to prevent replay attacks. /// This implementation does NOT check if a signature is non-malleable. library SignatureCheckerLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* SIGNATURE CHECKING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns whether `signature` is valid for `signer` and `hash`. /// If `signer` is a smart contract, the signature is validated with ERC1271. /// Otherwise, the signature is validated with `ECDSA.recover`. function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool isValid) { /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits of `signer` in case they are dirty. for { signer := shr(96, shl(96, signer)) } signer {} { let m := mload(0x40) if eq(mload(signature), 65) { mstore(0x00, hash) mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. mstore(0x40, mload(add(signature, 0x20))) // `r`. mstore(0x60, mload(add(signature, 0x40))) // `s`. let t := staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { isValid := 1 mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. break } } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. // Copy the `signature` over. let n := add(0x20, mload(signature)) pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n)) // forgefmt: disable-next-item isValid := and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. signer, // The `signer` address. m, // Offset of calldata in memory. add(returndatasize(), 0x44), // Length of calldata in memory. d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) break } } } /// @dev Returns whether `signature` is valid for `signer` and `hash`. /// If `signer` is a smart contract, the signature is validated with ERC1271. /// Otherwise, the signature is validated with `ECDSA.recover`. function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature) internal view returns (bool isValid) { /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits of `signer` in case they are dirty. for { signer := shr(96, shl(96, signer)) } signer {} { let m := mload(0x40) if eq(signature.length, 65) { mstore(0x00, hash) mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`. let t := staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { isValid := 1 mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. break } } mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. mstore(add(m, 0x44), signature.length) // Copy the `signature` over. calldatacopy(add(m, 0x64), signature.offset, signature.length) // forgefmt: disable-next-item isValid := and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. signer, // The `signer` address. m, // Offset of calldata in memory. add(signature.length, 0x64), // Length of calldata in memory. d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) break } } } /// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`. /// If `signer` is a smart contract, the signature is validated with ERC1271. /// Otherwise, the signature is validated with `ECDSA.recover`. function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns (bool isValid) { /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits of `signer` in case they are dirty. for { signer := shr(96, shl(96, signer)) } signer {} { let m := mload(0x40) mstore(0x00, hash) mstore(0x20, add(shr(255, vs), 27)) // `v`. mstore(0x40, r) // `r`. mstore(0x60, shr(1, shl(1, vs))) // `s`. let t := staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { isValid := 1 mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. break } let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. mstore(add(m, 0x44), 65) // Length of the signature. mstore(add(m, 0x64), r) // `r`. mstore(add(m, 0x84), mload(0x60)) // `s`. mstore8(add(m, 0xa4), mload(0x20)) // `v`. // forgefmt: disable-next-item isValid := and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. signer, // The `signer` address. m, // Offset of calldata in memory. 0xa5, // Length of calldata in memory. d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. break } } } /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`. /// If `signer` is a smart contract, the signature is validated with ERC1271. /// Otherwise, the signature is validated with `ECDSA.recover`. function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (bool isValid) { /// @solidity memory-safe-assembly assembly { // Clean the upper 96 bits of `signer` in case they are dirty. for { signer := shr(96, shl(96, signer)) } signer {} { let m := mload(0x40) mstore(0x00, hash) mstore(0x20, and(v, 0xff)) // `v`. mstore(0x40, r) // `r`. mstore(0x60, s) // `s`. let t := staticcall( gas(), // Amount of gas left for the transaction. 1, // Address of `ecrecover`. 0x00, // Start of input. 0x80, // Size of input. 0x01, // Start of output. 0x20 // Size of output. ) // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { isValid := 1 mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. break } let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. mstore(add(m, 0x44), 65) // Length of the signature. mstore(add(m, 0x64), r) // `r`. mstore(add(m, 0x84), s) // `s`. mstore8(add(m, 0xa4), v) // `v`. // forgefmt: disable-next-item isValid := and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. signer, // The `signer` address. m, // Offset of calldata in memory. 0xa5, // Length of calldata in memory. d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) mstore(0x60, 0) // Restore the zero slot. mstore(0x40, m) // Restore the free memory pointer. break } } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC1271 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns whether `signature` is valid for `hash` /// for an ERC1271 `signer` contract. function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool isValid) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. // Copy the `signature` over. let n := add(0x20, mload(signature)) pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n)) // forgefmt: disable-next-item isValid := and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. signer, // The `signer` address. m, // Offset of calldata in memory. add(returndatasize(), 0x44), // Length of calldata in memory. d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) } } /// @dev Returns whether `signature` is valid for `hash` /// for an ERC1271 `signer` contract. function isValidERC1271SignatureNowCalldata( address signer, bytes32 hash, bytes calldata signature ) internal view returns (bool isValid) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. mstore(add(m, 0x44), signature.length) // Copy the `signature` over. calldatacopy(add(m, 0x64), signature.offset, signature.length) // forgefmt: disable-next-item isValid := and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. signer, // The `signer` address. m, // Offset of calldata in memory. add(signature.length, 0x64), // Length of calldata in memory. d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) } } /// @dev Returns whether the signature (`r`, `vs`) is valid for `hash` /// for an ERC1271 `signer` contract. function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs) internal view returns (bool isValid) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. mstore(add(m, 0x44), 65) // Length of the signature. mstore(add(m, 0x64), r) // `r`. mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`. mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`. // forgefmt: disable-next-item isValid := and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. signer, // The `signer` address. m, // Offset of calldata in memory. 0xa5, // Length of calldata in memory. d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) } } /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash` /// for an ERC1271 `signer` contract. function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (bool isValid) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) let f := shl(224, 0x1626ba7e) mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. mstore(add(m, 0x04), hash) let d := add(m, 0x24) mstore(d, 0x40) // The offset of the `signature` in the calldata. mstore(add(m, 0x44), 65) // Length of the signature. mstore(add(m, 0x64), r) // `r`. mstore(add(m, 0x84), s) // `s`. mstore8(add(m, 0xa4), v) // `v`. // forgefmt: disable-next-item isValid := and( // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). eq(mload(d), f), // Whether the staticcall does not revert. // This must be placed at the end of the `and` clause, // as the arguments are evaluated from right to left. staticcall( gas(), // Remaining gas. signer, // The `signer` address. m, // Offset of calldata in memory. 0xa5, // Length of calldata in memory. d, // Offset of returndata. 0x20 // Length of returndata to write. ) ) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HASHING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns an Ethereum Signed Message, created from a `hash`. /// This produces a hash corresponding to the one signed with the /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) /// JSON-RPC method as part of EIP-191. function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { mstore(0x20, hash) // Store into scratch space for keccak256. mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. } } /// @dev Returns an Ethereum Signed Message, created from `s`. /// This produces a hash corresponding to the one signed with the /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) /// JSON-RPC method as part of EIP-191. /// Note: Supports lengths of `s` up to 999999 bytes. function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { let sLength := mload(s) let o := 0x20 mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. mstore(0x00, 0x00) // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. for { let temp := sLength } 1 {} { o := sub(o, 1) mstore8(o, add(48, mod(temp, 10))) temp := div(temp, 10) if iszero(temp) { break } } let n := sub(0x3a, o) // Header length: `26 + 32 - o`. // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) mstore(s, sLength) // Restore the length. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EMPTY CALLDATA HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns an empty calldata bytes. function emptySignature() internal pure returns (bytes calldata signature) { /// @solidity memory-safe-assembly assembly { signature.length := 0 } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Contract for EIP-712 typed structured data hashing and signing. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol) /// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol) /// /// @dev Note, this implementation: /// - Uses `address(this)` for the `verifyingContract` field. /// - Does NOT use the optional EIP-712 salt. /// - Does NOT use any EIP-712 extensions. /// This is for simplicity and to save gas. /// If you need to customize, please fork / modify accordingly. abstract contract EIP712 { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS AND IMMUTABLES */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. bytes32 internal constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; address private immutable _cachedThis; uint256 private immutable _cachedChainId; bytes32 private immutable _cachedNameHash; bytes32 private immutable _cachedVersionHash; bytes32 private immutable _cachedDomainSeparator; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTRUCTOR */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Cache the hashes for cheaper runtime gas costs. /// In the case of upgradeable contracts (i.e. proxies), /// or if the chain id changes due to a hard fork, /// the domain separator will be seamlessly calculated on-the-fly. constructor() { _cachedThis = address(this); _cachedChainId = block.chainid; string memory name; string memory version; if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion(); bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name)); bytes32 versionHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version)); _cachedNameHash = nameHash; _cachedVersionHash = versionHash; bytes32 separator; if (!_domainNameAndVersionMayChange()) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Load the free memory pointer. mstore(m, _DOMAIN_TYPEHASH) mstore(add(m, 0x20), nameHash) mstore(add(m, 0x40), versionHash) mstore(add(m, 0x60), chainid()) mstore(add(m, 0x80), address()) separator := keccak256(m, 0xa0) } } _cachedDomainSeparator = separator; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* FUNCTIONS TO OVERRIDE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Please override this function to return the domain name and version. /// ``` /// function _domainNameAndVersion() /// internal /// pure /// virtual /// returns (string memory name, string memory version) /// { /// name = "Solady"; /// version = "1"; /// } /// ``` /// /// Note: If the returned result may change after the contract has been deployed, /// you must override `_domainNameAndVersionMayChange()` to return true. function _domainNameAndVersion() internal view virtual returns (string memory name, string memory version); /// @dev Returns if `_domainNameAndVersion()` may change /// after the contract has been deployed (i.e. after the constructor). /// Default: false. function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {} /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HASHING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the EIP-712 domain separator. function _domainSeparator() internal view virtual returns (bytes32 separator) { if (_domainNameAndVersionMayChange()) { separator = _buildDomainSeparator(); } else { separator = _cachedDomainSeparator; if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator(); } } /// @dev Returns the hash of the fully encoded EIP-712 message for this domain, /// given `structHash`, as defined in /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct. /// /// The hash can be used together with {ECDSA-recover} to obtain the signer of a message: /// ``` /// bytes32 digest = _hashTypedData(keccak256(abi.encode( /// keccak256("Mail(address to,string contents)"), /// mailTo, /// keccak256(bytes(mailContents)) /// ))); /// address signer = ECDSA.recover(digest, signature); /// ``` function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) { bytes32 separator; if (_domainNameAndVersionMayChange()) { separator = _buildDomainSeparator(); } else { separator = _cachedDomainSeparator; if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator(); } /// @solidity memory-safe-assembly assembly { // Compute the digest. mstore(0x00, 0x1901000000000000) // Store "\x19\x01". mstore(0x1a, separator) // Store the domain separator. mstore(0x3a, structHash) // Store the struct hash. digest := keccak256(0x18, 0x42) // Restore the part of the free memory slot that was overwritten. mstore(0x3a, 0) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EIP-5267 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev See: https://eips.ethereum.org/EIPS/eip-5267 function eip712Domain() public view virtual returns ( bytes1 fields, string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] memory extensions ) { fields = hex"0f"; // `0b01111`. (name, version) = _domainNameAndVersion(); chainId = block.chainid; verifyingContract = address(this); salt = salt; // `bytes32(0)`. extensions = extensions; // `new uint256[](0)`. } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PRIVATE HELPERS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the EIP-712 domain separator. function _buildDomainSeparator() private view returns (bytes32 separator) { bytes32 nameHash; bytes32 versionHash; if (_domainNameAndVersionMayChange()) { (string memory name, string memory version) = _domainNameAndVersion(); nameHash = keccak256(bytes(name)); versionHash = keccak256(bytes(version)); } else { nameHash = _cachedNameHash; versionHash = _cachedVersionHash; } /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Load the free memory pointer. mstore(m, _DOMAIN_TYPEHASH) mstore(add(m, 0x20), nameHash) mstore(add(m, 0x40), versionHash) mstore(add(m, 0x60), chainid()) mstore(add(m, 0x80), address()) separator := keccak256(m, 0xa0) } } /// @dev Returns if the cached domain separator has been invalidated. function _cachedDomainSeparatorInvalidated() private view returns (bool result) { uint256 cachedChainId = _cachedChainId; address cachedThis = _cachedThis; /// @solidity memory-safe-assembly assembly { result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis))) } } }
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.19; import {IERC165} from "openzeppelin-contracts/utils/introspection/IERC165.sol"; /// @title Enum - Collection of enums /// @author Richard Meissner - <[email protected]> contract Enum { enum Operation { Call, DelegateCall } } interface ISafeWallet { /// @dev Allows a Module to execute a Safe transaction without any further confirmations. /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModule(address to, uint256 value, bytes calldata data, Enum.Operation operation) external returns (bool success); /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModuleReturnData(address to, uint256 value, bytes memory data, Enum.Operation operation) external returns (bool success, bytes memory returnData); function getStorageAt(uint256 offset, uint256 length) external view returns (bytes memory); function isOwner(address owner) external view returns (bool); function nonce() external view returns (uint256); function getThreshold() external view returns (uint256); function isModuleEnabled(address module) external view returns (bool); function enableModule(address module) external; function disableModule(address prevModule, address module) external; function removeOwner(address prevOwner, address owner, uint256 _threshold) external; function swapOwner(address prevOwner, address oldOwner, address newOwner) external; function getOwners() external view returns (address[] memory); function approveHash(bytes32 hashToApprove) external; function signedMessages(bytes32 _dataHash) external returns (uint256 _signatures); function execTransaction( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures ) external payable returns (bool); function setup( address[] memory _owners, uint256 _threshold, address to, bytes memory data, address fallbackHandler, address paymentToken, uint256 payment, address paymentReceiver ) external; function addOwnerWithThreshold(address owner, uint256 _threshold) external; function domainSeparator() external view returns (bytes32); function setFallbackHandler(address _fallbackHandler) external; function setGuard(address guard) external; function encodeTransactionData( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) external view returns (bytes memory); } interface Guard is IERC165 { function checkTransaction( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures, address msgSender ) external; function checkAfterExecution(bytes32 txHash, bool success) external; }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {IAddressProviderService} from "../../interfaces/IAddressProviderService.sol"; import {AddressProvider} from "../core/AddressProvider.sol"; import {Constants} from "./Constants.sol"; /** * @title AddressProviderService * @author Brahma.fi * @notice Provides a base contract for services to resolve other services through AddressProvider * @dev This contract is designed to be inheritable by other contracts * Provides quick and easy access to all contracts in Console Ecosystem */ abstract contract AddressProviderService is IAddressProviderService, Constants { error InvalidAddressProvider(); error NotGovernance(address); error InvalidAddress(); /// @notice address of addressProvider // solhint-disable-next-line immutable-vars-naming AddressProvider public immutable addressProvider; address public immutable walletRegistry; address public immutable policyRegistry; address public immutable executorRegistry; constructor(address _addressProvider) { if (_addressProvider == address(0)) revert InvalidAddressProvider(); addressProvider = AddressProvider(_addressProvider); walletRegistry = addressProvider.getRegistry(_WALLET_REGISTRY_HASH); policyRegistry = addressProvider.getRegistry(_POLICY_REGISTRY_HASH); executorRegistry = addressProvider.getRegistry(_EXECUTOR_REGISTRY_HASH); _notNull(walletRegistry); _notNull(policyRegistry); _notNull(executorRegistry); } /** * @inheritdoc IAddressProviderService */ function addressProviderTarget() external view override returns (address) { return address(addressProvider); } /** * @notice Helper to get authorized address from address provider * @param _key keccak256 key corresponding to authorized address * @return authorizedAddress */ function _getAuthorizedAddress(bytes32 _key) internal view returns (address authorizedAddress) { authorizedAddress = addressProvider.getAuthorizedAddress(_key); _notNull(authorizedAddress); } /** * @notice Helper to revert if address is null * @param _addr address to check */ function _notNull(address _addr) internal pure { if (_addr == address(0)) revert InvalidAddress(); } }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {ISafeWallet, Enum} from "interfaces/external/ISafeWallet.sol"; import {PolicyValidator} from "src/core/PolicyValidator.sol"; import {SafeHelper} from "src/libraries/SafeHelper.sol"; import {AddressProviderService} from "src/core/AddressProviderService.sol"; import {WalletRegistry} from "src/core/registries/WalletRegistry.sol"; import {TypeHashHelper} from "src/libraries/TypeHashHelper.sol"; /** * @title TransactionValidator * @author Brahma.fi * @notice Allows validation of transactions pre and post execution */ contract TransactionValidator is AddressProviderService { error AccountNotFound(address); error InvalidGuard(); error InvalidFallbackHandler(); error InvalidModule(); error InvalidExecutorPlugin(); error TxnUnAuthorized(); /** * @notice datatype for safe transaction params * @param from address of safe * @param to target address * @param value txn value * @param data txn callData * @param operation type of operation * @param safeTxGas gas that should be used for safe txn * @param baseGas gas cost independent of txn cost * @param gasPrice gas price in current block * @param gasToken address of token used for gas * @param refundReceiver address of receiver of gas payment * @param signatures user signatures appended with validation signature * @param msgSender address of msg.sender of original txn */ struct SafeTransactionParams { Enum.Operation operation; address from; address to; address payable refundReceiver; address gasToken; address msgSender; uint256 value; uint256 safeTxGas; uint256 baseGas; uint256 gasPrice; bytes data; bytes signatures; } constructor(address _addressProvider) AddressProviderService(_addressProvider) {} /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSOLE GUARD HOOKS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /** * @notice Validates a txn on guard before execution, for Brahma console accounts * @dev checks for possible console overrides and then performs policy validation * @param txParams params of transaction */ function validatePreTransactionOverridable(SafeTransactionParams memory txParams) external view { // Validate policy _validatePolicySignature( TypeHashHelper.ExecutionParams({ to: txParams.to, value: txParams.value, data: txParams.data, operation: uint8(txParams.operation), account: txParams.from, executor: address(0), nonce: ISafeWallet(txParams.from).nonce(), safeTxGas: txParams.safeTxGas, baseGas: txParams.baseGas, gasPrice: txParams.gasPrice, gasToken: txParams.gasToken, refundReceiver: txParams.refundReceiver }), txParams.signatures ); } /* solhint-disable no-empty-blocks */ /** * @notice Provides on-chain guarantees on security critical expected states of a Brhma console account * @dev Empty hook available for future use */ function validatePostTransactionOverridable(bytes32, /*txHash */ bool, /*success */ address /*console */ ) external view {} /* solhint-enable no-empty-blocks */ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* SUBACCOUNT GUARD HOOKS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /** * @notice Validates a txn on guard before execution, for subAccounts * @dev calls policy validator to check if policy signature is valid * @param txParams params of transaction */ function validatePreTransaction(SafeTransactionParams memory txParams) external view { _validatePolicySignature( TypeHashHelper.ExecutionParams({ to: txParams.to, value: txParams.value, data: txParams.data, operation: uint8(txParams.operation), account: txParams.from, executor: address(0), nonce: ISafeWallet(txParams.from).nonce(), safeTxGas: txParams.safeTxGas, baseGas: txParams.baseGas, gasPrice: txParams.gasPrice, gasToken: txParams.gasToken, refundReceiver: txParams.refundReceiver }), txParams.signatures ); } /** * @notice Provides on-chain guarantees on security critical expected states of subAccount for guard * @param subAccount address of subAccount to validate */ function validatePostTransaction(bytes32, /*txHash */ bool, /*success */ address subAccount) external view { _checkSubAccountSecurityConfig(subAccount); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EXECUTOR PLUGIN GUARD HOOKS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /** * @notice Validates a module txn before execution * @dev calls policy validator to check if policy signature is valid * @param from address of safe * @param executionStructHash execution struct hash * @param signatures user signatures appended with validation signature */ function validatePreExecutorTransaction( address, /*msgSender */ address from, bytes32 executionStructHash, bytes calldata signatures ) external view { _validatePolicySignature(from, executionStructHash, signatures); } /** * @notice Provides on-chain guarantees on security critical expected states of account for executor plugin * @param account address of account to validate */ function validatePostExecutorTransaction(address, /*msgSender */ address account) external view { // Check if account has executor plugin still enabled as a module on it if (!ISafeWallet(account).isModuleEnabled(AddressProviderService._getAuthorizedAddress(_EXECUTOR_PLUGIN_HASH))) { revert InvalidExecutorPlugin(); } if (WalletRegistry(walletRegistry).isWallet(account)) { _checkConsoleAccountSecurityConfig(account); } else if (WalletRegistry(walletRegistry).subAccountToWallet(account) != address(0)) { _checkSubAccountSecurityConfig(account); } else { revert AccountNotFound(account); } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL METHODS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /** * @notice Internal helper to validate the module, guard and fallback handler for a subaccount * @dev Ensures that guard has not been disabled/updated and the owner console as a module has not been disabled * @param _subAccount address of subAccount */ function _checkSubAccountSecurityConfig(address _subAccount) internal view { address guard = SafeHelper._getGuard(_subAccount); address fallbackHandler = SafeHelper._getFallbackHandler(_subAccount); // Ensure guard has not been disabled if (guard != AddressProviderService._getAuthorizedAddress(_SAFE_MODERATOR_HASH)) revert InvalidGuard(); // Ensure fallback handler has not been altered if (fallbackHandler != AddressProviderService._getAuthorizedAddress(_CONSOLE_FALLBACK_HANDLER_HASH)) { revert InvalidFallbackHandler(); } address ownerConsole = WalletRegistry(walletRegistry).subAccountToWallet(_subAccount); // Ensure owner console as a module has not been disabled if (!ISafeWallet(_subAccount).isModuleEnabled(ownerConsole)) revert InvalidModule(); } /** * @notice Internal helper to validate the module, guard and fallback handler for a console account * @dev Ensures that guard has not been disabled/updated * @param _consoleAccount address of consoleAccount */ function _checkConsoleAccountSecurityConfig(address _consoleAccount) internal view { address guard = SafeHelper._getGuard(_consoleAccount); address fallbackHandler = SafeHelper._getFallbackHandler(_consoleAccount); // Ensure guard has not been disabled if (guard != AddressProviderService._getAuthorizedAddress(_SAFE_MODERATOR_OVERRIDABLE_HASH)) { revert InvalidGuard(); } // Ensure fallback handler has not been altered if (fallbackHandler != AddressProviderService._getAuthorizedAddress(_CONSOLE_FALLBACK_HANDLER_HASH)) { revert InvalidFallbackHandler(); } } /** * @notice Internal helper to validate policy signature for a safe txn * @dev Calls policy validator to check if policy signature is valid * @param _executionParams execution params struct * @param _signatures user signatures appended with validation signature */ function _validatePolicySignature(TypeHashHelper.ExecutionParams memory _executionParams, bytes memory _signatures) internal view { if ( !PolicyValidator(AddressProviderService._getAuthorizedAddress(_POLICY_VALIDATOR_HASH)).isPolicySignatureValid( _executionParams, _signatures ) ) { revert TxnUnAuthorized(); } } /** * @notice Internal helper to validate policy signature for a module txn * @dev Calls policy validator to check if policy signature is valid * @param _from address of safe * @param _executionStructHash execution struct hash * @param _signatures user signatures appended with validation signature */ function _validatePolicySignature(address _from, bytes32 _executionStructHash, bytes memory _signatures) internal view { if ( !PolicyValidator(AddressProviderService._getAuthorizedAddress(_POLICY_VALIDATOR_HASH)).isPolicySignatureValid( _from, _executionStructHash, _signatures ) ) { revert TxnUnAuthorized(); } } }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {AddressProviderService} from "../AddressProviderService.sol"; import {WalletRegistry} from "./WalletRegistry.sol"; import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol"; /** * @title ExecutorRegistry * @author Brahma.fi * @notice Registry for executors for sub accounts */ contract ExecutorRegistry is AddressProviderService { using EnumerableSet for EnumerableSet.AddressSet; error NotAuthorized(); error AlreadyExists(); error DoesNotExist(); error NoPolicyCommit(); event RegisterExecutor(address indexed _account, address indexed _authorizer, address indexed _executor); event DeRegisterExecutor(address indexed _account, address indexed _authorizer, address indexed _executor); /// @notice account addresses mapped to executor addresses mapping(address account => EnumerableSet.AddressSet) private accountExecutors; constructor(address _addressProvider) AddressProviderService(_addressProvider) {} /** * @notice Registers an executor for account * @dev Adds new executor if it doesn't already exists else reverts with AlreadyExists() * @dev Can be only called by main console * @param _account console/subaccount address to add executor to * @param _executor executor to add */ function registerExecutor(address _account, address _executor) external { _validateMsgSenderConsoleAccount(_account); if (!accountExecutors[_account].add(_executor)) revert AlreadyExists(); emit RegisterExecutor(_account, msg.sender, _executor); } /** * @notice De-registers an executor for console account/ subaccount * @dev removes an executor if it exists else reverts with DoesNotExist() * @dev Can be only called by main console * @param _account console/subaccount address to remove executor from * @param _executor executor to remove */ function deRegisterExecutor(address _account, address _executor) external { _validateMsgSenderConsoleAccount(_account); if (!accountExecutors[_account].remove(_executor)) revert DoesNotExist(); emit DeRegisterExecutor(_account, msg.sender, _executor); } /** * @notice checks if _executor is registered for _account * @param _account address of account * @param _executor address of executor * @return isExecutorValid */ function isExecutor(address _account, address _executor) external view returns (bool) { return accountExecutors[_account].contains(_executor); } /** * @return all the executors for a console account / subaccount * @param _account address of account */ function getExecutorsForAccount(address _account) external view returns (address[] memory) { return accountExecutors[_account].values(); } function _validateMsgSenderConsoleAccount(address _account) internal view { // msg.sender is console account if (msg.sender == _account && WalletRegistry(walletRegistry).isWallet(msg.sender)) return; // msg.sender is console account and owns the _account if (WalletRegistry(walletRegistry).subAccountToWallet(_account) == msg.sender) return; revert NotAuthorized(); } }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {Enum, ISafeWallet} from "interfaces/external/ISafeWallet.sol"; import {Types} from "interfaces/Types.sol"; /** * @title SafeHelper * @author Brahma.fi * @notice Helper library containing functions to interact with safe wallet */ library SafeHelper { error InvalidMultiSendInput(); error UnableToParseOperation(); /// @notice uint256(keccak256("guard_manager.guard.address")) /// @dev This refers to the storage slot where guard is stored in Safe's layout: https://github.com/safe-global/safe-contracts/blob/ff4c6761fbfae8ab8a94f36fd26bcfb2b5414eb1/contracts/base/GuardManager.sol#L77 uint256 internal constant _GUARD_STORAGE_SLOT = 33528237782592280163068556224972516439282563014722366175641814928123294921928; /// @notice uint256(keccak256("fallback_manager.handler.address")) /// @dev This refers to the storage slot where fallback handler is stored in Safe's layout: https://github.com/safe-global/safe-contracts/blob/ff4c6761fbfae8ab8a94f36fd26bcfb2b5414eb1/contracts/base/FallbackManager.sol#L14 uint256 internal constant _FALLBACK_HANDLER_STORAGE_SLOT = 49122629484629529244014240937346711770925847994644146912111677022347558721749; /** * @notice Contains hash for expected overridable guard removal calldata * @dev This is the hash of the calldata for the following function call * * abi.encodeCall(ISafeWallet.setGuard, (address(0))) = 0xe19a9dd90000000000000000000000000000000000000000000000000000000000000000 * keccak256(abi.encodeCall(ISafeWallet.setGuard, (address(0)))) = 0xc0e2c16ecb99419a40dd8b9c0b339b27acebd27c481a28cd606927aeb86f5079 */ bytes32 internal constant _GUARD_REMOVAL_CALLDATA_HASH = 0xc0e2c16ecb99419a40dd8b9c0b339b27acebd27c481a28cd606927aeb86f5079; /** * @notice Contains hash for expected overridable fallback handler removal calldata * @dev This is the hash of the calldata for the following function call * * abi.encodeCall(ISafeWallet.setFallbackHandler, (address(0))) = 0xf08a03230000000000000000000000000000000000000000000000000000000000000000 * keccak256(abi.encodeCall(ISafeWallet.setFallbackHandler, (address(0)))) = 0x5bdf8c44c012c1347b2b15694dc5cc39b899eb99e32614676b7661001be925b7 */ bytes32 internal constant _FALLBACK_REMOVAL_CALLDATA_HASH = 0x5bdf8c44c012c1347b2b15694dc5cc39b899eb99e32614676b7661001be925b7; /** * @notice Packs multiple executables into a single bytes array compatible with Safe's MultiSend contract which can be used as argument for multicall method * @dev Reference contract at https://github.com/safe-global/safe-contracts/blob/main/contracts/libraries/MultiSend.sol * @param _txns Array of executables to pack * @return packedTxns bytes array containing packed transactions */ function _packMultisendTxns(Types.Executable[] memory _txns) internal pure returns (bytes memory packedTxns) { uint256 len = _txns.length; if (len == 0) revert InvalidMultiSendInput(); uint256 i = 0; do { uint8 call = uint8(_parseOperationEnum(_txns[i].callType)); uint256 calldataLength = _txns[i].data.length; bytes memory encodedTxn = abi.encodePacked( bytes1(call), bytes20(_txns[i].target), bytes32(_txns[i].value), bytes32(calldataLength), _txns[i].data ); if (i != 0) { // If not first transaction, append to packedTxns packedTxns = abi.encodePacked(packedTxns, encodedTxn); } else { // If first transaction, set packedTxns to encodedTxn packedTxns = encodedTxn; } unchecked { ++i; } } while (i < len); } /** * @notice Gets the guard for a safe * @param safe address of safe * @return address of guard, address(0) if no guard exists */ function _getGuard(address safe) internal view returns (address) { bytes memory guardAddress = ISafeWallet(safe).getStorageAt(_GUARD_STORAGE_SLOT, 1); return address(uint160(uint256(bytes32(guardAddress)))); } /** * @notice Gets the fallback handler for a safe * @param safe address of safe * @return address of fallback handler, address(0) if no fallback handler exists */ function _getFallbackHandler(address safe) internal view returns (address) { bytes memory fallbackHandlerAddress = ISafeWallet(safe).getStorageAt(_FALLBACK_HANDLER_STORAGE_SLOT, 1); return address(uint160(uint256(bytes32(fallbackHandlerAddress)))); } /** * @notice Converts a CallType enum to an Operation enum. * @dev Reverts with UnableToParseOperation error if the CallType is not supported. * @param callType The CallType enum to be converted. * @return operation The converted Operation enum. */ function _parseOperationEnum(Types.CallType callType) internal pure returns (Enum.Operation operation) { if (callType == Types.CallType.DELEGATECALL) { operation = Enum.Operation.DelegateCall; } else if (callType == Types.CallType.CALL) { operation = Enum.Operation.Call; } else { revert UnableToParseOperation(); } } }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; /** * @title TypeHashHelper * @author Brahma.fi * @notice Helper library containing functions to build EIP712 struct and type hashes */ library TypeHashHelper { /** * @notice Structural representation of execution details * @param operation type of operation * @param to address to send tx to * @param account address of safe * @param executor address of executor if executed via executor plugin, address(0) if executed via execTransaction * @param gasToken address of token used for gas * @param refundReceiver address of receiver of gas payment * @param value txn value * @param nonce txn nonce * @param safeTxGas gas that should be used for safe txn * @param baseGas gas cost independent of txn cost * @param gasPrice gas price in current block * @param data txn callData */ struct ExecutionParams { uint8 operation; address to; address account; address executor; address gasToken; address refundReceiver; uint256 value; uint256 nonce; uint256 safeTxGas; uint256 baseGas; uint256 gasPrice; bytes data; } /** * @notice Type of validation struct to hash * @param expiryEpoch max time till validity of the signature * @param executionStructHash txn digest generated using TypeHashHelper._buildExecutionParamsHash() * @param policyHash policy commit hash of the safe account */ struct ValidationParams { uint32 expiryEpoch; bytes32 executionStructHash; bytes32 policyHash; } /** * @notice EIP712 typehash for execution params data * @dev keccak256("ExecutionParams(uint8 operation,address to,address account,address executor,address gasToken,address refundReceiver,uint256 value,uint256 nonce,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,bytes data)"); */ bytes32 public constant EXECUTION_PARAMS_TYPEHASH = 0x483ad580f0a8d7881e792d04b2128f3b214b18aa7336126dc2e77a59752bd6f5; /** * @notice EIP712 typehash for validation data * @dev keccak256("ValidationParams(uint32 expiryEpoch,ExecutionParams executionParams,bytes32 policyHash)ExecutionParams(uint8 operation,address to,address account,address executor,address gasToken,address refundReceiver,uint256 value,uint256 nonce,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,bytes data)"); */ bytes32 public constant VALIDATION_PARAMS_TYPEHASH = 0x37af4ddfcab5e0a0d11676ce89534ca59ffac1c30a5a4cc21f840d2f4704a952; /** * @notice Builds EIP712 execution struct hash * @param txn execution params struct * @return executionStructHash */ function _buildExecutionParamsHash(ExecutionParams memory txn) internal pure returns (bytes32) { return keccak256( abi.encode( EXECUTION_PARAMS_TYPEHASH, txn.operation, txn.to, txn.account, txn.executor, txn.gasToken, txn.refundReceiver, txn.value, txn.nonce, txn.safeTxGas, txn.baseGas, txn.gasPrice, keccak256(txn.data) ) ); } /** * @notice Builds EIP712 validation struct hash * @param validation validation params struct * @return validationStructHash */ function _buildValidationParamsHash(ValidationParams memory validation) internal pure returns (bytes32) { return keccak256( abi.encode( VALIDATION_PARAMS_TYPEHASH, validation.expiryEpoch, validation.executionStructHash, validation.policyHash ) ); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.19; interface Types { enum CallType { CALL, DELEGATECALL, STATICCALL } struct Executable { CallType callType; address target; uint256 value; bytes data; } struct TokenRequest { address token; uint256 amount; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.19; interface IAddressProviderService { /// @notice Returns the address of the AddressProvider function addressProviderTarget() external view returns (address); }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {IAddressProviderService} from "interfaces/IAddressProviderService.sol"; import {Constants} from "src/core/Constants.sol"; /** * @title AddressProvider * @author Brahma.fi * @notice Single source of truth for resolving addresses of core components and external contracts */ contract AddressProvider is Constants { error RegistryAlreadyExists(); error AddressProviderUnsupported(); error NotGovernance(address); error NotPendingGovernance(address); error NullAddress(); event RegistryInitialised(address indexed registry, bytes32 indexed key); event AuthorizedAddressInitialised(address indexed authorizedAddress, bytes32 indexed key); event GovernanceTransferRequested(address indexed previousGovernance, address indexed newGovernance); event GovernanceTransferred(address indexed previousGovernance, address indexed newGovernance); /// @notice address of governance address public governance; /// @notice address of pending governance before accepting address public pendingGovernance; /** * @notice keccak256 hash of authorizedAddress keys mapped to their addresses * @dev Core & Roles are used as keys for this mapping. These addresses are mutable * @dev authorizedAddresses are updatable by governance */ mapping(bytes32 => address) internal authorizedAddresses; /** * @notice keccak256 hash of registry keys mapped to their addresses * @dev registries are only set once by governance and immutable */ mapping(bytes32 => address) internal registries; constructor(address _governance, address walletRegistry, address policyRegistry, address executorRegistry) { _notNull(_governance); governance = _governance; _notNull(walletRegistry); _notNull(policyRegistry); _notNull(executorRegistry); registries[_WALLET_REGISTRY_HASH] = walletRegistry; registries[_POLICY_REGISTRY_HASH] = policyRegistry; registries[_EXECUTOR_REGISTRY_HASH] = executorRegistry; } /** * @notice Governance setter * @param _newGovernance address of new governance */ function setGovernance(address _newGovernance) external { _notNull(_newGovernance); _onlyGov(); emit GovernanceTransferRequested(governance, _newGovernance); pendingGovernance = _newGovernance; } /** * @notice Governance accepter */ function acceptGovernance() external { if (msg.sender != pendingGovernance) { revert NotPendingGovernance(msg.sender); } emit GovernanceTransferred(governance, msg.sender); governance = msg.sender; delete pendingGovernance; } /** * @notice Authorized address setter * @param _key key of authorizedAddress * @param _authorizedAddress address to set * @param _overrideCheck overrides check for supported address provider */ function setAuthorizedAddress(bytes32 _key, address _authorizedAddress, bool _overrideCheck) external { _onlyGov(); _notNull(_authorizedAddress); /// @dev skips checks for supported `addressProvider()` if `_overrideCheck` is true if (!_overrideCheck) { /// @dev skips checks for supported `addressProvider()` if `_authorizedAddress` is an EOA if (_authorizedAddress.code.length != 0) _ensureAddressProvider(_authorizedAddress); } authorizedAddresses[_key] = _authorizedAddress; emit AuthorizedAddressInitialised(_authorizedAddress, _key); } /** * @notice Registry address setter * @param _key key of registry address * @param _registry address to set */ function setRegistry(bytes32 _key, address _registry) external { _onlyGov(); _ensureAddressProvider(_registry); if (registries[_key] != address(0)) revert RegistryAlreadyExists(); registries[_key] = _registry; emit RegistryInitialised(_registry, _key); } /** * @notice Authorized address getter * @param _key key of authorized address * @return address of authorized address */ function getAuthorizedAddress(bytes32 _key) external view returns (address) { return authorizedAddresses[_key]; } /** * @notice Registry address getter * @param _key key of registry address * @return address of registry address */ function getRegistry(bytes32 _key) external view returns (address) { return registries[_key]; } /** * @notice Ensures that the new address supports the AddressProviderService interface * and is pointing to this AddressProvider * @param _newAddress address to check */ function _ensureAddressProvider(address _newAddress) internal view { if (IAddressProviderService(_newAddress).addressProviderTarget() != address(this)) { revert AddressProviderUnsupported(); } } /** * @notice Checks if msg.sender is governance */ function _onlyGov() internal view { if (msg.sender != governance) revert NotGovernance(msg.sender); } /** * @notice Checks and reverts if address is null * @param addr address to check if null */ function _notNull(address addr) internal pure { if (addr == address(0)) revert NullAddress(); } }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; /** * @title Constants * @author Brahma.fi * @notice Contains constants used by multiple contracts * @dev Inflates bytecode size by approximately 560 bytes on deployment, but saves gas on runtime */ abstract contract Constants { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* REGISTRIES */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @notice Key to map address of ExecutorRegistry bytes32 internal constant _EXECUTOR_REGISTRY_HASH = bytes32(uint256(keccak256("console.core.ExecutorRegistry")) - 1); /// @notice Key to map address of WalletRegistry bytes32 internal constant _WALLET_REGISTRY_HASH = bytes32(uint256(keccak256("console.core.WalletRegistry")) - 1); /// @notice Key to map address of PolicyRegistry bytes32 internal constant _POLICY_REGISTRY_HASH = bytes32(uint256(keccak256("console.core.PolicyRegistry")) - 1); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CORE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @notice Key to map address of ExecutorPlugin bytes32 internal constant _EXECUTOR_PLUGIN_HASH = bytes32(uint256(keccak256("console.core.ExecutorPlugin")) - 1); /// @notice Key to map address of ConsoleFallbackHandler bytes32 internal constant _CONSOLE_FALLBACK_HANDLER_HASH = bytes32(uint256(keccak256("console.core.FallbackHandler")) - 1); /// @notice Key to map address of Safe FallbackHandler bytes32 internal constant _SAFE_FALLBACK_HANDLER_HASH = bytes32(uint256(keccak256("safe.FallbackHandler")) - 1); /// @notice Key to map address of Safe MultiSend bytes32 internal constant _SAFE_MULTI_SEND_HASH = bytes32(uint256(keccak256("safe.MultiSend")) - 1); /// @notice Key to map address of SafeProxyFactory bytes32 internal constant _SAFE_PROXY_FACTORY_HASH = bytes32(uint256(keccak256("safe.ProxyFactory")) - 1); /// @notice Key to map address of SafeSingleton bytes32 internal constant _SAFE_SINGLETON_HASH = bytes32(uint256(keccak256("safe.Singleton")) - 1); /// @notice Key to map address of PolicyValidator bytes32 internal constant _POLICY_VALIDATOR_HASH = bytes32(uint256(keccak256("console.core.PolicyValidator")) - 1); /// @notice Key to map address of SafeDeployer bytes32 internal constant _SAFE_DEPLOYER_HASH = bytes32(uint256(keccak256("console.core.SafeDeployer")) - 1); /// @notice Key to map address of SafeEnabler bytes32 internal constant _SAFE_ENABLER_HASH = bytes32(uint256(keccak256("console.core.SafeEnabler")) - 1); /// @notice Key to map address of SafeModerator bytes32 internal constant _SAFE_MODERATOR_HASH = bytes32(uint256(keccak256("console.core.SafeModerator")) - 1); /// @notice Key to map address of SafeModeratorOverridable bytes32 internal constant _SAFE_MODERATOR_OVERRIDABLE_HASH = bytes32(uint256(keccak256("console.core.SafeModeratorOverridable")) - 1); /// @notice Key to map address of TransactionValidator bytes32 internal constant _TRANSACTION_VALIDATOR_HASH = bytes32(uint256(keccak256("console.core.TransactionValidator")) - 1); /// @notice Key to map address of ConsoleOpBuilder bytes32 internal constant _CONSOLE_OP_BUILDER_HASH = bytes32(uint256(keccak256("console.core.ConsoleOpBuilder")) - 1); /// @notice Key to map address of ExecutionBlocker bytes32 internal constant _EXECUTION_BLOCKER_HASH = bytes32(uint256(keccak256("console.core.ExecutionBlocker")) - 1); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ROLES */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @notice Key to map address of Role PolicyAuthenticator bytes32 internal constant _POLICY_AUTHENTICATOR_HASH = bytes32(uint256(keccak256("console.roles.PolicyAuthenticator")) - 1); }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol"; import {EIP712} from "solady/utils/EIP712.sol"; import {AddressProviderService} from "src/core/AddressProviderService.sol"; import {PolicyRegistry} from "src/core/registries/PolicyRegistry.sol"; import {TypeHashHelper} from "src/libraries/TypeHashHelper.sol"; import {ISafeWallet, Enum} from "interfaces/external/ISafeWallet.sol"; /** * @title PolicyValidator * @author Brahma.fi * @notice Responsible for validating policy signatures for safe transactions */ contract PolicyValidator is AddressProviderService, EIP712 { error InvalidSignature(); error NoPolicyCommit(); error TxnExpired(uint32 expiryEpoch); error InvalidSignatures(); /// @notice EIP712 domain name string private constant _NAME = "PolicyValidator"; /// @notice EIP712 domain version string private constant _VERSION = "1.0"; constructor(address _addressProvider) AddressProviderService(_addressProvider) {} /** * @notice generates digest and validates signature against policies for safe transaction * @dev The POLICY_AUTHENTICATOR is expected to sign EIP712 digest generated from the following struct: * TypeHashHelper.ValidationParams, where - * txnDigest = EIP712 digest generated from struct: TypeHashHelper.ExecutionParams * policyHash = policy commit hash of the safe account * expiryEpoch = expiry timestamp * * @dev signatures = abi.encodePacked(safeSignature, validatorSignature, validatorSignatureLength, expiryEpoch) * safeSignature = safe owners signatures (arbitrary bytes length) * validatorSignature = EIP 712 digest signature (arbitrary bytes length) * validatorSignatureLength = length of `validatorSignature` (4 bytes) * expiryEpoch = expiry timestamp (4 bytes) * * @param executionParams execution params struct * @param signatures user signatures appended with validation signature * @return isSignatureValid boolean */ function isPolicySignatureValid(TypeHashHelper.ExecutionParams memory executionParams, bytes calldata signatures) external view returns (bool) { // Build transaction struct hash bytes32 executionStructHash = TypeHashHelper._buildExecutionParamsHash(executionParams); // Validate signature return isPolicySignatureValid(executionParams.account, executionStructHash, signatures); } /** * @notice generates digest and validates signature against policies for module execution * @dev signatures = abi.encodePacked(safeSignature, validatorSignature, validatorSignatureLength, expiryEpoch) * safeSignature = safe owners signatures (arbitrary bytes length) * validatorSignature = EIP 712 digest signed by `POLICY_AUTHENTICATOR`(arbitrary bytes length) * validatorSignatureLength = length of `validatorSignature` (4 bytes) * expiryEpoch = expiry timestamp (4 bytes) * Here, * The `POLICY_AUTHENTICATOR` is expected to sign the EIP 712 digest generated from following struct: * TypeHashHelper.ValidationParams - * txnDigest = EIP712 digest generated from struct: TypeHashHelper.ExecutionParams, with valid executor * policyHash = policy commit hash of the safe account * expiryEpoch = expiry timestamp * * @param account address of account to validate txn for * @param executionStructHash execution digest from ExecutorPlugin * @param signatures user signatures appended with validation signature * @return isSignatureValid boolean */ function isPolicySignatureValid(address account, bytes32 executionStructHash, bytes calldata signatures) public view returns (bool) { // Get policy hash from registry bytes32 policyHash = PolicyRegistry(policyRegistry).commitments(account); if (policyHash == bytes32(0)) { revert NoPolicyCommit(); } // Get expiry epoch and validator signature from signatures (uint32 expiryEpoch, bytes memory validatorSignature) = _decompileSignatures(signatures); // Ensure transaction has not expired if (expiryEpoch < uint32(block.timestamp)) { revert TxnExpired(expiryEpoch); } // Build validation struct hash bytes32 validationStructHash = TypeHashHelper._buildValidationParamsHash( TypeHashHelper.ValidationParams({ executionStructHash: executionStructHash, policyHash: policyHash, expiryEpoch: expiryEpoch }) ); // Build EIP712 digest with validation struct hash bytes32 txnValidityDigest = _hashTypedData(validationStructHash); address policyAuthenticator = AddressProviderService._getAuthorizedAddress(_POLICY_AUTHENTICATOR_HASH); // Empty Signature check for EOA signer if (validatorSignature.length == 0) { uint256 _codesize; assembly { _codesize := extcodesize(policyAuthenticator) } if (_codesize == 0) { // PolicyAuthenticator is an EOA and no policyAuthenticator signature is provided revert InvalidSignature(); } } // Validate signature return SignatureCheckerLib.isValidSignatureNow(policyAuthenticator, txnValidityDigest, validatorSignature); } /** * @notice Internal helper to extract validity signature from overall safe transaction signature * @dev _signatures = abi.encodePacked(safeSignature, validatorSignature, validatorSignatureLength, expiryEpoch) * safeSignature = safe owners signatures (arbitrary bytes length) * validatorSignature = EIP 712 digest signed (arbitrary bytes length) * validatorSignatureLength = length of `validatorSignature` (4 bytes) * expiryEpoch = expiry timestamp (4 bytes) * * @param _signatures packed transaction signature * @return expiryEpoch extracted expiry epoch signed by brahma backend * @return validatorSignature extracted validity signature */ function _decompileSignatures(bytes calldata _signatures) internal pure returns (uint32 expiryEpoch, bytes memory validatorSignature) { if (_signatures.length < 8) revert InvalidSignatures(); uint32 sigLength = uint32(bytes4(_signatures[_signatures.length - 8:_signatures.length - 4])); if (_signatures.length - 8 < sigLength) revert InvalidSignatures(); expiryEpoch = uint32(bytes4(_signatures[_signatures.length - 4:_signatures.length])); validatorSignature = _signatures[_signatures.length - 8 - sigLength:_signatures.length - 8]; } /** * @notice Internal helper to get EIP712 domain name and version * @return name domainName * @return version domainVersion */ function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { return (_NAME, _VERSION); } }
/// SPDX-License-Identifier: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {AddressProviderService} from "../AddressProviderService.sol"; /** * @title WalletRegistry * @author Brahma.fi * @notice Registry for wallet and sub account addresses */ contract WalletRegistry is AddressProviderService { error AlreadyRegistered(); error InvalidSender(); error IsSubAccount(); event RegisterWallet(address indexed wallet); event RegisterSubAccount(address indexed wallet, address indexed subAccount); /// @notice subAccount addresses mapped to owner wallet mapping(address subAccount => address wallet) public subAccountToWallet; /// @notice wallet addresses mapped to list of subAccounts mapping(address wallet => address[] subAccountList) public walletToSubAccountList; /// @notice address of wallet mapped to boolean indicating if it's a wallet mapping(address => bool) public isWallet; constructor(address _addressProvider) AddressProviderService(_addressProvider) {} /** * @notice Registers a wallet * @dev Can only be called by wallet to register itself */ function registerWallet() external { if (isWallet[msg.sender]) revert AlreadyRegistered(); if (subAccountToWallet[msg.sender] != address(0)) revert IsSubAccount(); isWallet[msg.sender] = true; emit RegisterWallet(msg.sender); } /** * @notice Registers a sub account for a Safe * @param _wallet Console account address, owner of sub account * @param _subAccount Sub account address to register * @dev Can only be called by safe deployer */ function registerSubAccount(address _wallet, address _subAccount) external { if (msg.sender != AddressProviderService._getAuthorizedAddress(_SAFE_DEPLOYER_HASH)) revert InvalidSender(); if (subAccountToWallet[_subAccount] != address(0) || isWallet[_subAccount]) revert AlreadyRegistered(); subAccountToWallet[_subAccount] = _wallet; walletToSubAccountList[_wallet].push(_subAccount); emit RegisterSubAccount(_wallet, _subAccount); } /** * @notice sub account list getter * @dev returns sub account list associated with _wallet * @param _wallet safe address * @return list of subAccounts for wallet */ function getSubAccountsForWallet(address _wallet) external view returns (address[] memory) { return walletToSubAccountList[_wallet]; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. pragma solidity ^0.8.0; /** * @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 of the value in the `values` array, plus 1 because index 0 // means a value is not in the set. mapping(bytes32 => uint256) _indexes; } /** * @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._indexes[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 read and store the value's index to prevent multiple reads from the same storage slot uint256 valueIndex = set._indexes[value]; if (valueIndex != 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 toDeleteIndex = valueIndex - 1; uint256 lastIndex = set._values.length - 1; if (lastIndex != toDeleteIndex) { bytes32 lastValue = set._values[lastIndex]; // Move the last value to the index where the value to delete is set._values[toDeleteIndex] = lastValue; // Update the index for the moved value set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex } // Delete the slot where the moved value was stored set._values.pop(); // Delete the index for the deleted slot delete set._indexes[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._indexes[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: BUSL-1.1 /// Copyright (C) 2023 Brahma.fi pragma solidity 0.8.19; import {AddressProviderService} from "src/core/AddressProviderService.sol"; import {WalletRegistry} from "src/core/registries/WalletRegistry.sol"; /** * @title PolicyRegistry * @author Brahma.fi * @notice Registry for policy commits for wallets and sub accounts */ contract PolicyRegistry is AddressProviderService { error PolicyCommitInvalid(); error UnauthorizedPolicyUpdate(); event UpdatedPolicyCommit(address indexed account, bytes32 policyCommit, bytes32 oldPolicyCommit); /// @notice account addresses mapped to their policy commits mapping(address account => bytes32 policyCommit) public commitments; constructor(address _addressProvider) AddressProviderService(_addressProvider) {} /** * @notice Enables setting policy commits for accounts * @param account address of account to set policy commit for * @param policyCommit policy commit hash to set * @dev policyCommit for an account can be set by: * 1. by safe deployer, if the account is uninitialized * 2. by the registered wallet, if the account is a subAccount * 3. by the account itself, if account is a registered wallet */ function updatePolicy(address account, bytes32 policyCommit) external { if (policyCommit == bytes32(0)) { revert PolicyCommitInvalid(); } bytes32 currentCommit = commitments[account]; // solhint-disable no-empty-blocks if ( currentCommit == bytes32(0) && msg.sender == AddressProviderService._getAuthorizedAddress(_SAFE_DEPLOYER_HASH) ) { // In case invoker is safe deployer } else { if (WalletRegistry(walletRegistry).subAccountToWallet(account) == msg.sender) { //In case invoker is updating on behalf of sub account } else if (msg.sender == account && WalletRegistry(walletRegistry).isWallet(msg.sender)) { // In case invoker is a registered wallet } else { revert UnauthorizedPolicyUpdate(); } } // solhint-enable no-empty-blocks _updatePolicy(account, policyCommit, currentCommit); } /** * @notice Internal function to update policy commit for an account * @param account address of account to set policy commit for * @param policyCommit policy commit hash to set */ function _updatePolicy(address account, bytes32 policyCommit, bytes32 oldPolicyCommit) internal { emit UpdatedPolicyCommit(account, policyCommit, oldPolicyCommit); commitments[account] = policyCommit; } }
{ "remappings": [ "ds-test/=lib/forge-std/lib/ds-test/src/", "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", "forge-std/=lib/forge-std/src/", "openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/", "openzeppelin/=lib/openzeppelin-contracts/contracts/", "safe-contracts/=lib/safe-contracts/contracts/", "solady/=lib/solady/src/", "solmate/=lib/solady/lib/solmate/src/", "solidity-bytes-utils/=lib/solidity-bytes-utils/contracts/" ], "optimizer": { "enabled": true, "runs": 20000 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "paris", "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_addressProvider","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidAddressProvider","type":"error"},{"inputs":[],"name":"InvalidExecutor","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"ModuleExecutionFailed","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"NotGovernance","type":"error"},{"inputs":[],"name":"UnableToParseOperation","type":"error"},{"inputs":[],"name":"addressProvider","outputs":[{"internalType":"contract AddressProvider","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"addressProviderTarget","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"enum Types.CallType","name":"callType","type":"uint8"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Types.Executable","name":"exec","type":"tuple"},{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"executor","type":"address"},{"internalType":"bytes","name":"executorSignature","type":"bytes"},{"internalType":"bytes","name":"validatorSignature","type":"bytes"}],"internalType":"struct ExecutorPlugin.ExecutionRequest","name":"execRequest","type":"tuple"}],"name":"executeTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"executor","type":"address"}],"name":"executorNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"executorRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"policyRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"walletRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
6101a06040523480156200001257600080fd5b5060405162001a1f38038062001a1f833981016040819052620000359162000388565b806001600160a01b0381166200005e5760405163186b216f60e11b815260040160405180910390fd5b6001600160a01b038116608081905263e51fd7a66200009f60017f70dc150361dabd9d041fabc7ce344e2a2f31a73e202f37c7ba3188518a32c207620003ba565b60405160e083901b6001600160e01b03191681526004810191909152602401602060405180830381865afa158015620000dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000102919062000388565b6001600160a01b0390811660a0526080511663e51fd7a66200014660017f8547df78c054d57ec09e2772223da2229912d59e65dbd44da39709b40d8c40c3620003ba565b60405160e083901b6001600160e01b03191681526004810191909152602401602060405180830381865afa15801562000183573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001a9919062000388565b6001600160a01b0390811660c0526080511663e51fd7a6620001ed60017f25c3a50f208295151157a1115daca4d2b545fb99d7e2f46dca323d4edca762f2620003ba565b60405160e083901b6001600160e01b03191681526004810191909152602401602060405180830381865afa1580156200022a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000250919062000388565b6001600160a01b031660e05260a0516200026a906200035d565b60c05162000278906200035d565b60e05162000286906200035d565b50600160005530610100524661012052606080620002a2600090565b620002f357620002ed604080518082018252600e81526d22bc32b1baba37b928363ab3b4b760911b602080830191909152825180840190935260038352620312e360ec1b9083015291565b90925090505b8151602092830120815191830191909120610140829052610160819052604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8152938401929092529082015246606082015230608082015260a090206101805250620003e2565b6001600160a01b038116620003855760405163e6c4247b60e01b815260040160405180910390fd5b50565b6000602082840312156200039b57600080fd5b81516001600160a01b0381168114620003b357600080fd5b9392505050565b81810381811115620003dc57634e487b7160e01b600052601160045260246000fd5b92915050565b60805160a05160c05160e05161010051610120516101405161016051610180516115b9620004666000396000610cca01526000610d8401526000610d5e01526000610d0e01526000610ceb0152600081816101cb015261051e015260006092015260006101a401526000818160e001528181610109015261047c01526115b96000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c80634611b4c71161005b5780634611b4c71461014b57806384b0196e14610184578063ab7aa6ad1461019f578063b1cebbe0146101c657600080fd5b80631c4dd7d01461008d57806321b1e480146100de5780632954018c146101045780632bf4762b1461012b575b600080fd5b6100b47f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b7f00000000000000000000000000000000000000000000000000000000000000006100b4565b6100b47f000000000000000000000000000000000000000000000000000000000000000081565b61013e610139366004610ea7565b6101ed565b6040516100d59190610f50565b610176610159366004610f85565b600160209081526000928352604080842090915290825290205481565b6040519081526020016100d5565b61018c61032a565b6040516100d59796959493929190610fbe565b6100b47f000000000000000000000000000000000000000000000000000000000000000081565b6100b47f000000000000000000000000000000000000000000000000000000000000000081565b60606101f76103d3565b600061022c61022760017f28c61107d4cd3a85ca89b185e44bf7402301acc30bf1bcf06b0e5c9d61063bdd6110ac565b61044a565b90506102388382610507565b600061026561024d60408601602087016110bf565b61025786806110dc565b61026090611216565b6109dd565b905073ffffffffffffffffffffffffffffffffffffffff821663dcf297313361029460408801602089016110bf565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff92831660048201529116602482015260440160006040518083038186803b1580156102ff57600080fd5b505afa158015610313573d6000803e3d6000fd5b50929450505050506103256001600055565b919050565b7f0f0000000000000000000000000000000000000000000000000000000000000060608060008080836103c1604080518082018252600e81527f4578656375746f72506c7567696e0000000000000000000000000000000000006020808301919091528251808401909352600383527f312e3000000000000000000000000000000000000000000000000000000000009083015291565b97989097965046955030945091925090565b600260005403610443576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015260640160405180910390fd5b6002600055565b6040517f2bf84475000000000000000000000000000000000000000000000000000000008152600481018290526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690632bf8447590602401602060405180830381865afa1580156104d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fc91906112c7565b905061032581610ae3565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016639c84cc4861055360408501602086016110bf565b61056360608601604087016110bf565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105f791906112f4565b61062d576040517f710c949700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61063d60608301604084016110bf565b73ffffffffffffffffffffffffffffffffffffffff163b15801561066d5750610669606083018361130f565b1590505b156106a4576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805161018081019091526000906108ac90806106d86106c587806110dc565b6106d390602081019061137b565b610b33565b60018111156106e9576106e9611396565b60ff1681526020016106fb86806110dc565b61070c9060408101906020016110bf565b73ffffffffffffffffffffffffffffffffffffffff16815260200185602001602081019061073a91906110bf565b73ffffffffffffffffffffffffffffffffffffffff16815260200161076560608701604088016110bf565b73ffffffffffffffffffffffffffffffffffffffff168152600060208201819052604082015260600161079886806110dc565b604001358152602001600160008760200160208101906107b891906110bf565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600087604001602081019061080791906110bf565b73ffffffffffffffffffffffffffffffffffffffff1681526020810191909152604001600090812080549161083b836113c5565b90915550815260006020820181905260408201819052606082015260800161086386806110dc565b61087190606081019061130f565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050915250610ba9565b905060006108b982610cc6565b90506109166108ce60608601604087016110bf565b826108dc606088018861130f565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610de192505050565b61094c576040517f710c949700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316635a8793943361097960408801602089016110bf565b8561098760808a018a61130f565b6040518663ffffffff1660e01b81526004016109a79594939291906113fd565b60006040518083038186803b1580156109bf57600080fd5b505afa1580156109d3573d6000803e3d6000fd5b5050505050505050565b60606000808473ffffffffffffffffffffffffffffffffffffffff16635229073f856020015186604001518760600151610a1a8960000151610b33565b6040518563ffffffff1660e01b8152600401610a39949392919061147c565b6000604051808303816000875af1158015610a58573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a9e91908101906114f9565b9150915081610ad9576040517fcce1466600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9150505b92915050565b73ffffffffffffffffffffffffffffffffffffffff8116610b30576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b60006001826002811115610b4957610b49611396565b03610b5657506001919050565b6000826002811115610b6a57610b6a611396565b03610b7757506000919050565b6040517f664a3ff500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80516020808301516040808501516060860151608087015160a088015160c089015160e08a01516101008b01516101208c01516101408d01516101608e01518051908d0120995160009d610ca99d7f483ad580f0a8d7881e792d04b2128f3b214b18aa7336126dc2e77a59752bd6f59d919c919b9a9998979695949392019c8d5260ff9b909b1660208d015273ffffffffffffffffffffffffffffffffffffffff998a1660408d015297891660608c015295881660808b015293871660a08a01529190951660c088015260e08701949094526101008601939093526101208501929092526101408401919091526101608301526101808201526101a00190565b604051602081830303815290604052805190602001209050919050565b60007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000030147f0000000000000000000000000000000000000000000000000000000000000000461416610dbb5750604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81527f000000000000000000000000000000000000000000000000000000000000000060208201527f00000000000000000000000000000000000000000000000000000000000000009181019190915246606082015230608082015260a090205b67190100000000000060005280601a5282603a52604260182091506000603a5250919050565b73ffffffffffffffffffffffffffffffffffffffff9092169160008315610ea0576040516041835103610e575783600052606083015160001a60205260208301516040526040830151606052602060016080600060015afa805186183d1517610e5557506000606052604052506001610ea0565b505b600060605280604052631626ba7e60e01b808252846004830152602482016040815284516020018060448501828860045afa505060208160443d01858a5afa9051909114169150505b9392505050565b600060208284031215610eb957600080fd5b813567ffffffffffffffff811115610ed057600080fd5b820160a08185031215610ea057600080fd5b60005b83811015610efd578181015183820152602001610ee5565b50506000910152565b60008151808452610f1e816020860160208601610ee2565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610ea06020830184610f06565b73ffffffffffffffffffffffffffffffffffffffff81168114610b3057600080fd5b60008060408385031215610f9857600080fd5b8235610fa381610f63565b91506020830135610fb381610f63565b809150509250929050565b7fff00000000000000000000000000000000000000000000000000000000000000881681526000602060e081840152610ffa60e084018a610f06565b838103604085015261100c818a610f06565b6060850189905273ffffffffffffffffffffffffffffffffffffffff8816608086015260a0850187905284810360c0860152855180825283870192509083019060005b8181101561106b5783518352928401929184019160010161104f565b50909c9b505050505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610add57610add61107d565b6000602082840312156110d157600080fd5b8135610ea081610f63565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8183360301811261111057600080fd5b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516080810167ffffffffffffffff8111828210171561116c5761116c61111a565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156111b9576111b961111a565b604052919050565b80356003811061032557600080fd5b600067ffffffffffffffff8211156111ea576111ea61111a565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b60006080823603121561122857600080fd5b611230611149565b611239836111c1565b815260208084013561124a81610f63565b8282015260408481013590830152606084013567ffffffffffffffff81111561127257600080fd5b840136601f82011261128357600080fd5b8035611296611291826111d0565b611172565b81815236848385010111156112aa57600080fd5b818484018583013760009181019093015250606082015292915050565b6000602082840312156112d957600080fd5b8151610ea081610f63565b8051801515811461032557600080fd5b60006020828403121561130657600080fd5b610ea0826112e4565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261134457600080fd5b83018035915067ffffffffffffffff82111561135f57600080fd5b60200191503681900382131561137457600080fd5b9250929050565b60006020828403121561138d57600080fd5b610ea0826111c1565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036113f6576113f661107d565b5060010190565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525084604083015260806060830152826080830152828460a0840137600060a0848401015260a07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f85011683010190509695505050505050565b73ffffffffffffffffffffffffffffffffffffffff851681528360208201526080604082015260006114b16080830185610f06565b9050600283106114ea577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b82606083015295945050505050565b6000806040838503121561150c57600080fd5b611515836112e4565b9150602083015167ffffffffffffffff81111561153157600080fd5b8301601f8101851361154257600080fd5b8051611550611291826111d0565b81815286602083850101111561156557600080fd5b611576826020830160208601610ee2565b809350505050925092905056fea2646970667358221220c8c45e195a0dad5f5c876eeec47bafe9896aec771f35e88effeaf5ba1d19d83064736f6c634300081300330000000000000000000000006fcf22e22f736d9ead75de8a1f12ca869287e229
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100885760003560e01c80634611b4c71161005b5780634611b4c71461014b57806384b0196e14610184578063ab7aa6ad1461019f578063b1cebbe0146101c657600080fd5b80631c4dd7d01461008d57806321b1e480146100de5780632954018c146101045780632bf4762b1461012b575b600080fd5b6100b47f000000000000000000000000e2033fd8a642e67f11df2c5567023c1900e440f881565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b7f0000000000000000000000006fcf22e22f736d9ead75de8a1f12ca869287e2296100b4565b6100b47f0000000000000000000000006fcf22e22f736d9ead75de8a1f12ca869287e22981565b61013e610139366004610ea7565b6101ed565b6040516100d59190610f50565b610176610159366004610f85565b600160209081526000928352604080842090915290825290205481565b6040519081526020016100d5565b61018c61032a565b6040516100d59796959493929190610fbe565b6100b47f00000000000000000000000027fbc3310907c0425ea09115397a40dddc15464181565b6100b47f0000000000000000000000000145f9674b22be444c9f0e5e2a7761643fe785be81565b60606101f76103d3565b600061022c61022760017f28c61107d4cd3a85ca89b185e44bf7402301acc30bf1bcf06b0e5c9d61063bdd6110ac565b61044a565b90506102388382610507565b600061026561024d60408601602087016110bf565b61025786806110dc565b61026090611216565b6109dd565b905073ffffffffffffffffffffffffffffffffffffffff821663dcf297313361029460408801602089016110bf565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff92831660048201529116602482015260440160006040518083038186803b1580156102ff57600080fd5b505afa158015610313573d6000803e3d6000fd5b50929450505050506103256001600055565b919050565b7f0f0000000000000000000000000000000000000000000000000000000000000060608060008080836103c1604080518082018252600e81527f4578656375746f72506c7567696e0000000000000000000000000000000000006020808301919091528251808401909352600383527f312e3000000000000000000000000000000000000000000000000000000000009083015291565b97989097965046955030945091925090565b600260005403610443576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015260640160405180910390fd5b6002600055565b6040517f2bf84475000000000000000000000000000000000000000000000000000000008152600481018290526000907f0000000000000000000000006fcf22e22f736d9ead75de8a1f12ca869287e22973ffffffffffffffffffffffffffffffffffffffff1690632bf8447590602401602060405180830381865afa1580156104d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fc91906112c7565b905061032581610ae3565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000145f9674b22be444c9f0e5e2a7761643fe785be16639c84cc4861055360408501602086016110bf565b61056360608601604087016110bf565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156105d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105f791906112f4565b61062d576040517f710c949700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61063d60608301604084016110bf565b73ffffffffffffffffffffffffffffffffffffffff163b15801561066d5750610669606083018361130f565b1590505b156106a4576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805161018081019091526000906108ac90806106d86106c587806110dc565b6106d390602081019061137b565b610b33565b60018111156106e9576106e9611396565b60ff1681526020016106fb86806110dc565b61070c9060408101906020016110bf565b73ffffffffffffffffffffffffffffffffffffffff16815260200185602001602081019061073a91906110bf565b73ffffffffffffffffffffffffffffffffffffffff16815260200161076560608701604088016110bf565b73ffffffffffffffffffffffffffffffffffffffff168152600060208201819052604082015260600161079886806110dc565b604001358152602001600160008760200160208101906107b891906110bf565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600087604001602081019061080791906110bf565b73ffffffffffffffffffffffffffffffffffffffff1681526020810191909152604001600090812080549161083b836113c5565b90915550815260006020820181905260408201819052606082015260800161086386806110dc565b61087190606081019061130f565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505050915250610ba9565b905060006108b982610cc6565b90506109166108ce60608601604087016110bf565b826108dc606088018861130f565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610de192505050565b61094c576040517f710c949700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316635a8793943361097960408801602089016110bf565b8561098760808a018a61130f565b6040518663ffffffff1660e01b81526004016109a79594939291906113fd565b60006040518083038186803b1580156109bf57600080fd5b505afa1580156109d3573d6000803e3d6000fd5b5050505050505050565b60606000808473ffffffffffffffffffffffffffffffffffffffff16635229073f856020015186604001518760600151610a1a8960000151610b33565b6040518563ffffffff1660e01b8152600401610a39949392919061147c565b6000604051808303816000875af1158015610a58573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a9e91908101906114f9565b9150915081610ad9576040517fcce1466600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9150505b92915050565b73ffffffffffffffffffffffffffffffffffffffff8116610b30576040517fe6c4247b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b60006001826002811115610b4957610b49611396565b03610b5657506001919050565b6000826002811115610b6a57610b6a611396565b03610b7757506000919050565b6040517f664a3ff500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80516020808301516040808501516060860151608087015160a088015160c089015160e08a01516101008b01516101208c01516101408d01516101608e01518051908d0120995160009d610ca99d7f483ad580f0a8d7881e792d04b2128f3b214b18aa7336126dc2e77a59752bd6f59d919c919b9a9998979695949392019c8d5260ff9b909b1660208d015273ffffffffffffffffffffffffffffffffffffffff998a1660408d015297891660608c015295881660808b015293871660a08a01529190951660c088015260e08701949094526101008601939093526101208501929092526101408401919091526101608301526101808201526101a00190565b604051602081830303815290604052805190602001209050919050565b60007f067db72730c0a467faa9d9b32324fdbb731f98cdb8b0eafe3a677133b16a3c8c7f000000000000000000000000ec4181ee959e47f72e9cc60274fbe53d68949e4730147f0000000000000000000000000000000000000000000000000000000000000001461416610dbb5750604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81527f93ae17d63c11c26435c52b81ff53503650df80d35c62972110e64a0454badbec60208201527fe6bbd6277e1bf288eed5e8d1780f9a50b239e86b153736bceebccf4ea79d90b39181019190915246606082015230608082015260a090205b67190100000000000060005280601a5282603a52604260182091506000603a5250919050565b73ffffffffffffffffffffffffffffffffffffffff9092169160008315610ea0576040516041835103610e575783600052606083015160001a60205260208301516040526040830151606052602060016080600060015afa805186183d1517610e5557506000606052604052506001610ea0565b505b600060605280604052631626ba7e60e01b808252846004830152602482016040815284516020018060448501828860045afa505060208160443d01858a5afa9051909114169150505b9392505050565b600060208284031215610eb957600080fd5b813567ffffffffffffffff811115610ed057600080fd5b820160a08185031215610ea057600080fd5b60005b83811015610efd578181015183820152602001610ee5565b50506000910152565b60008151808452610f1e816020860160208601610ee2565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610ea06020830184610f06565b73ffffffffffffffffffffffffffffffffffffffff81168114610b3057600080fd5b60008060408385031215610f9857600080fd5b8235610fa381610f63565b91506020830135610fb381610f63565b809150509250929050565b7fff00000000000000000000000000000000000000000000000000000000000000881681526000602060e081840152610ffa60e084018a610f06565b838103604085015261100c818a610f06565b6060850189905273ffffffffffffffffffffffffffffffffffffffff8816608086015260a0850187905284810360c0860152855180825283870192509083019060005b8181101561106b5783518352928401929184019160010161104f565b50909c9b505050505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610add57610add61107d565b6000602082840312156110d157600080fd5b8135610ea081610f63565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8183360301811261111057600080fd5b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516080810167ffffffffffffffff8111828210171561116c5761116c61111a565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156111b9576111b961111a565b604052919050565b80356003811061032557600080fd5b600067ffffffffffffffff8211156111ea576111ea61111a565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b60006080823603121561122857600080fd5b611230611149565b611239836111c1565b815260208084013561124a81610f63565b8282015260408481013590830152606084013567ffffffffffffffff81111561127257600080fd5b840136601f82011261128357600080fd5b8035611296611291826111d0565b611172565b81815236848385010111156112aa57600080fd5b818484018583013760009181019093015250606082015292915050565b6000602082840312156112d957600080fd5b8151610ea081610f63565b8051801515811461032557600080fd5b60006020828403121561130657600080fd5b610ea0826112e4565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261134457600080fd5b83018035915067ffffffffffffffff82111561135f57600080fd5b60200191503681900382131561137457600080fd5b9250929050565b60006020828403121561138d57600080fd5b610ea0826111c1565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036113f6576113f661107d565b5060010190565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525084604083015260806060830152826080830152828460a0840137600060a0848401015260a07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f85011683010190509695505050505050565b73ffffffffffffffffffffffffffffffffffffffff851681528360208201526080604082015260006114b16080830185610f06565b9050600283106114ea577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b82606083015295945050505050565b6000806040838503121561150c57600080fd5b611515836112e4565b9150602083015167ffffffffffffffff81111561153157600080fd5b8301601f8101851361154257600080fd5b8051611550611291826111d0565b81815286602083850101111561156557600080fd5b611576826020830160208601610ee2565b809350505050925092905056fea2646970667358221220c8c45e195a0dad5f5c876eeec47bafe9896aec771f35e88effeaf5ba1d19d83064736f6c63430008130033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000006fcf22e22f736d9ead75de8a1f12ca869287e229
-----Decoded View---------------
Arg [0] : _addressProvider (address): 0x6FCf22e22f736D9ead75de8A1f12cA869287E229
-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 0000000000000000000000006fcf22e22f736d9ead75de8a1f12ca869287e229
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 35 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ 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.