Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 1 from a total of 1 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Initialize | 12054783 | 1214 days ago | IN | 0 ETH | 0.0132262 |
Latest 25 internal transactions (View All)
Advanced mode:
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
PersonalAccountRegistry
Compiler Version
v0.6.12+commit.27d51765
Optimization Enabled:
No with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../common/access/Guarded.sol"; import "../common/account/AccountController.sol"; import "../common/account/AccountRegistry.sol"; import "../common/libs/BlockLib.sol"; import "../common/libs/ECDSALib.sol"; import "../common/libs/ECDSAExtendedLib.sol"; import "../common/libs/SafeMathLib.sol"; import "../common/lifecycle/Initializable.sol"; import "../common/token/ERC20Token.sol"; import "../gateway/GatewayRecipient.sol"; /** * @title Personal account registry * * @notice A registry for personal (controlled by owners) accounts * * @author Stanisław Głogowski <[email protected]> */ contract PersonalAccountRegistry is Guarded, AccountController, AccountRegistry, Initializable, GatewayRecipient { using BlockLib for BlockLib.BlockRelated; using SafeMathLib for uint256; using ECDSALib for bytes32; using ECDSAExtendedLib for bytes; struct Account { bool deployed; bytes32 salt; mapping(address => BlockLib.BlockRelated) owners; } mapping(address => Account) private accounts; // events /** * @dev Emitted when the new owner is added * @param account account address * @param owner owner address */ event AccountOwnerAdded( address account, address owner ); /** * @dev Emitted when the existing owner is removed * @param account account address * @param owner owner address */ event AccountOwnerRemoved( address account, address owner ); /** * @dev Emitted when the call is refunded * @param account account address * @param beneficiary beneficiary address * @param token token address * @param value value */ event AccountCallRefunded( address account, address beneficiary, address token, uint256 value ); /** * @dev Public constructor */ constructor() public Initializable() {} // external functions /** * @notice Initializes `PersonalAccountRegistry` contract * @param guardians_ array of guardians addresses * @param accountImplementation_ account implementation address * @param gateway_ `Gateway` contract address */ function initialize( address[] calldata guardians_, address accountImplementation_, address gateway_ ) external onlyInitializer { // Guarded _initializeGuarded(guardians_); // AccountController _initializeAccountController(address(this), accountImplementation_); // GatewayRecipient _initializeGatewayRecipient(gateway_); } /** * @notice Upgrades `PersonalAccountRegistry` contract * @param accountImplementation_ account implementation address */ function upgrade( address accountImplementation_ ) external onlyGuardian { _setAccountImplementation(accountImplementation_, true); } /** * @notice Deploys account * @param account account address */ function deployAccount( address account ) external { _verifySender(account); _deployAccount(account); } /** * @notice Upgrades account * @param account account address */ function upgradeAccount( address account ) external { _verifySender(account); _upgradeAccount(account, true); } /** * @notice Adds a new account owner * @param account account address * @param owner owner address */ function addAccountOwner( address account, address owner ) external { _verifySender(account); require( owner != address(0), "PersonalAccountRegistry: cannot add 0x0 owner" ); require( !accounts[account].owners[owner].verifyAtCurrentBlock(), "PersonalAccountRegistry: owner already exists" ); accounts[account].owners[owner].added = true; accounts[account].owners[owner].removedAtBlockNumber = 0; emit AccountOwnerAdded( account, owner ); } /** * @notice Removes the existing account owner * @param account account address * @param owner owner address */ function removeAccountOwner( address account, address owner ) external { address sender = _verifySender(account); require( owner != sender, "PersonalAccountRegistry: cannot remove self" ); require( accounts[account].owners[owner].verifyAtCurrentBlock(), "PersonalAccountRegistry: owner doesn't exist" ); accounts[account].owners[owner].removedAtBlockNumber = block.number; emit AccountOwnerRemoved( account, owner ); } /** * @notice Executes account transaction * @dev Deploys an account if not deployed yet * @param account account address * @param to to address * @param value value * @param data data */ function executeAccountTransaction( address account, address to, uint256 value, bytes calldata data ) external { _verifySender(account); _deployAccount(account); _executeAccountTransaction( account, to, value, data, true ); } /** * @notice Refunds account call * @dev Deploys an account if not deployed yet * @param account account address * @param token token address * @param value value */ function refundAccountCall( address account, address token, uint256 value ) external { _verifySender(account); _deployAccount(account); /* solhint-disable avoid-tx-origin */ if (token == address(0)) { _executeAccountTransaction( account, tx.origin, value, new bytes(0), false ); } else { bytes memory response = _executeAccountTransaction( account, token, 0, abi.encodeWithSelector( ERC20Token(token).transfer.selector, tx.origin, value ), false ); if (response.length > 0) { require( abi.decode(response, (bool)), "PersonalAccountRegistry: ERC20Token transfer reverted" ); } } emit AccountCallRefunded( account, tx.origin, token, value ); /* solhint-enable avoid-tx-origin */ } // external functions (views) /** * @notice Computes account address * @param saltOwner salt owner address * @return account address */ function computeAccountAddress( address saltOwner ) external view returns (address) { return _computeAccountAddress(saltOwner); } /** * @notice Checks if account is deployed * @param account account address * @return true when account is deployed */ function isAccountDeployed( address account ) external view returns (bool) { return accounts[account].deployed; } /** * @notice Verifies the owner of the account at the current block * @param account account address * @param owner owner address * @return true on correct account owner */ function verifyAccountOwner( address account, address owner ) external view returns (bool) { return _verifyAccountOwner(account, owner); } /** * @notice Verifies the owner of the account at a specific block * @param account account address * @param owner owner address * @param blockNumber block number to verify * @return true on correct account owner */ function verifyAccountOwnerAtBlock( address account, address owner, uint256 blockNumber ) external view returns (bool) { bool result = false; if (_verifyAccountOwner(account, owner)) { result = true; } else { result = accounts[account].owners[owner].verifyAtBlock(blockNumber); } return result; } /** * @notice Verifies account signature * @param account account address * @param messageHash message hash * @param signature signature * @return magic hash if valid */ function isValidAccountSignature( address account, bytes32 messageHash, bytes calldata signature ) override external view returns (bool) { return _verifyAccountOwner( account, messageHash.recoverAddress(signature) ); } /** * @notice Verifies account signature * @param account account address * @param message message * @param signature signature * @return magic hash if valid */ function isValidAccountSignature( address account, bytes calldata message, bytes calldata signature ) override external view returns (bool) { return _verifyAccountOwner( account, message.toEthereumSignedMessageHash().recoverAddress(signature) ); } // private functions function _verifySender( address account ) private returns (address) { address sender = _getContextSender(); if (accounts[account].owners[sender].added) { require( accounts[account].owners[sender].removedAtBlockNumber == 0, "PersonalAccountRegistry: sender is not the account owner" ); } else { require( accounts[account].salt == 0, "PersonalAccountRegistry: sender is not the account owner" ); bytes32 salt = keccak256( abi.encodePacked(sender) ); require( account == _computeAccountAddress(salt), "PersonalAccountRegistry: sender is not the account owner" ); accounts[account].salt = salt; accounts[account].owners[sender].added = true; emit AccountOwnerAdded( account, sender ); } return sender; } function _deployAccount( address account ) internal { if (!accounts[account].deployed) { _deployAccount( accounts[account].salt, true ); accounts[account].deployed = true; } } // private functions (views) function _computeAccountAddress( address saltOwner ) private view returns (address) { bytes32 salt = keccak256( abi.encodePacked(saltOwner) ); return _computeAccountAddress(salt); } function _verifyAccountOwner( address account, address owner ) private view returns (bool) { bool result; if (accounts[account].owners[owner].added) { result = accounts[account].owners[owner].removedAtBlockNumber == 0; } else if (accounts[account].salt == 0) { result = account == _computeAccountAddress(owner); } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title Controlled * * @dev Contract module which provides an access control mechanism. * It ensures there is only one controlling account of the smart contract * and grants that account exclusive access to specific functions. * * The controller account will be the one that deploys the contract. * * @author Stanisław Głogowski <[email protected]> */ contract Controlled { /** * @return controller account address */ address public controller; // modifiers /** * @dev Throws if msg.sender is not the controller */ modifier onlyController() { require( msg.sender == controller, "Controlled: msg.sender is not the controller" ); _; } /** * @dev Internal constructor */ constructor() internal { controller = msg.sender; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../common/access/Controlled.sol"; /** * @title Payment deposit account * * @dev Simple account contract with only one method - `executeTransaction` * * @author Stanisław Głogowski <[email protected]> */ contract PaymentDepositAccount is Controlled { /** * @dev Public constructor */ constructor() public payable Controlled() {} /** * @notice Allow receives */ receive() external payable { // } // external functions /** * @notice Executes transaction * @param to to address * @param value value * @param data data * @return transaction result */ function executeTransaction( address to, uint256 value, bytes calldata data ) external onlyController returns (bytes memory) { bytes memory result; bool succeeded; // solhint-disable-next-line avoid-call-value, avoid-low-level-calls (succeeded, result) = payable(to).call{value: value}(data); require( succeeded, "Account: transaction reverted" ); return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import "../common/access/Guarded.sol"; import "../common/libs/ECDSALib.sol"; import "../common/libs/SafeMathLib.sol"; import "../common/lifecycle/Initializable.sol"; import "../common/signature/SignatureValidator.sol"; import "../common/token/ERC20Token.sol"; import "../external/ExternalAccountRegistry.sol"; import "../personal/PersonalAccountRegistry.sol"; import "../gateway/GatewayRecipient.sol"; import "./PaymentDepositAccount.sol"; /** * @title Payment registry * * @notice A registry for payment and payment channels * * @dev the `DepositExit` process can be used in a case operator (guardian) couldn't sign commit / withdrawal message. * Process will be rejected when any of senders channels will be committed. * * @author Stanisław Głogowski <[email protected]> */ contract PaymentRegistry is Guarded, Initializable, SignatureValidator, GatewayRecipient { using ECDSALib for bytes32; using SafeMathLib for uint256; struct Deposit { address account; mapping(address => uint256) withdrawnAmount; mapping(address => uint256) exitLockedUntil; } struct PaymentChannel { uint256 committedAmount; } struct DepositWithdrawal { address owner; address token; uint256 amount; } struct PaymentChannelCommit { address sender; address recipient; address token; bytes32 uid; uint256 blockNumber; uint256 amount; } uint256 private constant DEFAULT_DEPOSIT_EXIT_LOCK_PERIOD = 28 days; bytes32 private constant HASH_PREFIX_DEPOSIT_WITHDRAWAL = keccak256( "DepositWithdrawal(address owner,address token,uint256 amount)" ); bytes32 private constant HASH_PREFIX_PAYMENT_CHANNEL_COMMIT = keccak256( "PaymentChannelCommit(address sender,address recipient,address token,bytes32 uid,uint256 blockNumber,uint256 amount)" ); ExternalAccountRegistry public externalAccountRegistry; PersonalAccountRegistry public personalAccountRegistry; uint256 public depositExitLockPeriod; mapping(address => Deposit) private deposits; mapping(bytes32 => PaymentChannel) private paymentChannels; // events /** * @dev Emitted when the deposit account is deployed * @param depositAccount deposit account address * @param owner owner address */ event DepositAccountDeployed( address depositAccount, address owner ); /** * @dev Emitted when the deposit exist is requested * @param depositAccount deposit account address * @param owner owner address * @param token token address * @param lockedUntil deposit exist locked util time */ event DepositExitRequested( address depositAccount, address owner, address token, uint256 lockedUntil ); /** * @dev Emitted when the deposit exist is completed * @param depositAccount deposit account address * @param owner owner address * @param token token address * @param amount deposit exist amount */ event DepositExitCompleted( address depositAccount, address owner, address token, uint256 amount ); /** * @dev Emitted when the deposit exist is rejected * @param depositAccount deposit account address * @param owner owner address * @param token token address */ event DepositExitRejected( address depositAccount, address owner, address token ); /** * @dev Emitted when the deposit has been withdrawn * @param depositAccount deposit account address * @param owner owner address * @param token token address * @param amount withdrawn amount */ event DepositWithdrawn( address depositAccount, address owner, address token, uint256 amount ); /** * @dev Emitted when the payment channel has been committed * @param hash channel hash * @param sender sender address * @param recipient recipient address * @param token token address * @param uid unique channel id * @param amount committed amount */ event PaymentChannelCommitted( bytes32 hash, address sender, address recipient, address token, bytes32 uid, uint256 amount ); /** * @dev Emitted when the payment has been withdrawn * @param channelHash channel hash * @param value payment value */ event PaymentWithdrawn( bytes32 channelHash, uint256 value ); /** * @dev Emitted when the payment has been deposited * @param channelHash channel hash * @param value payment value */ event PaymentDeposited( bytes32 channelHash, uint256 value ); /** * @dev Emitted when the payment has been withdrawn and deposited (split) * @param channelHash channel hash * @param totalValue payment total value * @param depositValue payment deposited value */ event PaymentSplit( bytes32 channelHash, uint256 totalValue, uint256 depositValue ); /** * @dev Public constructor */ constructor() public Initializable() SignatureValidator() {} // external functions /** * @notice Initialize `PaymentRegistry` contract * @param externalAccountRegistry_ `ExternalAccountRegistry` contract address * @param personalAccountRegistry_ `PersonalAccountRegistry` contract address * @param depositExitLockPeriod_ deposit exit lock period * @param guardians_ array of guardians addresses * @param gateway_ `Gateway` contract address */ function initialize( ExternalAccountRegistry externalAccountRegistry_, PersonalAccountRegistry personalAccountRegistry_, uint256 depositExitLockPeriod_, address[] calldata guardians_, address gateway_ ) external onlyInitializer { externalAccountRegistry = externalAccountRegistry_; personalAccountRegistry = personalAccountRegistry_; if (depositExitLockPeriod_ == 0) { depositExitLockPeriod = DEFAULT_DEPOSIT_EXIT_LOCK_PERIOD; } else { depositExitLockPeriod = depositExitLockPeriod_; } // Guarded _initializeGuarded(guardians_); // GatewayRecipient _initializeGatewayRecipient(gateway_); } /** * @notice Deploys deposit account * @param owner owner address */ function deployDepositAccount( address owner ) external { _deployDepositAccount(owner); } /** * @notice Requests deposit exit * @param token token address */ function requestDepositExit( address token ) external { address owner = _getContextAccount(); uint256 lockedUntil = deposits[owner].exitLockedUntil[token]; require( lockedUntil == 0, "PaymentRegistry: deposit exit already requested" ); _deployDepositAccount(owner); // solhint-disable-next-line not-rely-on-time lockedUntil = now.add(depositExitLockPeriod); deposits[owner].exitLockedUntil[token] = lockedUntil; emit DepositExitRequested( deposits[owner].account, owner, token, lockedUntil ); } /** * @notice Processes deposit exit * @param token token address */ function processDepositExit( address token ) external { address owner = _getContextAccount(); uint256 lockedUntil = deposits[owner].exitLockedUntil[token]; require( lockedUntil != 0, "PaymentRegistry: deposit exit not requested" ); require( // solhint-disable-next-line not-rely-on-time lockedUntil <= now, "PaymentRegistry: deposit exit locked" ); deposits[owner].exitLockedUntil[token] = 0; address depositAccount = deposits[owner].account; uint256 depositValue; if (token == address(0)) { depositValue = depositAccount.balance; } else { depositValue = ERC20Token(token).balanceOf(depositAccount); } _transferFromDeposit( depositAccount, owner, token, depositValue ); emit DepositExitCompleted( depositAccount, owner, token, depositValue ); } /** * @notice Withdraws deposit * @param token token address * @param amount amount to withdraw * @param guardianSignature guardian signature */ function withdrawDeposit( address token, uint256 amount, bytes calldata guardianSignature ) external { address owner = _getContextAccount(); uint256 value = amount.sub(deposits[owner].withdrawnAmount[token]); require( value > 0, "PaymentRegistry: invalid amount" ); bytes32 messageHash = _hashDepositWithdrawal( owner, token, amount ); require( _verifyGuardianSignature(messageHash, guardianSignature), "PaymentRegistry: invalid guardian signature" ); deposits[owner].withdrawnAmount[token] = amount; _verifyDepositExitOrDeployAccount(owner, token); _transferFromDeposit( deposits[owner].account, owner, token, value ); emit DepositWithdrawn( deposits[owner].account, owner, token, amount ); } /** * @notice Commits payment channel and withdraw payment * @param sender sender address * @param token token address * @param uid unique channel id * @param blockNumber block number * @param amount amount to commit * @param senderSignature sender signature * @param guardianSignature guardian signature */ function commitPaymentChannelAndWithdraw( address sender, address token, bytes32 uid, uint256 blockNumber, uint256 amount, bytes calldata senderSignature, bytes calldata guardianSignature ) external { address recipient = _getContextAccount(); (bytes32 hash, address depositAccount, uint256 paymentValue) = _commitPaymentChannel( sender, recipient, token, uid, blockNumber, amount, senderSignature, guardianSignature ); _transferFromDeposit( depositAccount, recipient, token, paymentValue ); emit PaymentWithdrawn(hash, paymentValue); } /** * @notice Commits payment channel and deposit payment * @param sender sender address * @param token token address * @param uid unique channel id * @param blockNumber block number * @param amount amount to commit * @param senderSignature sender signature * @param guardianSignature guardian signature */ function commitPaymentChannelAndDeposit( address sender, address token, bytes32 uid, uint256 blockNumber, uint256 amount, bytes calldata senderSignature, bytes calldata guardianSignature ) external { address recipient = _getContextAccount(); (bytes32 hash, address depositAccount, uint256 paymentValue) = _commitPaymentChannel( sender, recipient, token, uid, blockNumber, amount, senderSignature, guardianSignature ); _transferFromDeposit( depositAccount, _computeDepositAccountAddress(recipient), token, paymentValue ); emit PaymentDeposited(hash, paymentValue); } /** * @notice Commits payment channel, withdraws and deposits (split) payment * @param sender sender address * @param token token address * @param uid unique channel id * @param blockNumber block number * @param amount amount to commit * @param depositPaymentValue amount to deposit * @param senderSignature sender signature * @param guardianSignature guardian signature */ function commitPaymentChannelAndSplit( address sender, address token, bytes32 uid, uint256 blockNumber, uint256 amount, uint256 depositPaymentValue, bytes calldata senderSignature, bytes calldata guardianSignature ) external { address recipient = _getContextAccount(); (bytes32 hash, address depositAccount, uint256 paymentValue) = _commitPaymentChannel( sender, recipient, token, uid, blockNumber, amount, senderSignature, guardianSignature ); _transferSplitFromDeposit( depositAccount, recipient, token, paymentValue, depositPaymentValue ); emit PaymentSplit(hash, paymentValue, depositPaymentValue); } // external functions (views) /** * @notice Computes deposit account address * @param owner owner address * @return deposit account address */ function computeDepositAccountAddress( address owner ) external view returns (address) { return _computeDepositAccountAddress(owner); } /** * @notice Checks if deposit account is deployed * @param owner owner address * @return true when deposit account is deployed */ function isDepositAccountDeployed( address owner ) external view returns (bool) { return deposits[owner].account != address(0); } /** * @notice Gets deposit exit locked until time * @param owner owner address * @param token token address * @return locked until time */ function getDepositExitLockedUntil( address owner, address token ) external view returns (uint256) { return deposits[owner].exitLockedUntil[token]; } /** * @notice Gets deposit withdrawn amount * @param owner owner address * @param token token address * @return withdrawn amount */ function getDepositWithdrawnAmount( address owner, address token ) external view returns (uint256) { return deposits[owner].withdrawnAmount[token]; } /** * @notice Gets payment channel committed amount * @param hash payment channel hash * @return committed amount */ function getPaymentChannelCommittedAmount( bytes32 hash ) external view returns (uint256) { return paymentChannels[hash].committedAmount; } // external functions (pure) /** * @notice Computes payment channel hash * @param sender sender address * @param recipient recipient address * @param token token address * @param uid unique channel id * @return hash */ function computePaymentChannelHash( address sender, address recipient, address token, bytes32 uid ) external pure returns (bytes32) { return _computePaymentChannelHash( sender, recipient, token, uid ); } // public functions (views) /** * @notice Hashes `DepositWithdrawal` message payload * @param depositWithdrawal struct * @return hash */ function hashDepositWithdrawal( DepositWithdrawal memory depositWithdrawal ) public view returns (bytes32) { return _hashDepositWithdrawal( depositWithdrawal.owner, depositWithdrawal.token, depositWithdrawal.amount ); } /** * @notice Hashes `PaymentChannelCommit` message payload * @param paymentChannelCommit struct * @return hash */ function hashPaymentChannelCommit( PaymentChannelCommit memory paymentChannelCommit ) public view returns (bytes32) { return _hashPaymentChannelCommit( paymentChannelCommit.sender, paymentChannelCommit.recipient, paymentChannelCommit.token, paymentChannelCommit.uid, paymentChannelCommit.blockNumber, paymentChannelCommit.amount ); } // private functions function _deployDepositAccount( address owner ) private { if (deposits[owner].account == address(0)) { bytes32 salt = keccak256( abi.encodePacked( owner ) ); deposits[owner].account = address(new PaymentDepositAccount{salt: salt}()); emit DepositAccountDeployed( deposits[owner].account, owner ); } } function _verifyDepositExitOrDeployAccount( address owner, address token ) private { if (deposits[owner].exitLockedUntil[token] > 0) { deposits[owner].exitLockedUntil[token] = 0; emit DepositExitRejected( deposits[owner].account, owner, token ); } else { _deployDepositAccount(owner); } } function _commitPaymentChannel( address sender, address recipient, address token, bytes32 uid, uint256 blockNumber, uint256 amount, bytes memory senderSignature, bytes memory guardianSignature ) private returns (bytes32 hash, address depositAccount, uint256 paymentValue) { bytes32 messageHash = _hashPaymentChannelCommit( sender, recipient, token, uid, blockNumber, amount ); if (senderSignature.length == 0) { require( externalAccountRegistry.verifyAccountProofAtBlock(sender, messageHash, blockNumber), "PaymentRegistry: invalid guardian signature" ); } else { address signer = messageHash.recoverAddress(senderSignature); if (sender != signer) { require( personalAccountRegistry.verifyAccountOwnerAtBlock(sender, signer, blockNumber) || externalAccountRegistry.verifyAccountOwnerAtBlock(sender, signer, blockNumber), "PaymentRegistry: invalid sender signature" ); } } require( _verifyGuardianSignature(messageHash, guardianSignature), "PaymentRegistry: invalid guardian signature" ); hash = _computePaymentChannelHash( sender, recipient, token, uid ); /// @dev calc payment value paymentValue = amount.sub(paymentChannels[hash].committedAmount); require( paymentValue != 0, "PaymentRegistry: invalid payment value" ); paymentChannels[hash].committedAmount = amount; _verifyDepositExitOrDeployAccount(sender, token); depositAccount = deposits[sender].account; emit PaymentChannelCommitted( hash, sender, recipient, token, uid, amount ); return (hash, depositAccount, paymentValue); } function _transferFromDeposit( address depositAccount, address to, address token, uint256 value ) private { if (token == address(0)) { PaymentDepositAccount(payable(depositAccount)).executeTransaction( to, value, new bytes(0) ); } else { bytes memory response = PaymentDepositAccount(payable(depositAccount)).executeTransaction( token, 0, abi.encodeWithSelector( ERC20Token(token).transfer.selector, to, value ) ); if (response.length > 0) { require( abi.decode(response, (bool)), "PaymentRegistry: ERC20Token transfer reverted" ); } } } function _transferSplitFromDeposit( address depositAccount, address to, address token, uint256 paymentValue, uint256 depositValue ) private { require( depositValue > 0, "PaymentRegistry: invalid deposit value" ); uint256 withdrawValue = paymentValue.sub(depositValue); require( withdrawValue > 0, "PaymentRegistry: invalid withdraw value" ); _transferFromDeposit( depositAccount, to, token, withdrawValue ); _transferFromDeposit( depositAccount, _computeDepositAccountAddress(to), token, depositValue ); } // private functions (views) function _computeDepositAccountAddress( address owner ) private view returns (address) { bytes32 salt = keccak256( abi.encodePacked( owner ) ); bytes memory creationCode = type(PaymentDepositAccount).creationCode; bytes32 data = keccak256( abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(creationCode) ) ); return address(uint160(uint256(data))); } function _hashDepositWithdrawal( address owner, address token, uint256 amount ) private view returns (bytes32) { return _hashMessagePayload(HASH_PREFIX_DEPOSIT_WITHDRAWAL, abi.encodePacked( owner, token, amount )); } function _hashPaymentChannelCommit( address sender, address recipient, address token, bytes32 uid, uint256 blockNumber, uint256 amount ) private view returns (bytes32) { return _hashMessagePayload(HASH_PREFIX_PAYMENT_CHANNEL_COMMIT, abi.encodePacked( sender, recipient, token, uid, blockNumber, amount )); } // private functions (pure) function _computePaymentChannelHash( address sender, address recipient, address token, bytes32 uid ) private pure returns (bytes32) { return keccak256( abi.encodePacked( sender, recipient, token, uid ) ); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../libs/ECDSALib.sol"; /** * @title Guarded * * @dev Contract module which provides a guardian-type control mechanism. * It allows key accounts to have guardians and restricts specific methods to be accessible by guardians only. * * Each guardian account can remove other guardians * * Use `_initializeGuarded` to initialize the contract * * @author Stanisław Głogowski <[email protected]> */ contract Guarded { using ECDSALib for bytes32; mapping(address => bool) private guardians; // events /** * @dev Emitted when a new guardian is added * @param sender sender address * @param guardian guardian address */ event GuardianAdded( address sender, address guardian ); /** * @dev Emitted when the existing guardian is removed * @param sender sender address * @param guardian guardian address */ event GuardianRemoved( address sender, address guardian ); // modifiers /** * @dev Throws if tx.origin is not a guardian account */ modifier onlyGuardian() { require( // solhint-disable-next-line avoid-tx-origin guardians[tx.origin], "Guarded: tx.origin is not the guardian" ); _; } /** * @dev Internal constructor */ constructor() internal {} // external functions /** * @notice Adds a new guardian * @param guardian guardian address */ function addGuardian( address guardian ) external onlyGuardian { _addGuardian(guardian); } /** * @notice Removes the existing guardian * @param guardian guardian address */ function removeGuardian( address guardian ) external onlyGuardian { require( // solhint-disable-next-line avoid-tx-origin tx.origin != guardian, "Guarded: cannot remove self" ); require( guardians[guardian], "Guarded: guardian doesn't exist" ); guardians[guardian] = false; emit GuardianRemoved( // solhint-disable-next-line avoid-tx-origin tx.origin, guardian ); } // external functions (views) /** * @notice Check if guardian exists * @param guardian guardian address * @return true when guardian exists */ function isGuardian( address guardian ) external view returns (bool) { return guardians[guardian]; } /** * @notice Verifies guardian signature * @param messageHash message hash * @param signature signature * @return true on correct guardian signature */ function verifyGuardianSignature( bytes32 messageHash, bytes calldata signature ) external view returns (bool) { return _verifyGuardianSignature( messageHash, signature ); } // internal functions /** * @notice Initializes `Guarded` contract * @dev If `guardians_` array is empty `tx.origin` is added as guardian account * @param guardians_ array of guardians addresses */ function _initializeGuarded( address[] memory guardians_ ) internal { if (guardians_.length == 0) { // solhint-disable-next-line avoid-tx-origin _addGuardian(tx.origin); } else { uint guardiansLen = guardians_.length; for (uint i = 0; i < guardiansLen; i++) { _addGuardian(guardians_[i]); } } } // internal functions (views) function _verifyGuardianSignature( bytes32 messageHash, bytes memory signature ) internal view returns (bool) { address guardian = messageHash.recoverAddress(signature); return guardians[guardian]; } // private functions function _addGuardian( address guardian ) private { require( guardian != address(0), "Guarded: cannot add 0x0 guardian" ); require( !guardians[guardian], "Guarded: guardian already exists" ); guardians[guardian] = true; emit GuardianAdded( // solhint-disable-next-line avoid-tx-origin tx.origin, guardian ); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title ECDSA library * * @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/cryptography/ECDSA.sol#L26 */ library ECDSALib { function recoverAddress( bytes32 messageHash, bytes memory signature ) internal pure returns (address) { address result = address(0); if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // solhint-disable-next-line no-inline-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } if (v < 27) { v += 27; } if (v == 27 || v == 28) { result = ecrecover(messageHash, v, r, s); } } return result; } function toEthereumSignedMessageHash( bytes32 messageHash ) internal pure returns (bytes32) { return keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", messageHash )); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title Safe math library * * @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/math/SafeMath.sol */ library SafeMathLib { function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMathLib: addition overflow"); return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMathLib: subtraction overflow"); } function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); return a - b; } function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMathLib: multiplication overflow"); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMathLib: division by zero"); } function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b > 0, errorMessage); return a / b; } function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMathLib: modulo by zero"); } function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title Initializable * * @dev Contract module which provides access control mechanism, where * there is the initializer account that can be granted exclusive access to * specific functions. * * The initializer account will be tx.origin during contract deployment and will be removed on first use. * Use `onlyInitializer` modifier on contract initialize process. * * @author Stanisław Głogowski <[email protected]> */ contract Initializable { address private initializer; // events /** * @dev Emitted after `onlyInitializer` * @param initializer initializer address */ event Initialized( address initializer ); // modifiers /** * @dev Throws if tx.origin is not the initializer */ modifier onlyInitializer() { require( // solhint-disable-next-line avoid-tx-origin tx.origin == initializer, "Initializable: tx.origin is not the initializer" ); /// @dev removes initializer initializer = address(0); _; emit Initialized( // solhint-disable-next-line avoid-tx-origin tx.origin ); } /** * @dev Internal constructor */ constructor() internal { // solhint-disable-next-line avoid-tx-origin initializer = tx.origin; } // external functions (views) /** * @notice Check if contract is initialized * @return true when contract is initialized */ function isInitialized() external view returns (bool) { return initializer == address(0); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../libs/ECDSALib.sol"; /** * @title Signature validator * * @author Stanisław Głogowski <[email protected]> */ contract SignatureValidator { using ECDSALib for bytes32; uint256 public chainId; /** * @dev internal constructor */ constructor() internal { uint256 chainId_; // solhint-disable-next-line no-inline-assembly assembly { chainId_ := chainid() } chainId = chainId_; } // internal functions function _hashMessagePayload( bytes32 messagePrefix, bytes memory messagePayload ) internal view returns (bytes32) { return keccak256(abi.encodePacked( chainId, address(this), messagePrefix, messagePayload )).toEthereumSignedMessageHash(); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../libs/SafeMathLib.sol"; /** * @title ERC20 token * * @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC20/ERC20.sol */ contract ERC20Token { using SafeMathLib for uint256; string public name; string public symbol; uint8 public decimals; uint256 public totalSupply; mapping(address => uint256) internal balances; mapping(address => mapping(address => uint256)) internal allowances; // events event Transfer( address indexed from, address indexed to, uint256 value ); event Approval( address indexed owner, address indexed spender, uint256 value ); /** * @dev internal constructor */ constructor() internal {} // external functions function transfer( address to, uint256 value ) external returns (bool) { _transfer(_getSender(), to, value); return true; } function transferFrom( address from, address to, uint256 value ) virtual external returns (bool) { address sender = _getSender(); _transfer(from, to, value); _approve(from, sender, allowances[from][sender].sub(value)); return true; } function approve( address spender, uint256 value ) virtual external returns (bool) { _approve(_getSender(), spender, value); return true; } // external functions (views) function balanceOf( address owner ) virtual external view returns (uint256) { return balances[owner]; } function allowance( address owner, address spender ) virtual external view returns (uint256) { return allowances[owner][spender]; } // internal functions function _transfer( address from, address to, uint256 value ) virtual internal { require( from != address(0), "ERC20Token: cannot transfer from 0x0 address" ); require( to != address(0), "ERC20Token: cannot transfer to 0x0 address" ); balances[from] = balances[from].sub(value); balances[to] = balances[to].add(value); emit Transfer(from, to, value); } function _approve( address owner, address spender, uint256 value ) virtual internal { require( owner != address(0), "ERC20Token: cannot approve from 0x0 address" ); require( spender != address(0), "ERC20Token: cannot approve to 0x0 address" ); allowances[owner][spender] = value; emit Approval(owner, spender, value); } function _mint( address owner, uint256 value ) virtual internal { require( owner != address(0), "ERC20Token: cannot mint to 0x0 address" ); require( value > 0, "ERC20Token: cannot mint 0 value" ); balances[owner] = balances[owner].add(value); totalSupply = totalSupply.add(value); emit Transfer(address(0), owner, value); } function _burn( address owner, uint256 value ) virtual internal { require( owner != address(0), "ERC20Token: cannot burn from 0x0 address" ); balances[owner] = balances[owner].sub( value, "ERC20Token: burn value exceeds balance" ); totalSupply = totalSupply.sub(value); emit Transfer(owner, address(0), value); } // internal functions (views) function _getSender() virtual internal view returns (address) { return msg.sender; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../common/libs/BlockLib.sol"; /** * @title External account registry * * @notice Global registry for keys and external (outside of the platform) contract based wallets * * @dev An account can call the registry to add (`addAccountOwner`) or remove (`removeAccountOwner`) its own owners. * When the owner has been added, information about that fact will live in the registry forever. * Removing an owner only affects the future blocks (until the owner is re-added). * * Given the fact, there is no way to sign the data using a contract based wallet, * we created a registry to store signed by the key wallet proofs. * ERC-1271 allows removing a signer after the signature was created. Thus store the signature for the later use * doesn't guarantee the signer is still has access to that smart account. * Because of that, the ERC1271's `isValidSignature()` cannot be used in e.g. `PaymentRegistry`.* * * An account can call the registry to add (`addAccountProof`) or remove (`removeAccountProof`) proof hash. * When the proof has been added, information about that fact will live in the registry forever. * Removing a proof only affects the future blocks (until the proof is re-added). * * @author Stanisław Głogowski <[email protected]> */ contract ExternalAccountRegistry { using BlockLib for BlockLib.BlockRelated; struct Account { mapping(address => BlockLib.BlockRelated) owners; mapping(bytes32 => BlockLib.BlockRelated) proofs; } mapping(address => Account) private accounts; // events /** * @dev Emitted when the new owner is added * @param account account address * @param owner owner address */ event AccountOwnerAdded( address account, address owner ); /** * @dev Emitted when the existing owner is removed * @param account account address * @param owner owner address */ event AccountOwnerRemoved( address account, address owner ); /** * @dev Emitted when the new proof is added * @param account account address * @param hash proof hash */ event AccountProofAdded( address account, bytes32 hash ); /** * @dev Emitted when the existing proof is removed * @param account account address * @param hash proof hash */ event AccountProofRemoved( address account, bytes32 hash ); // external functions /** * @notice Adds a new account owner * @param owner owner address */ function addAccountOwner( address owner ) external { require( owner != address(0), "ExternalAccountRegistry: cannot add 0x0 owner" ); require( !accounts[msg.sender].owners[owner].verifyAtCurrentBlock(), "ExternalAccountRegistry: owner already exists" ); accounts[msg.sender].owners[owner].added = true; accounts[msg.sender].owners[owner].removedAtBlockNumber = 0; emit AccountOwnerAdded( msg.sender, owner ); } /** * @notice Removes existing account owner * @param owner owner address */ function removeAccountOwner( address owner ) external { require( accounts[msg.sender].owners[owner].verifyAtCurrentBlock(), "ExternalAccountRegistry: owner doesn't exist" ); accounts[msg.sender].owners[owner].removedAtBlockNumber = block.number; emit AccountOwnerRemoved( msg.sender, owner ); } /** * @notice Adds a new account proof * @param hash proof hash */ function addAccountProof( bytes32 hash ) external { require( !accounts[msg.sender].proofs[hash].verifyAtCurrentBlock(), "ExternalAccountRegistry: proof already exists" ); accounts[msg.sender].proofs[hash].added = true; accounts[msg.sender].proofs[hash].removedAtBlockNumber = 0; emit AccountProofAdded( msg.sender, hash ); } /** * @notice Removes existing account proof * @param hash proof hash */ function removeAccountProof( bytes32 hash ) external { require( accounts[msg.sender].proofs[hash].verifyAtCurrentBlock(), "ExternalAccountRegistry: proof doesn't exist" ); accounts[msg.sender].proofs[hash].removedAtBlockNumber = block.number; emit AccountProofRemoved( msg.sender, hash ); } // external functions (views) /** * @notice Verifies the owner of the account at current block * @param account account address * @param owner owner address * @return true on correct account owner */ function verifyAccountOwner( address account, address owner ) external view returns (bool) { return accounts[account].owners[owner].verifyAtCurrentBlock(); } /** * @notice Verifies the owner of the account at specific block * @param account account address * @param owner owner address * @param blockNumber block number to verify * @return true on correct account owner */ function verifyAccountOwnerAtBlock( address account, address owner, uint256 blockNumber ) external view returns (bool) { return accounts[account].owners[owner].verifyAtBlock(blockNumber); } /** * @notice Verifies the proof of the account at current block * @param account account address * @param hash proof hash * @return true on correct account proof */ function verifyAccountProof( address account, bytes32 hash ) external view returns (bool) { return accounts[account].proofs[hash].verifyAtCurrentBlock(); } /** * @notice Verifies the proof of the account at specific block * @param account account address * @param hash proof hash * @param blockNumber block number to verify * @return true on correct account proof */ function verifyAccountProofAtBlock( address account, bytes32 hash, uint256 blockNumber ) external view returns (bool) { return accounts[account].proofs[hash].verifyAtBlock(blockNumber); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../common/libs/BytesLib.sol"; /** * @title Gateway recipient * * @notice Gateway target contract * * @author Stanisław Głogowski <[email protected]> */ contract GatewayRecipient { using BytesLib for bytes; address public gateway; /** * @dev internal constructor */ constructor() internal {} // internal functions /** * @notice Initializes `GatewayRecipient` contract * @param gateway_ `Gateway` contract address */ function _initializeGatewayRecipient( address gateway_ ) internal { gateway = gateway_; } // internal functions (views) /** * @notice Gets gateway context account * @return context account address */ function _getContextAccount() internal view returns (address) { return _getContextAddress(40); } /** * @notice Gets gateway context sender * @return context sender address */ function _getContextSender() internal view returns (address) { return _getContextAddress(20); } /** * @notice Gets gateway context data * @return context data */ function _getContextData() internal view returns (bytes calldata) { bytes calldata result; if (_isGatewaySender()) { result = msg.data[:msg.data.length - 40]; } else { result = msg.data; } return result; } // private functions (views) function _getContextAddress( uint256 offset ) private view returns (address) { address result = address(0); if (_isGatewaySender()) { uint from = msg.data.length - offset; result = bytes(msg.data[from:from + 20]).toAddress(); } else { result = msg.sender; } return result; } function _isGatewaySender() private view returns (bool) { bool result; if (msg.sender == gateway) { require( msg.data.length >= 44, "GatewayRecipient: invalid msg.data" ); result = true; } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title Block library * * @author Stanisław Głogowski <[email protected]> */ library BlockLib { struct BlockRelated { bool added; uint256 removedAtBlockNumber; } /** * @notice Verifies self struct at current block * @param self self struct * @return true on correct self struct */ function verifyAtCurrentBlock( BlockRelated memory self ) internal view returns (bool) { return verifyAtBlock(self, block.number); } /** * @notice Verifies self struct at any block * @param self self struct * @return true on correct self struct */ function verifyAtAnyBlock( BlockRelated memory self ) internal pure returns (bool) { return verifyAtBlock(self, 0); } /** * @notice Verifies self struct at specific block * @param self self struct * @param blockNumber block number to verify * @return true on correct self struct */ function verifyAtBlock( BlockRelated memory self, uint256 blockNumber ) internal pure returns (bool) { bool result = false; if (self.added) { if (self.removedAtBlockNumber == 0) { result = true; } else if (blockNumber == 0) { result = true; } else { result = self.removedAtBlockNumber > blockNumber; } } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "./Account.sol"; /** * @title Account controller * * @dev Contract module which provides Account deployment mechanism * * @author Stanisław Głogowski <[email protected]> */ contract AccountController { address public accountRegistry; address public accountImplementation; // events /** * @dev Emitted when the account registry is updated * @param accountRegistry account registry address */ event AccountRegistryUpdated( address accountRegistry ); /** * @dev Emitted when the account implementation is updated * @param accountImplementation account implementation address */ event AccountImplementationUpdated( address accountImplementation ); /** * @dev Emitted when the account is deployed * @param account account address * @param accountImplementation account implementation address */ event AccountDeployed( address account, address accountImplementation ); /** * @dev Emitted when the account is upgraded * @param account account address * @param accountImplementation account implementation address */ event AccountUpgraded( address account, address accountImplementation ); /** * @dev Emitted when the transaction is executed * @param account account address * @param to to address * @param value value * @param data data * @param response response */ event AccountTransactionExecuted( address account, address to, uint256 value, bytes data, bytes response ); /** * @dev Internal constructor */ constructor() internal {} // internal functions /** * @notice Initializes `AccountController` contract * @param accountRegistry_ account registry address * @param accountImplementation_ account implementation address */ function _initializeAccountController( address accountRegistry_, address accountImplementation_ ) internal { _setAccountRegistry(accountRegistry_, false); _setAccountImplementation(accountImplementation_, false); } /** * @notice Sets account registry * @param accountRegistry_ account registry address * @param emitEvent it will emit event when flag is set to true */ function _setAccountRegistry( address accountRegistry_, bool emitEvent ) internal { require( accountRegistry_ != address(0), "AccountController: cannot set account registry to 0x0" ); accountRegistry = accountRegistry_; if (emitEvent) { emit AccountRegistryUpdated(accountRegistry); } } /** * @notice Sets account implementation * @param accountImplementation_ account implementation address * @param emitEvent it will emit event when flag is set to true */ function _setAccountImplementation( address accountImplementation_, bool emitEvent ) internal { require( accountImplementation_ != address(0), "AccountController: cannot set account Implementation to 0x0" ); accountImplementation = accountImplementation_; if (emitEvent) { emit AccountImplementationUpdated(accountImplementation); } } /** * @notice Deploys account * @param salt CREATE2 salt * @param emitEvent it will emit event when flag is set to true * @return account address */ function _deployAccount( bytes32 salt, bool emitEvent ) internal returns (address) { address account = address(new Account{salt: salt}( accountRegistry, accountImplementation )); if (emitEvent) { emit AccountDeployed( account, accountImplementation ); } return account; } /** * @notice Upgrades account * @param account account address * @param emitEvent it will emit event when flag is set to true */ function _upgradeAccount( address account, bool emitEvent ) internal { require( Account(payable(account)).implementation() != accountImplementation, "AccountController: account already upgraded" ); Account(payable(account)).setImplementation(accountImplementation); if (emitEvent) { emit AccountUpgraded( account, accountImplementation ); } } /** * @notice Executes transaction from the account * @param account account address * @param to to address * @param value value * @param data data * @param emitEvent it will emit event when flag is set to true * @return transaction result */ function _executeAccountTransaction( address account, address to, uint256 value, bytes memory data, bool emitEvent ) internal returns (bytes memory) { require( to != address(0), "AccountController: cannot send to 0x0" ); require( to != address(this), "AccountController: cannot send to controller" ); require( to != account, "AccountController: cannot send to self" ); bytes memory response = Account(payable(account)).executeTransaction( to, value, data ); if (emitEvent) { emit AccountTransactionExecuted( account, to, value, data, response ); } return response; } // internal functions (views) /** * @notice Computes account CREATE2 address * @param salt CREATE2 salt * @return account address */ function _computeAccountAddress( bytes32 salt ) internal view returns (address) { bytes memory creationCode = abi.encodePacked( type(Account).creationCode, bytes12(0), accountRegistry, bytes12(0), accountImplementation ); bytes32 data = keccak256( abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(creationCode) ) ); return address(uint160(uint256(data))); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "./Account.sol"; /** * @title Account registry * * @author Stanisław Głogowski <[email protected]> */ abstract contract AccountRegistry { /** * @notice Verifies account signature * @param account account address * @param messageHash message hash * @param signature signature * @return true if valid */ function isValidAccountSignature( address account, bytes32 messageHash, bytes calldata signature ) virtual external view returns (bool); /** * @notice Verifies account signature * @param account account address * @param message message * @param signature signature * @return true if valid */ function isValidAccountSignature( address account, bytes calldata message, bytes calldata signature ) virtual external view returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "./StringsLib.sol"; /** * @title ECDSA extended library */ library ECDSAExtendedLib { using StringsLib for uint; function toEthereumSignedMessageHash( bytes memory message ) internal pure returns (bytes32) { return keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n", message.length.toString(), abi.encodePacked(message) )); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../access/Controlled.sol"; import "./AccountBase.sol"; /** * @title Account * * @author Stanisław Głogowski <[email protected]> */ contract Account is Controlled, AccountBase { address public implementation; /** * @dev Public constructor * @param registry_ account registry address * @param implementation_ account implementation address */ constructor( address registry_, address implementation_ ) public Controlled() { registry = registry_; implementation = implementation_; } // external functions /** * @notice Payable receive */ receive() external payable { // } /** * @notice Fallback */ // solhint-disable-next-line payable-fallback fallback() external { if (msg.data.length != 0) { address implementation_ = implementation; // solhint-disable-next-line no-inline-assembly assembly { let calldedatasize := calldatasize() calldatacopy(0, 0, calldedatasize) let result := delegatecall(gas(), implementation_, 0, calldedatasize, 0, 0) let returneddatasize := returndatasize() returndatacopy(0, 0, returneddatasize) switch result case 0 { revert(0, returneddatasize) } default { return(0, returneddatasize) } } } } /** * @notice Sets implementation * @param implementation_ implementation address */ function setImplementation( address implementation_ ) external onlyController { implementation = implementation_; } /** * @notice Executes transaction * @param to to address * @param value value * @param data data * @return transaction result */ function executeTransaction( address to, uint256 value, bytes calldata data ) external onlyController returns (bytes memory) { bytes memory result; bool succeeded; // solhint-disable-next-line avoid-call-value, avoid-low-level-calls (succeeded, result) = payable(to).call{value: value}(data); require( succeeded, "Account: transaction reverted" ); return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title Account base * * @author Stanisław Głogowski <[email protected]> */ contract AccountBase { address public registry; }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title Strings library * * @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/utils/Strings.sol#L12 */ library StringsLib { function toString( uint256 value ) internal pure returns (string memory) { if (value == 0) { return "0"; } uint256 temp = value; uint256 digits; while (temp != 0) { digits++; temp /= 10; } bytes memory buffer = new bytes(digits); uint256 index = digits - 1; temp = value; while (temp != 0) { buffer[index--] = byte(uint8(48 + temp % 10)); temp /= 10; } return string(buffer); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title Bytes library * * @author Stanisław Głogowski <[email protected]> */ library BytesLib { /** * @notice Converts bytes to address * @param data data * @return address */ function toAddress( bytes memory data ) internal pure returns (address) { address result; require( data.length == 20, "BytesLib: invalid data length" ); // solhint-disable-next-line no-inline-assembly assembly { result := div(mload(add(data, 0x20)), 0x1000000000000000000000000) } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../common/lifecycle/Initializable.sol"; import "../common/token/ERC20Token.sol"; import "../gateway/GatewayRecipient.sol"; /** * @title Wrapped wei token * * @notice One to one wei consumable ERC20 token * * @dev After the transfer to consumer's account is done, the token will be automatically burned and withdrawn. * * Use `startConsuming` to become a consumer. * * @author Stanisław Głogowski <[email protected]> */ contract WrappedWeiToken is Initializable, ERC20Token, GatewayRecipient { mapping(address => bool) private consumers; // events /** * @dev Emitted when the new consumer is added * @param consumer consumer address */ event ConsumerAdded( address consumer ); /** * @dev Emitted when the existing consumer is removed * @param consumer consumer address */ event ConsumerRemoved( address consumer ); /** * @dev Public constructor */ constructor() public Initializable() { name = "Wrapped Wei"; symbol = "WWEI"; } /** * @notice Receive fallback */ receive() external payable { _mint(_getSender(), msg.value); } // external functions /** * @notice Initializes `WrappedWeiToken` contract * @param consumers_ array of consumers addresses * @param gateway_ `Gateway` contract address */ function initialize( address[] calldata consumers_, address gateway_ ) external onlyInitializer { if (consumers_.length != 0) { uint consumersLen = consumers_.length; for (uint i = 0; i < consumersLen; i++) { _addConsumer(consumers_[i]); } } _initializeGatewayRecipient(gateway_); } /** * @notice Starts consuming * @dev Add caller as a consumer */ function startConsuming() external { _addConsumer(_getSender()); } /** * @notice Stops consuming * @dev Remove caller from consumers */ function stopConsuming() external { address consumer = _getSender(); require( consumers[consumer], "WrappedWeiToken: consumer doesn't exist" ); consumers[consumer] = false; emit ConsumerRemoved(consumer); } /** * @notice Deposits `msg.value` to address * @param to to address */ function depositTo( address to ) external payable { _mint(to, msg.value); } /** * @notice Withdraws * @param value value to withdraw */ function withdraw( uint256 value ) external { _withdraw(_getSender(), _getSender(), value); } /** * @notice Withdraws to address * @param to to address * @param value value to withdraw */ function withdrawTo( address to, uint256 value ) external { _withdraw(_getSender(), to, value); } /** * @notice Withdraws all */ function withdrawAll() external { address sender = _getSender(); _withdraw(sender, sender, balances[sender]); } /** * @notice Withdraws all to address * @param to to address */ function withdrawAllTo( address to ) external { address sender = _getSender(); _withdraw(sender, to, balances[sender]); } // external functions (views) /** * @notice Checks if consumer exists * @param consumer consumer address * @return true if consumer exists */ function isConsumer( address consumer ) external view returns (bool) { return consumers[consumer]; } // internal functions function _transfer( address from, address to, uint256 value ) override internal { if (consumers[to]) { _withdraw(from, to, value); } else { super._transfer(from, to, value); } } // internal functions (views) function _getSender() override internal view returns (address) { return _getContextAccount(); } // private functions function _addConsumer( address consumer ) private { require( !consumers[consumer], "WrappedWeiToken: consumer already exists" ); consumers[consumer] = true; emit ConsumerAdded(consumer); } function _withdraw( address from, address to, uint256 value ) private { _burn(from, value); require( // solhint-disable-next-line check-send-result payable(to).send(value), "WrappedWeiToken: transaction reverted" ); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import "../common/libs/ECDSALib.sol"; import "../common/libs/SafeMathLib.sol"; import "../common/lifecycle/Initializable.sol"; import "../common/signature/SignatureValidator.sol"; import "../external/ExternalAccountRegistry.sol"; import "../personal/PersonalAccountRegistry.sol"; /** * @title Gateway * * @notice GSN replacement * * @author Stanisław Głogowski <[email protected]> */ contract Gateway is Initializable, SignatureValidator { using ECDSALib for bytes32; using SafeMathLib for uint256; struct DelegatedBatch { address account; uint256 nonce; address[] to; bytes[] data; } struct DelegatedBatchWithGasPrice { address account; uint256 nonce; address[] to; bytes[] data; uint256 gasPrice; } bytes32 private constant HASH_PREFIX_DELEGATED_BATCH = keccak256( "DelegatedBatch(address account,uint256 nonce,address[] to,bytes[] data)" ); bytes32 private constant HASH_PREFIX_DELEGATED_BATCH_WITH_GAS_PRICE = keccak256( "DelegatedBatchWithGasPrice(address account,uint256 nonce,address[] to,bytes[] data,uint256 gasPrice)" ); ExternalAccountRegistry public externalAccountRegistry; PersonalAccountRegistry public personalAccountRegistry; mapping(address => uint256) private accountNonce; // events /** * @dev Emitted when the single batch is delegated * @param sender sender address * @param batch batch * @param succeeded if succeeded */ event BatchDelegated( address sender, bytes batch, bool succeeded ); /** * @dev Public constructor */ constructor() public Initializable() SignatureValidator() {} // external functions /** * @notice Initializes `Gateway` contract * @param externalAccountRegistry_ `ExternalAccountRegistry` contract address * @param personalAccountRegistry_ `PersonalAccountRegistry` contract address */ function initialize( ExternalAccountRegistry externalAccountRegistry_, PersonalAccountRegistry personalAccountRegistry_ ) external onlyInitializer { externalAccountRegistry = externalAccountRegistry_; personalAccountRegistry = personalAccountRegistry_; } // public functions /** * @notice Sends batch * @dev `GatewayRecipient` context api: * `_getContextAccount` will return `msg.sender` * `_getContextSender` will return `msg.sender` * * @param to array of batch recipients contracts * @param data array of batch data */ function sendBatch( address[] memory to, bytes[] memory data ) public { _sendBatch( msg.sender, msg.sender, to, data ); } /** * @notice Sends batch from the account * @dev `GatewayRecipient` context api: * `_getContextAccount` will return `account` arg * `_getContextSender` will return `msg.sender` * * @param account account address * @param to array of batch recipients contracts * @param data array of batch data */ function sendBatchFromAccount( address account, address[] memory to, bytes[] memory data ) public { _sendBatch( account, msg.sender, to, data ); } /** * @notice Delegates batch from the account * @dev Use `hashDelegatedBatch` to create sender message payload. * * `GatewayRecipient` context api: * `_getContextAccount` will return `account` arg * `_getContextSender` will return recovered address from `senderSignature` arg * * @param account account address * @param nonce next account nonce * @param to array of batch recipients contracts * @param data array of batch data * @param senderSignature sender signature */ function delegateBatch( address account, uint256 nonce, address[] memory to, bytes[] memory data, bytes memory senderSignature ) public { require( nonce > accountNonce[account], "Gateway: nonce is lower than current account nonce" ); address sender = _hashDelegatedBatch( account, nonce, to, data ).recoverAddress(senderSignature); accountNonce[account] = nonce; _sendBatch( account, sender, to, data ); } /** * @notice Delegates batch from the account (with gas price) * * @dev Use `hashDelegatedBatchWithGasPrice` to create sender message payload (tx.gasprice as gasPrice) * * `GatewayRecipient` context api: * `_getContextAccount` will return `account` arg * `_getContextSender` will return recovered address from `senderSignature` arg * * @param account account address * @param nonce next account nonce * @param to array of batch recipients contracts * @param data array of batch data * @param senderSignature sender signature */ function delegateBatchWithGasPrice( address account, uint256 nonce, address[] memory to, bytes[] memory data, bytes memory senderSignature ) public { require( nonce > accountNonce[account], "Gateway: nonce is lower than current account nonce" ); address sender = _hashDelegatedBatchWithGasPrice( account, nonce, to, data, tx.gasprice ).recoverAddress(senderSignature); accountNonce[account] = nonce; _sendBatch( account, sender, to, data ); } /** * @notice Delegates multiple batches * @dev It will revert when all batches fail * @param batches array of batches * @param revertOnFailure reverts on any error */ function delegateBatches( bytes[] memory batches, bool revertOnFailure ) public { require( batches.length > 0, "Gateway: cannot delegate empty batches" ); bool anySucceeded; for (uint256 i = 0; i < batches.length; i++) { // solhint-disable-next-line avoid-low-level-calls (bool succeeded,) = address(this).call(batches[i]); if (revertOnFailure) { require( succeeded, "Gateway: batch reverted" ); } else if (succeeded && !anySucceeded) { anySucceeded = true; } emit BatchDelegated( msg.sender, batches[i], succeeded ); } if (!anySucceeded) { revert("Gateway: all batches reverted"); } } // public functions (views) /** * @notice Hashes `DelegatedBatch` message payload * @param delegatedBatch struct * @return hash */ function hashDelegatedBatch( DelegatedBatch memory delegatedBatch ) public view returns (bytes32) { return _hashDelegatedBatch( delegatedBatch.account, delegatedBatch.nonce, delegatedBatch.to, delegatedBatch.data ); } /** * @notice Hashes `DelegatedBatchWithGasPrice` message payload * @param delegatedBatch struct * @return hash */ function hashDelegatedBatchWithGasPrice( DelegatedBatchWithGasPrice memory delegatedBatch ) public view returns (bytes32) { return _hashDelegatedBatchWithGasPrice( delegatedBatch.account, delegatedBatch.nonce, delegatedBatch.to, delegatedBatch.data, delegatedBatch.gasPrice ); } // external functions (views) /** * @notice Gets next account nonce * @param account account address * @return next nonce */ function getAccountNextNonce( address account ) external view returns (uint256) { return accountNonce[account].add(1); } // private functions function _sendBatch( address account, address sender, address[] memory to, bytes[] memory data ) private { require( account != address(0), "Gateway: cannot send from 0x0 account" ); require( to.length > 0, "Gateway: cannot send empty batch" ); require( data.length == to.length, "Gateway: invalid batch" ); if (account != sender) { require( personalAccountRegistry.verifyAccountOwner(account, sender) || externalAccountRegistry.verifyAccountOwner(account, sender), "Gateway: sender is not the account owner" ); } bool succeeded; for (uint256 i = 0; i < data.length; i++) { require( to[i] != address(0), "Gateway: cannot send to 0x0" ); // solhint-disable-next-line avoid-low-level-calls (succeeded,) = to[i].call(abi.encodePacked(data[i], account, sender)); require( succeeded, "Gateway: batch transaction reverted" ); } } // private functions (views) function _hashDelegatedBatch( address account, uint256 nonce, address[] memory to, bytes[] memory data ) private view returns (bytes32) { return _hashMessagePayload(HASH_PREFIX_DELEGATED_BATCH, abi.encodePacked( account, nonce, to, _concatBytes(data) )); } function _hashDelegatedBatchWithGasPrice( address account, uint256 nonce, address[] memory to, bytes[] memory data, uint256 gasPrice ) private view returns (bytes32) { return _hashMessagePayload(HASH_PREFIX_DELEGATED_BATCH_WITH_GAS_PRICE, abi.encodePacked( account, nonce, to, _concatBytes(data), gasPrice )); } // private functions (pure) function _concatBytes(bytes[] memory data) private pure returns (bytes memory) { bytes memory result; uint dataLen = data.length; for (uint i = 0 ; i < dataLen ; i++) { result = abi.encodePacked(result, data[i]); } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import "../common/access/Guarded.sol"; import "../common/lifecycle/Initializable.sol"; import "../common/signature/SignatureValidator.sol"; import "../gateway/GatewayRecipient.sol"; import "./resolvers/ENSAddressResolver.sol"; import "./resolvers/ENSNameResolver.sol"; import "./resolvers/ENSPubKeyResolver.sol"; import "./resolvers/ENSTextResolver.sol"; import "./ENSRegistry.sol"; /** * @title ENS controller * * @notice ENS subnode registrar * * @dev The process of adding root node consists of 3 steps: * 1. `submitNode` - should be called from ENS node owner, * 2. Change ENS node owner in ENS registry to ENS controller, * 3. `verifyNode` - should be called from previous ENS node owner, * * To register sub node, `msg.sender` need to send valid signature from one of guardian key. * Once registration is complete `msg.sender` becoming both node owner and `addr` record value. * * After registration sub node cannot be replaced. * * @author Stanisław Głogowski <[email protected]> */ contract ENSController is Guarded, Initializable, SignatureValidator, GatewayRecipient, ENSAddressResolver, ENSNameResolver, ENSPubKeyResolver, ENSTextResolver { struct SubNodeRegistration { address account; bytes32 node; bytes32 label; } bytes4 private constant INTERFACE_META_ID = bytes4(keccak256(abi.encodePacked("supportsInterface(bytes4)"))); bytes32 private constant HASH_PREFIX_SUB_NODE_REGISTRATION = keccak256( "SubNodeRegistration(address account,bytes32 node,bytes32 label)" ); ENSRegistry public registry; mapping(bytes32 => address) public nodeOwners; // events /** * @dev Emitted when new node is submitted * @param node node name hash * @param owner owner address */ event NodeSubmitted( bytes32 node, address owner ); /** * @dev Emitted when the existing owner is verified * @param node node name hash */ event NodeVerified( bytes32 node ); /** * @dev Emitted when new node is released * @param node node name hash * @param owner owner address */ event NodeReleased( bytes32 node, address owner ); /** * @dev Emitted when ENS registry address is changed * @param registry registry address */ event RegistryChanged( address registry ); /** * @dev Public constructor */ constructor() public Guarded() Initializable() SignatureValidator() {} // external functions /** * @notice Initializes `ENSController` contract * @param registry_ ENS registry address * @param gateway_ gateway address */ function initialize( ENSRegistry registry_, address[] calldata guardians_, address gateway_ ) external onlyInitializer { require( address(registry_) != address(0), "ENSController: cannot set 0x0 registry" ); registry = registry_; // Guarded _initializeGuarded(guardians_); // GatewayRecipient _initializeGatewayRecipient(gateway_); } /** * @notice Sets registry * @param registry_ registry address */ function setRegistry( ENSRegistry registry_ ) external onlyGuardian { require( address(registry_) != address(0), "ENSController: cannot set 0x0 registry" ); require( registry_ != registry, "ENSController: registry already set" ); registry = registry_; emit RegistryChanged( address(registry) ); } /** * @notice Submits node * @dev Should be called from the current ENS node owner * @param node node name hash */ function submitNode( bytes32 node ) external { address owner = _getContextAccount(); require( _addr(node) == address(0), "ENSController: node already exists" ); require( nodeOwners[node] == address(0), "ENSController: node already submitted" ); require( registry.owner(node) == owner, "ENSController: invalid ens node owner" ); nodeOwners[node] = owner; emit NodeSubmitted(node, owner); } /** * @notice Verifies node * @dev Should be called from the previous ENS node owner * @param node node name hash */ function verifyNode( bytes32 node ) external { address owner = _getContextAccount(); require( _addr(node) == address(0), "ENSController: node already exists" ); require( nodeOwners[node] == owner, "ENSController: invalid node owner" ); require( registry.owner(node) == address(this), "ENSController: invalid ens node owner" ); _setAddr(node, address(this)); registry.setResolver(node, address(this)); emit NodeVerified(node); } /** * @notice Releases node * @dev Should be called from the previous ENS node owner * @param node node name hash */ function releaseNode( bytes32 node ) external { address owner = _getContextAccount(); require( _addr(node) == address(this), "ENSController: node doesn't exist" ); require( nodeOwners[node] == owner, "ENSController: invalid node owner" ); registry.setOwner(node, owner); delete nodeOwners[node]; emit NodeReleased(node, owner); } /** * @notice Sync address * @param node node name hash */ function syncAddr( bytes32 node ) external { address account = _getContextAccount(); require( account == registry.owner(node), "ENSController: caller is not the node owner" ); require( registry.resolver(node) == address(this), "ENSController: invalid node resolver" ); require( _addr(node) == address(0), "ENSController: node already in sync" ); _setAddr(node, account); } /** * @notice Registers sub node * @param node node name hash * @param label label hash * @param guardianSignature guardian signature */ function registerSubNode( bytes32 node, bytes32 label, bytes calldata guardianSignature ) external { address account = _getContextAccount(); bytes32 messageHash = _hashSubNodeRegistration( account, node, label ); require( _verifyGuardianSignature(messageHash, guardianSignature), "ENSController: invalid guardian signature" ); bytes32 subNode = keccak256( abi.encodePacked( node, label ) ); require( _addr(node) == address(this), "ENSController: invalid node" ); require( _addr(subNode) == address(0), "ENSController: label already taken" ); registry.setSubnodeRecord(node, label, address(this), address(this), 0); registry.setOwner(subNode, account); _setAddr(subNode, account); } // external functions (pure) function supportsInterface( bytes4 interfaceID ) external pure returns(bool) { return interfaceID == INTERFACE_META_ID || interfaceID == INTERFACE_ADDR_ID || interfaceID == INTERFACE_ADDRESS_ID || interfaceID == INTERFACE_NAME_ID || interfaceID == INTERFACE_PUB_KEY_ID || interfaceID == INTERFACE_TEXT_ID; } // public functions (views) /** * @notice Hashes `SubNodeRegistration` message payload * @param subNodeRegistration struct * @return hash */ function hashSubNodeRegistration( SubNodeRegistration memory subNodeRegistration ) public view returns (bytes32) { return _hashSubNodeRegistration( subNodeRegistration.account, subNodeRegistration.node, subNodeRegistration.label ); } // internal functions (views) function _isNodeOwner( bytes32 node ) internal override view returns (bool) { return registry.owner(node) == _getContextAccount(); } // private functions (views) function _hashSubNodeRegistration( address account, bytes32 node, bytes32 label ) private view returns (bytes32) { return _hashMessagePayload(HASH_PREFIX_SUB_NODE_REGISTRATION, abi.encodePacked( account, node, label )); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "./ENSAbstractResolver.sol"; /** * @title ENS abstract address resolver * * @dev Base on https://github.com/ensdomains/resolvers/blob/f7d62ab04bfe1692a4344f6f1d31ff81315a98c3/contracts/profiles/AddrResolver.sol */ abstract contract ENSAddressResolver is ENSAbstractResolver { bytes4 internal constant INTERFACE_ADDR_ID = bytes4(keccak256(abi.encodePacked("addr(bytes32)"))); bytes4 internal constant INTERFACE_ADDRESS_ID = bytes4(keccak256(abi.encodePacked("addr(bytes32,uint)"))); uint internal constant COIN_TYPE_ETH = 60; mapping(bytes32 => mapping(uint => bytes)) internal resolverAddresses; // events event AddrChanged( bytes32 indexed node, address addr ); event AddressChanged( bytes32 indexed node, uint coinType, bytes newAddress ); // external functions function setAddr( bytes32 node, address addr_ ) external onlyNodeOwner(node) { _setAddr(node, addr_); } function setAddr( bytes32 node, uint coinType, bytes memory addr_ ) external onlyNodeOwner(node) { _setAddr(node, coinType, addr_); } // external functions (views) function addr( bytes32 node ) external view returns (address) { return _addr(node); } function addr( bytes32 node, uint coinType ) external view returns (bytes memory) { return resolverAddresses[node][coinType]; } // internal functions function _setAddr( bytes32 node, address addr_ ) internal { _setAddr(node, COIN_TYPE_ETH, _addressToBytes(addr_)); } function _setAddr( bytes32 node, uint coinType, bytes memory addr_ ) internal { emit AddressChanged(node, coinType, addr_); if(coinType == COIN_TYPE_ETH) { emit AddrChanged(node, _bytesToAddress(addr_)); } resolverAddresses[node][coinType] = addr_; } // internal functions (views) function _addr( bytes32 node ) internal view returns (address) { address result; bytes memory addr_ = resolverAddresses[node][COIN_TYPE_ETH]; if (addr_.length > 0) { result = _bytesToAddress(addr_); } return result; } // private function (pure) function _bytesToAddress( bytes memory data ) private pure returns(address payable) { address payable result; require(data.length == 20); // solhint-disable-next-line no-inline-assembly assembly { result := div(mload(add(data, 32)), exp(256, 12)) } return result; } function _addressToBytes( address addr_ ) private pure returns(bytes memory) { bytes memory result = new bytes(20); // solhint-disable-next-line no-inline-assembly assembly { mstore(add(result, 32), mul(addr_, exp(256, 12))) } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "./ENSAbstractResolver.sol"; /** * @title ENS abstract name resolver * * @dev Base on https://github.com/ensdomains/resolvers/blob/f7d62ab04bfe1692a4344f6f1d31ff81315a98c3/contracts/profiles/NameResolver.sol */ abstract contract ENSNameResolver is ENSAbstractResolver { bytes4 internal constant INTERFACE_NAME_ID = bytes4(keccak256(abi.encodePacked("name(bytes32)"))); mapping(bytes32 => string) internal resolverNames; // events event NameChanged( bytes32 indexed node, string name ); // external functions function setName( bytes32 node, string calldata name ) external onlyNodeOwner(node) { resolverNames[node] = name; emit NameChanged(node, name); } // external functions (views) function name( bytes32 node ) external view returns (string memory) { return resolverNames[node]; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "./ENSAbstractResolver.sol"; /** * @title ENS abstract pub key resolver * * @dev Base on https://github.com/ensdomains/resolvers/blob/f7d62ab04bfe1692a4344f6f1d31ff81315a98c3/contracts/profiles/PubkeyResolver.sol */ abstract contract ENSPubKeyResolver is ENSAbstractResolver { bytes4 internal constant INTERFACE_PUB_KEY_ID = bytes4(keccak256(abi.encodePacked("pubkey(bytes32)"))); struct PubKey { bytes32 x; bytes32 y; } mapping(bytes32 => PubKey) internal resolverPubKeys; // events event PubkeyChanged( bytes32 indexed node, bytes32 x, bytes32 y ); // external functions (views) function setPubkey( bytes32 node, bytes32 x, bytes32 y ) external onlyNodeOwner(node) { resolverPubKeys[node] = PubKey(x, y); emit PubkeyChanged(node, x, y); } // external functions (views) function pubkey( bytes32 node ) external view returns (bytes32 x, bytes32 y) { return (resolverPubKeys[node].x, resolverPubKeys[node].y); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "./ENSAbstractResolver.sol"; /** * @title ENS abstract text resolver * * @dev Base on https://github.com/ensdomains/resolvers/blob/f7d62ab04bfe1692a4344f6f1d31ff81315a98c3/contracts/profiles/TextResolver.sol */ abstract contract ENSTextResolver is ENSAbstractResolver { bytes4 internal constant INTERFACE_TEXT_ID = bytes4(keccak256(abi.encodePacked("text(bytes32,string)"))); mapping(bytes32 => mapping(string => string)) internal resolverTexts; // events event TextChanged( bytes32 indexed node, string indexed indexedKey, string key ); // external functions (views) function setText( bytes32 node, string calldata key, string calldata value ) external onlyNodeOwner(node) { resolverTexts[node][key] = value; emit TextChanged(node, key, key); } // external functions (views) function text( bytes32 node, string calldata key ) external view returns (string memory) { return resolverTexts[node][key]; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title ENS registry * * @dev Base on https://github.com/ensdomains/ens/blob/ff0f41747c05f1598973b0fe7ad0d9e09565dfcd/contracts/ENSRegistry.sol */ contract ENSRegistry { struct Record { address owner; address resolver; uint64 ttl; } mapping (bytes32 => Record) private records; mapping (address => mapping(address => bool)) private operators; // events event NewOwner( bytes32 indexed node, bytes32 indexed label, address owner ); event Transfer( bytes32 indexed node, address owner ); event NewResolver( bytes32 indexed node, address resolver ); event NewTTL( bytes32 indexed node, uint64 ttl ); event ApprovalForAll( address indexed owner, address indexed operator, bool approved ); // modifiers modifier authorised( bytes32 node ) { address owner = records[node].owner; require( owner == msg.sender || operators[owner][msg.sender], "ENSRegistry: reverted by authorised modifier" ); _; } /** * @dev Public constructor */ constructor() public { // solhint-disable-next-line avoid-tx-origin records[0x0].owner = tx.origin; } // external functions function setRecord( bytes32 node, address owner_, address resolver_, uint64 ttl_ ) external { setOwner(node, owner_); _setResolverAndTTL(node, resolver_, ttl_); } function setTTL( bytes32 node, uint64 ttl_ ) external authorised(node) { records[node].ttl = ttl_; emit NewTTL(node, ttl_); } function setSubnodeRecord( bytes32 node, bytes32 label, address owner_, address resolver_, uint64 ttl_ ) external { bytes32 subNode = setSubnodeOwner(node, label, owner_); _setResolverAndTTL(subNode, resolver_, ttl_); } function setApprovalForAll( address operator, bool approved ) external { operators[msg.sender][operator] = approved; emit ApprovalForAll( msg.sender, operator, approved ); } // external functions (views) function owner( bytes32 node ) external view returns (address) { address addr = records[node].owner; if (addr == address(this)) { return address(0x0); } return addr; } function resolver( bytes32 node ) external view returns (address) { return records[node].resolver; } function ttl( bytes32 node ) external view returns (uint64) { return records[node].ttl; } function recordExists( bytes32 node ) external view returns (bool) { return records[node].owner != address(0x0); } function isApprovedForAll( address owner_, address operator ) external view returns (bool) { return operators[owner_][operator]; } // public functions function setOwner( bytes32 node, address owner_ ) public authorised(node) { records[node].owner = owner_; emit Transfer(node, owner_); } function setResolver( bytes32 node, address resolver_ ) public authorised(node) { records[node].resolver = resolver_; emit NewResolver(node, resolver_); } function setSubnodeOwner( bytes32 node, bytes32 label, address owner_ ) public authorised(node) returns(bytes32) { bytes32 subNode = keccak256(abi.encodePacked(node, label)); records[subNode].owner = owner_; emit NewOwner(node, label, owner_); return subNode; } // private functions function _setResolverAndTTL( bytes32 node, address resolver_, uint64 ttl_ ) private { if (resolver_ != records[node].resolver) { records[node].resolver = resolver_; emit NewResolver(node, resolver_); } if (ttl_ != records[node].ttl) { records[node].ttl = ttl_; emit NewTTL(node, ttl_); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title ENS abstract resolver * * @dev Base on https://github.com/ensdomains/resolvers/blob/f7d62ab04bfe1692a4344f6f1d31ff81315a98c3/contracts/ResolverBase.sol */ abstract contract ENSAbstractResolver { // modifiers modifier onlyNodeOwner(bytes32 node) { require( _isNodeOwner(node), "ENSAbstractResolver: reverted by onlyNodeOwner modifier" ); _; } // internal functions (views) function _isNodeOwner( bytes32 node ) internal virtual view returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../common/libs/AddressLib.sol"; import "../common/lifecycle/Initializable.sol"; import "./resolvers/ENSNameResolver.sol"; import "./ENSRegistry.sol"; /** * @title ENS reverse registrar * * @dev Base on https://github.com/ensdomains/ens/blob/ff0f41747c05f1598973b0fe7ad0d9e09565dfcd/contracts/ReverseRegistrar.sol */ contract ENSReverseRegistrar is Initializable { using AddressLib for address; // namehash('addr.reverse') bytes32 public constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; ENSRegistry public registry; ENSNameResolver public resolver; /** * @dev Public constructor */ constructor() public Initializable() {} // external functions /** * @notice Initializes `ENSReverseRegistrar` contract * @param registry_ ENS registry address * @param resolver_ ENS name resolver address */ function initialize( ENSRegistry registry_, ENSNameResolver resolver_ ) external onlyInitializer { registry = registry_; resolver = resolver_; } // external functions function claim( address owner ) public returns (bytes32) { return _claimWithResolver(owner, address(0)); } function claimWithResolver( address owner, address resolver_ ) public returns (bytes32) { return _claimWithResolver(owner, resolver_); } function setName( string memory name ) public returns (bytes32) { bytes32 node = _claimWithResolver(address(this), address(resolver)); resolver.setName(node, name); return node; } // external functions (pure) function node( address addr_ ) external pure returns (bytes32) { return keccak256(abi.encodePacked(ADDR_REVERSE_NODE, addr_.toSha3Hash())); } // private functions function _claimWithResolver( address owner, address resolver_ ) private returns (bytes32) { bytes32 label = address(msg.sender).toSha3Hash(); bytes32 node_ = keccak256(abi.encodePacked(ADDR_REVERSE_NODE, label)); address currentOwner = registry.owner(node_); if (resolver_ != address(0x0) && resolver_ != registry.resolver(node_)) { if (currentOwner != address(this)) { registry.setSubnodeOwner(ADDR_REVERSE_NODE, label, address(this)); currentOwner = address(this); } registry.setResolver(node_, resolver_); } // Update the owner if required if (currentOwner != owner) { registry.setSubnodeOwner(ADDR_REVERSE_NODE, label, owner); } return node_; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; /** * @title Address library */ library AddressLib { /** * @notice Converts address into sha3 hash * @param self address * @return sha3 hash */ function toSha3Hash( address self ) internal pure returns (bytes32) { bytes32 result; // solhint-disable-next-line no-inline-assembly assembly { let lookup := 0x3031323334353637383961626364656600000000000000000000000000000000 for { let i := 40 } gt(i, 0) { } { i := sub(i, 1) mstore8(i, byte(and(self, 0xf), lookup)) self := div(self, 0x10) i := sub(i, 1) mstore8(i, byte(and(self, 0xf), lookup)) self := div(self, 0x10) } result := keccak256(0, 40) } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import "../common/lifecycle/Initializable.sol"; import "./resolvers/ENSAddressResolver.sol"; import "./resolvers/ENSNameResolver.sol"; import "./ENSRegistry.sol"; /** * @title ENS helper * * @author Stanisław Głogowski <[email protected]> */ contract ENSHelper is Initializable { ENSRegistry public registry; /** * @dev Public constructor */ constructor() public Initializable() {} // external functions /** * @notice Initializes `ENSLookupHelper` contract * @param registry_ ENS registry address */ function initialize( ENSRegistry registry_ ) external onlyInitializer { registry = registry_; } // external functions (views) /** * @notice Gets nodes addresses * @param nodes array of nodes * @return nodes addresses */ function getAddresses( bytes32[] memory nodes ) external view returns (address[] memory) { uint nodesLen = nodes.length; address[] memory result = new address[](nodesLen); for (uint i = 0; i < nodesLen; i++) { result[i] = _getAddress(nodes[i]); } return result; } /** * @notice Gets nodes names * @param nodes array of nodes * @return nodes names */ function getNames( bytes32[] memory nodes ) external view returns (string[] memory) { uint nodesLen = nodes.length; string[] memory result = new string[](nodesLen); for (uint i = 0; i < nodesLen; i++) { result[i] = _getName(nodes[i]); } return result; } // private functions (views) function _getAddress( bytes32 node ) private view returns (address) { address result; address resolver = registry.resolver(node); if (resolver != address(0)) { try ENSAddressResolver(resolver).addr(node) returns (address addr) { result = addr; } catch { // } } return result; } function _getName( bytes32 node ) private view returns (string memory) { string memory result; address resolver = registry.resolver(node); if (resolver != address(0)) { try ENSNameResolver(resolver).name(node) returns (string memory name) { result = name; } catch { // } } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../Guarded.sol"; /** * @title Guarded mock * * @dev Used in `Guarded` contract tests * * @author Stanisław Głogowski <[email protected]> */ contract GuardedMock is Guarded { /** * @dev Public constructor * @param guardians_ array of guardians addresses */ constructor( address[] memory guardians_ ) public { _initializeGuarded(guardians_); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../../libs/ECDSALib.sol"; import "../../libs/ECDSAExtendedLib.sol"; import "../AccountRegistry.sol"; /** * @title Account registry mock * * @author Stanisław Głogowski <[email protected]> */ contract AccountRegistryMock is AccountRegistry { using ECDSALib for bytes32; using ECDSAExtendedLib for bytes; mapping(address => mapping(address => bool)) private mockedAccountsOwners; // external functions function mockAccountOwners( address account, address[] memory owners ) external { uint ownersLen = owners.length; for (uint i = 0; i < ownersLen; i++) { mockedAccountsOwners[account][owners[i]] = true; } } // external functions (views) function isValidAccountSignature( address account, bytes32 messageHash, bytes calldata signature ) override external view returns (bool) { address recovered = messageHash.recoverAddress(signature); return mockedAccountsOwners[account][recovered]; } function isValidAccountSignature( address account, bytes calldata message, bytes calldata signature ) override external view returns (bool) { address recovered = message.toEthereumSignedMessageHash().recoverAddress(signature); return mockedAccountsOwners[account][recovered]; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../AccountController.sol"; /** * @title Account controller mock * * @author Stanisław Głogowski <[email protected]> */ contract AccountControllerMock is AccountController { /** * @dev Public constructor * @param accountRegistry_ account registry address * @param accountImplementation_ account implementation address */ constructor( address accountRegistry_, address accountImplementation_ ) public AccountController() { _initializeAccountController(accountRegistry_, accountImplementation_); } // external functions /** * @notice Sets account registry * @param accountRegistry_ account registry address */ function setAccountRegistry( address accountRegistry_ ) external { _setAccountRegistry(accountRegistry_, true); } /** * @notice Sets account implementation * @param accountImplementation_ account implementation address */ function setAccountImplementation( address accountImplementation_ ) external { _setAccountImplementation(accountImplementation_, true); } /** * @notice Deploys account * @param salt CREATE2 salt */ function deployAccount( bytes32 salt ) external { _deployAccount(salt, true); } /** * @notice Upgrades account * @param account account address */ function upgradeAccount( address account ) external { _upgradeAccount(account, true); } /** * @notice Executes transaction from the account * @param account account address * @param to to address * @param value value * @param data data */ function executeAccountTransaction( address account, address to, uint256 value, bytes memory data ) external { _executeAccountTransaction(account, to, value, data, true); } // external functions (views) /** * @notice Computes account CREATE2 address * @param salt CREATE2 salt * @return account address */ function computeAccountAddress( bytes32 salt ) external view returns (address) { return _computeAccountAddress(salt); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../lifecycle/Initializable.sol"; import "./AccountBase.sol"; import "./AccountRegistry.sol"; /** * @title Account implementation (version 1) * * @author Stanisław Głogowski <[email protected]> */ contract AccountImplementationV1 is Initializable, AccountBase { bytes32 constant private ERC777_TOKENS_RECIPIENT_INTERFACE_HASH = keccak256(abi.encodePacked("ERC777TokensRecipient")); bytes32 constant private ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")); bytes4 constant private ERC1271_VALID_MESSAGE_HASH_SIGNATURE = bytes4(keccak256(abi.encodePacked("isValidSignature(bytes32,bytes)"))); bytes4 constant private ERC1271_VALID_MESSAGE_SIGNATURE = bytes4(keccak256(abi.encodePacked("isValidSignature(bytes,bytes)"))); bytes4 constant private ERC1271_INVALID_SIGNATURE = 0xffffffff; /** * @dev Internal constructor */ constructor() internal Initializable() {} // external functions /** * @notice Initializes `AccountImplementation` contract * @param registry_ registry address */ function initialize( address registry_ ) external onlyInitializer { registry = registry_; } // external functions (views) // ERC1820 function canImplementInterfaceForAddress( bytes32 interfaceHash, address addr ) external view returns(bytes32) { bytes32 result; if (interfaceHash == ERC777_TOKENS_RECIPIENT_INTERFACE_HASH && addr == address(this)) { result = ERC1820_ACCEPT_MAGIC; } return result; } // ERC1271 function isValidSignature( bytes32 messageHash, bytes calldata signature ) external view returns (bytes4) { return AccountRegistry(registry).isValidAccountSignature(address(this), messageHash, signature) ? ERC1271_VALID_MESSAGE_HASH_SIGNATURE : ERC1271_INVALID_SIGNATURE; } function isValidSignature( bytes calldata message, bytes calldata signature ) external view returns (bytes4) { return AccountRegistry(registry).isValidAccountSignature(address(this), message, signature) ? ERC1271_VALID_MESSAGE_SIGNATURE : ERC1271_INVALID_SIGNATURE; } // external functions (pure) // ERC721 function onERC721Received( address, address, uint256, bytes calldata ) external pure returns (bytes4) { return this.onERC721Received.selector; } // ERC1155 function onERC1155Received( address, address, uint256, uint256, bytes calldata ) external pure returns (bytes4) { return this.onERC1155Received.selector; } // ERC777 function tokensReceived( address, address, address, uint256, bytes calldata, bytes calldata ) external pure {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../common/account/AccountImplementationV1.sol"; /** * @title Personal account implementation (version 1) * * @author Stanisław Głogowski <[email protected]> */ contract PersonalAccountImplementationV1 is AccountImplementationV1 { /** * @dev Public constructor */ constructor() public AccountImplementationV1() {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../AccountImplementationV1.sol"; /** * @title Account implementation mock (version 1) * * @author Stanisław Głogowski <[email protected]> */ contract AccountImplementationV1Mock is AccountImplementationV1 { /** * @dev Public constructor * @param registry_ account registry address */ constructor( address registry_ ) public AccountImplementationV1() { registry = registry_; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../token/ERC20Token.sol"; import "../libs/SafeMathLib.sol"; /** * @title Balances helper * * @author Jegor Sidorenko <[email protected]> * @author Stanisław Głogowski <[email protected]> */ contract BalancesHelper { using SafeMathLib for uint256; // external functions /** * @notice Checks the token balances of accounts for multiple tokens. * @dev Pass 0x0 as a "token" address to get ETH balance. * * Possible error throws: * - extremely large arrays for account and or tokens (gas cost too high) * * @param accounts array of accounts addresses * @param tokens array of tokens addresses * @return a one-dimensional that's user.length * tokens.length long. The * array is ordered by all of the 0th accounts token balances, then the 1th * user, and so on. */ function getBalances( address[] calldata accounts, address[] calldata tokens ) external view returns (uint[] memory) { uint[] memory result = new uint[](accounts.length.mul(tokens.length)); for (uint i = 0; i < accounts.length; i++) { for (uint j = 0; j < tokens.length; j++) { uint index = j.add(tokens.length.mul(i)); if (tokens[j] != address(0x0)) { result[index] = _getBalance(accounts[i], tokens[j]); } else { result[index] = accounts[i].balance; } } } return result; } // private functions function _getBalance( address account, address token ) private view returns (uint256) { uint256 result = 0; uint256 tokenCode; /// @dev check if token is actually a contract // solhint-disable-next-line no-inline-assembly assembly { tokenCode := extcodesize(token) } // contract code size if (tokenCode > 0) { /// @dev is it a contract and does it implement balanceOf // solhint-disable-next-line avoid-low-level-calls (bool methodExists,) = token.staticcall(abi.encodeWithSelector( ERC20Token(token).balanceOf.selector, account )); if (methodExists) { result = ERC20Token(token).balanceOf(account); } } return result; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; import "../GatewayRecipient.sol"; /** * @title Gateway recipient mock * * @dev Used in `GatewayRecipient` contract tests * * @author Stanisław Głogowski <[email protected]> */ contract GatewayRecipientMock is GatewayRecipient { // events event Context( address account, address sender, bytes data ); /** * @dev Public constructor * @param gateway_ `Gateway` contract address */ constructor( address gateway_ ) public { _initializeGatewayRecipient(gateway_); } function emitContext() external { emit Context( _getContextAccount(), _getContextSender(), _getContextData() ); } }
{ "evmVersion": "istanbul", "metadata": { "bytecodeHash": "none", "useLiteralContent": true }, "optimizer": { "enabled": false, "runs": 200 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "abi" ] } }, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"AccountCallRefunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"accountImplementation","type":"address"}],"name":"AccountDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"accountImplementation","type":"address"}],"name":"AccountImplementationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"AccountOwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"AccountOwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"accountRegistry","type":"address"}],"name":"AccountRegistryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"response","type":"bytes"}],"name":"AccountTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"accountImplementation","type":"address"}],"name":"AccountUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"guardian","type":"address"}],"name":"GuardianAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"guardian","type":"address"}],"name":"GuardianRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"initializer","type":"address"}],"name":"Initialized","type":"event"},{"inputs":[],"name":"accountImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accountRegistry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"addAccountOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"guardian","type":"address"}],"name":"addGuardian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"saltOwner","type":"address"}],"name":"computeAccountAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"deployAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"executeAccountTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gateway","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"guardians_","type":"address[]"},{"internalType":"address","name":"accountImplementation_","type":"address"},{"internalType":"address","name":"gateway_","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isAccountDeployed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"guardian","type":"address"}],"name":"isGuardian","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes","name":"message","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"isValidAccountSignature","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"messageHash","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"isValidAccountSignature","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"refundAccountCall","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"removeAccountOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"guardian","type":"address"}],"name":"removeGuardian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"accountImplementation_","type":"address"}],"name":"upgrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"upgradeAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"verifyAccountOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"verifyAccountOwnerAtBlock","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"messageHash","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"verifyGuardianSignature","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
608060405234801561001057600080fd5b5032600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506144d1806100616000396000f3fe608060405234801561001057600080fd5b50600436106101425760003560e01c806390482d72116100b8578063d0f710d61161007c578063d0f710d61461075e578063da9fc1ae146107f7578063db63f5821461083b578063e1e382ce1461089f578063e5c7278f14610958578063f4876c7414610a1b57610142565b806390482d7214610545578063a526d83b146105fe578063bb890d3f14610642578063cade6a5d146106bc578063d089e11a1461072a57610142565b80631a8414031161010a5780631a841403146103515780633164b5e1146103bf57806334d323a414610419578063392e53cd1461049d57806343013c24146104bd578063714041561461050157610142565b80630900f010146101475780630c68ba211461018b57806311464fbe146101e5578063116191b614610219578063124e9eb31461024d575b600080fd5b6101896004803603602081101561015d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610a7f565b005b6101cd600480360360208110156101a157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b2e565b60405180821515815260200191505060405180910390f35b6101ed610b83565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610221610ba9565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6103396004803603606081101561026357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156102a057600080fd5b8201836020820111156102b257600080fd5b803590602001918460018302840111640100000000831117156102d457600080fd5b9091929391929390803590602001906401000000008111156102f557600080fd5b82018360208201111561030757600080fd5b8035906020019184600183028401116401000000008311171561032957600080fd5b9091929391929390505050610bcf565b60405180821515815260200191505060405180910390f35b6103bd6004803603606081101561036757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c88565b005b610401600480360360208110156103d557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ef3565b60405180821515815260200191505060405180910390f35b6104856004803603606081101561042f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610f4c565b60405180821515815260200191505060405180910390f35b6104a561103a565b60405180821515815260200191505060405180910390f35b6104ff600480360360208110156104d357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611092565b005b6105436004803603602081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506110aa565b005b6105fc6004803603606081101561055b57600080fd5b810190808035906020019064010000000081111561057857600080fd5b82018360208201111561058a57600080fd5b803590602001918460208302840111640100000000831117156105ac57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611370565b005b6106406004803603602081101561061457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611508565b005b6106a46004803603604081101561065857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506115b5565b60405180821515815260200191505060405180910390f35b6106fe600480360360208110156106d257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506115c9565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6107326115db565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6107df6004803603604081101561077457600080fd5b81019080803590602001909291908035906020019064010000000081111561079b57600080fd5b8201836020820111156107ad57600080fd5b803590602001918460018302840111640100000000831117156107cf57600080fd5b9091929391929390505050611601565b60405180821515815260200191505060405180910390f35b6108396004803603602081101561080d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061165a565b005b61089d6004803603604081101561085157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611670565b005b610940600480360360608110156108b557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156108fc57600080fd5b82018360208201111561090e57600080fd5b8035906020019184600183028401116401000000008311171561093057600080fd5b9091929391929390505050611905565b60405180821515815260200191505060405180910390f35b610a196004803603608081101561096e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109d557600080fd5b8201836020820111156109e757600080fd5b80359060200191846001830284011164010000000083111715610a0957600080fd5b9091929391929390505050611971565b005b610a7d60048036036040811015610a3157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506119de565b005b6000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610b20576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806143e46026913960400191505060405180910390fd5b610b2b816001611d0d565b50565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff169050919050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000610c7d86610c7885858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610c6a89898080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050611e4e565b611fb790919063ffffffff16565b612087565b905095945050505050565b610c918361223e565b50610c9b83612685565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610d2f57610d29833283600067ffffffffffffffff81118015610cee57600080fd5b506040519080825280601f01601f191660200182016040528015610d215781602001600182028036833780820191505090505b506000612786565b50610e5d565b6060610dd38484600063a9059cbb60e01b3287604051602401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506000612786565b9050600081511115610e5b57808060200190516020811015610df457600080fd5b8101908080519060200190929190505050610e5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603581526020018061440a6035913960400191505060405180910390fd5b5b505b7feb5511fbef89123439f12682f0e9d0fc9696913b26eee55434c77d62c30aa7ca83328484604051808573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200194505050505060405180910390a1505050565b6000600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff169050919050565b60008060009050610f5d8585612087565b15610f6b576001905061102f565b61102c83600560008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518060400160405290816000820160009054906101000a900460ff16151515158152602001600182015481525050612c5490919063ffffffff16565b90505b809150509392505050565b60008073ffffffffffffffffffffffffffffffffffffffff16600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614905090565b61109b8161223e565b506110a7816001612ca3565b50565b6000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1661114b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806143e46026913960400191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff163273ffffffffffffffffffffffffffffffffffffffff1614156111ed576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f477561726465643a2063616e6e6f742072656d6f76652073656c66000000000081525060200191505060405180910390fd5b6000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166112ab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f477561726465643a20677561726469616e20646f65736e27742065786973740081525060200191505060405180910390fd5b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055507fee943cdb81826d5909c559c6b1ae6908fcaf2dbc16c4b730346736b486283e8b3282604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a150565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163273ffffffffffffffffffffffffffffffffffffffff1614611416576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f815260200180614318602f913960400191505060405180910390fd5b6000600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506114a2848480806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050612f05565b6114ac3083612f5d565b6114b581612f77565b7f908408e307fc569b417f6cbec5d5a06f44a0a505ac0479b47d421a4b2fd6a1e632604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150505050565b6000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166115a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806143e46026913960400191505060405180910390fd5b6115b281612fbb565b50565b60006115c18383612087565b905092915050565b60006115d4826131e2565b9050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006116518484848080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050613238565b90509392505050565b6116638161223e565b5061166d81612685565b50565b600061167b8361223e565b90508073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611702576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b8152602001806143b9602b913960400191505060405180910390fd5b6117b9600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518060400160405290816000820160009054906101000a900460ff161515151581526020016001820154815250506132a5565b61180e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806142b7602c913960400191505060405180910390fd5b43600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055507f1ce3cbbc43ee231e5b950332f2b0b9dd7d349291a3ee411ce5c5c7ed745661bb8383604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a1505050565b60006119678561196285858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505087611fb790919063ffffffff16565b612087565b9050949350505050565b61197a8561223e565b5061198485612685565b6119d685858585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506001612786565b505050505050565b6119e78261223e565b50600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415611a6e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602d815260200180614498602d913960400191505060405180910390fd5b611b25600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518060400160405290816000820160009054906101000a900460ff161515151581526020016001820154815250506132a5565b15611b7b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602d81526020018061446b602d913960400191505060405180910390fd5b6001600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160006101000a81548160ff0219169083151502179055506000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055507f27e282f7712c5b4617277759c834b86d163dfc1aad25c8c3c5926a1c9e5644688282604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a15050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611d93576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b81526020018061421e603b913960400191505060405180910390fd5b81600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508015611e4a577f70d9f5a076620216a6050e493f3ce69749de0b68fa4b839ba2518660ba8b9cf0600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5050565b6000611e5a82516132b8565b826040516020018082805190602001908083835b60208310611e915780518252602082019150602081019050602083039250611e6e565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405260405160200180807f19457468657265756d205369676e6564204d6573736167653a0a000000000000815250601a0183805190602001908083835b60208310611f235780518252602082019150602081019050602083039250611f00565b6001836020036101000a03801982511681845116808217855250505050505090500182805190602001908083835b60208310611f745780518252602082019150602081019050602083039250611f51565b6001836020036101000a03801982511681845116808217855250505050505090500192505050604051602081830303815290604052805190602001209050919050565b6000806000905060418351141561207d5760008060006020860151925060408601519150606086015160001a9050601b8160ff161015611ff857601b810190505b601b8160ff16148061200d5750601c8160ff16145b156120795760018782858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa15801561206c573d6000803e3d6000fd5b5050506020604051035193505b5050505b8091505092915050565b600080600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff16156121ac576000600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154149050612234565b6000801b600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154141561223357612202836131e2565b73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161490505b5b8091505092915050565b6000806122496133ff565b9050600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff16156123c0576000600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154146123bb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260388152602001806142596038913960400191505060405180910390fd5b61267c565b6000801b600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101541461245d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260388152602001806142596038913960400191505060405180910390fd5b600081604051602001808273ffffffffffffffffffffffffffffffffffffffff1660601b81526014019150506040516020818303038152906040528051906020012090506124aa81613410565b73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461252d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260388152602001806142596038913960400191505060405180910390fd5b80600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055506001600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160006101000a81548160ff0219169083151502179055507f27e282f7712c5b4617277759c834b86d163dfc1aad25c8c3c5926a1c9e5644688483604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a1505b80915050919050565b600560008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff1661278357612726600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101546001613606565b506001600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160006101000a81548160ff0219169083151502179055505b50565b6060600073ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16141561280e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806143476025913960400191505060405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161415612893576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c81526020018061443f602c913960400191505060405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161415612918576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806142916026913960400191505060405180910390fd5b60608673ffffffffffffffffffffffffffffffffffffffff16633f579f428787876040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156129aa57808201518184015260208101905061298f565b50505050905090810190601f1680156129d75780820380516001836020036101000a031916815260200191505b50945050505050600060405180830381600087803b1580156129f857600080fd5b505af1158015612a0c573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015612a3657600080fd5b8101908080516040519392919084640100000000821115612a5657600080fd5b83820191506020820185811115612a6c57600080fd5b8251866001820283011164010000000082111715612a8957600080fd5b8083526020830192505050908051906020019080838360005b83811015612abd578082015181840152602081019050612aa2565b50505050905090810190601f168015612aea5780820380516001836020036101000a031916815260200191505b5060405250505090508215612c47577f99eeae1fb8801e2e878e060ec5e5b3557b3c4f8106e35d88cb573f293dae4e248787878785604051808673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018060200180602001838103835285818151815260200191508051906020019080838360005b83811015612ba1578082015181840152602081019050612b86565b50505050905090810190601f168015612bce5780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015612c07578082015181840152602081019050612bec565b50505050905090810190601f168015612c345780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15b8091505095945050505050565b60008060009050836000015115612c9957600084602001511415612c7b5760019050612c98565b6000831415612c8d5760019050612c97565b8284602001511190505b5b5b8091505092915050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16635c60da1b6040518163ffffffff1660e01b815260040160206040518083038186803b158015612d2257600080fd5b505afa158015612d36573d6000803e3d6000fd5b505050506040513d6020811015612d4c57600080fd5b810190808051906020019092919050505073ffffffffffffffffffffffffffffffffffffffff161415612dca576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b81526020018061438e602b913960400191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff1663d784d426600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b158015612e5557600080fd5b505af1158015612e69573d6000803e3d6000fd5b505050508015612f01577feec27cdb8bd6f55c2c537deb52ab094a2437e85ef5197e6f064f90bff3ff563f82600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a15b5050565b600081511415612f1d57612f1832612fbb565b612f5a565b60008151905060005b81811015612f5757612f4a838281518110612f3d57fe5b6020026020010151612fbb565b8080600101915050612f26565b50505b50565b612f6882600061375a565b612f73816000611d0d565b5050565b80600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141561305e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f477561726465643a2063616e6e6f74206164642030783020677561726469616e81525060200191505060405180910390fd5b6000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161561311d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f477561726465643a20677561726469616e20616c72656164792065786973747381525060200191505060405180910390fd5b60016000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055507fbc3292102fa77e083913064b282926717cdfaede4d35f553d66366c0a3da755a3282604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a150565b60008082604051602001808273ffffffffffffffffffffffffffffffffffffffff1660601b815260140191505060405160208183030381529060405280519060200120905061323081613410565b915050919050565b60008061324e8385611fb790919063ffffffff16565b90506000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1691505092915050565b60006132b18243612c54565b9050919050565b60606000821415613300576040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525090506133fa565b600082905060005b6000821461332a578080600101915050600a828161332257fe5b049150613308565b60608167ffffffffffffffff8111801561334357600080fd5b506040519080825280601f01601f1916602001820160405280156133765781602001600182028036833780820191505090505b50905060006001830390508593505b600084146133f257600a848161339757fe5b0660300160f81b828280600190039350815181106133b157fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600a84816133ea57fe5b049350613385565b819450505050505b919050565b600061340b601461389b565b905090565b600060606040518060200161342490613a8c565b6020820181038252601f19601f82011660405250600060a01b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600060a01b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040516020018086805190602001908083835b602083106134be578051825260208201915060208101905060208303925061349b565b6001836020036101000a0380198251168184511680821785525050505050509050018573ffffffffffffffffffffffffffffffffffffffff19168152600c018473ffffffffffffffffffffffffffffffffffffffff1660601b81526014018373ffffffffffffffffffffffffffffffffffffffff19168152600c018273ffffffffffffffffffffffffffffffffffffffff1660601b8152601401955050505050506040516020818303038152906040529050600060ff60f81b3085848051906020012060405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018473ffffffffffffffffffffffffffffffffffffffff1660601b81526014018381526020018281526020019450505050506040516020818303038152906040528051906020012090508060001c92505050919050565b60008083600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660405161365c90613a8c565b808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff168152602001925050508190604051809103906000f59050801580156136b9573d6000803e3d6000fd5b5090508215613750577f2682a218602b9036c9869f006c5405ee4a1a65267653eb7e286123e5e1afde4c81600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a15b8091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156137e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260358152602001806142e36035913960400191505060405180910390fd5b81600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508015613897577f02e3c47057b8dc27a0929d3c394c314c73aa002d46939c31c9f71d1e77e86c10600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5050565b600080600090506138aa613930565b1561392357600083600036905003905061391b600036839060148501926138d393929190613a99565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506139f3565b915050613927565b3390505b80915050919050565b600080600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156139ec57602c600036905010156139e7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061436c6022913960400191505060405180910390fd5b600190505b8091505090565b6000806014835114613a6d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f42797465734c69623a20696e76616c69642064617461206c656e67746800000081525060200191505060405180910390fd5b6c01000000000000000000000000602084015104905080915050919050565b61075180613acd83390190565b60008085851115613aa957600080fd5b83861115613ab657600080fd5b600185028301915084860390509450949250505056fe608060405234801561001057600080fd5b506040516107513803806107518339818101604052604081101561003357600080fd5b810190808051906020019092919080519060200190929190505050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555081600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050610630806101216000396000f3fe60806040526004361061004e5760003560e01c80633f579f42146100be5780635c60da1b146101e75780637b10399914610228578063d784d42614610269578063f77c4791146102ba57610055565b3661005557005b34801561006157600080fd5b50600080369050146100bc576000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050368060008037600080826000855af43d806000803e81600081146100b757816000f35b816000fd5b005b3480156100ca57600080fd5b5061016c600480360360608110156100e157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561012857600080fd5b82018360208201111561013a57600080fd5b8035906020019184600183028401116401000000008311171561015c57600080fd5b90919293919293905050506102fb565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101ac578082015181840152602081019050610191565b50505050905090810190601f1680156101d95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156101f357600080fd5b506101fc61049f565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561023457600080fd5b5061023d6104c5565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561027557600080fd5b506102b86004803603602081101561028c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506104eb565b005b3480156102c657600080fd5b506102cf6105d3565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b606060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146103a1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806105f8602c913960400191505060405180910390fd5b606060008673ffffffffffffffffffffffffffffffffffffffff1686868660405180838380828437808301925050509250505060006040518083038185875af1925050503d8060008114610411576040519150601f19603f3d011682016040523d82523d6000602084013e610416565b606091505b50809350819250505080610492576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f4163636f756e743a207472616e73616374696f6e20726576657274656400000081525060200191505060405180910390fd5b8192505050949350505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461058f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806105f8602c913960400191505060405180910390fd5b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fe436f6e74726f6c6c65643a206d73672e73656e646572206973206e6f742074686520636f6e74726f6c6c6572a164736f6c634300060c000a4163636f756e74436f6e74726f6c6c65723a2063616e6e6f7420736574206163636f756e7420496d706c656d656e746174696f6e20746f20307830506572736f6e616c4163636f756e7452656769737472793a2073656e646572206973206e6f7420746865206163636f756e74206f776e65724163636f756e74436f6e74726f6c6c65723a2063616e6e6f742073656e6420746f2073656c66506572736f6e616c4163636f756e7452656769737472793a206f776e657220646f65736e27742065786973744163636f756e74436f6e74726f6c6c65723a2063616e6e6f7420736574206163636f756e7420726567697374727920746f20307830496e697469616c697a61626c653a2074782e6f726967696e206973206e6f742074686520696e697469616c697a65724163636f756e74436f6e74726f6c6c65723a2063616e6e6f742073656e6420746f2030783047617465776179526563697069656e743a20696e76616c6964206d73672e646174614163636f756e74436f6e74726f6c6c65723a206163636f756e7420616c7265616479207570677261646564506572736f6e616c4163636f756e7452656769737472793a2063616e6e6f742072656d6f76652073656c66477561726465643a2074782e6f726967696e206973206e6f742074686520677561726469616e506572736f6e616c4163636f756e7452656769737472793a204552433230546f6b656e207472616e736665722072657665727465644163636f756e74436f6e74726f6c6c65723a2063616e6e6f742073656e6420746f20636f6e74726f6c6c6572506572736f6e616c4163636f756e7452656769737472793a206f776e657220616c726561647920657869737473506572736f6e616c4163636f756e7452656769737472793a2063616e6e6f742061646420307830206f776e6572a164736f6c634300060c000a
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106101425760003560e01c806390482d72116100b8578063d0f710d61161007c578063d0f710d61461075e578063da9fc1ae146107f7578063db63f5821461083b578063e1e382ce1461089f578063e5c7278f14610958578063f4876c7414610a1b57610142565b806390482d7214610545578063a526d83b146105fe578063bb890d3f14610642578063cade6a5d146106bc578063d089e11a1461072a57610142565b80631a8414031161010a5780631a841403146103515780633164b5e1146103bf57806334d323a414610419578063392e53cd1461049d57806343013c24146104bd578063714041561461050157610142565b80630900f010146101475780630c68ba211461018b57806311464fbe146101e5578063116191b614610219578063124e9eb31461024d575b600080fd5b6101896004803603602081101561015d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610a7f565b005b6101cd600480360360208110156101a157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b2e565b60405180821515815260200191505060405180910390f35b6101ed610b83565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610221610ba9565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6103396004803603606081101561026357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001906401000000008111156102a057600080fd5b8201836020820111156102b257600080fd5b803590602001918460018302840111640100000000831117156102d457600080fd5b9091929391929390803590602001906401000000008111156102f557600080fd5b82018360208201111561030757600080fd5b8035906020019184600183028401116401000000008311171561032957600080fd5b9091929391929390505050610bcf565b60405180821515815260200191505060405180910390f35b6103bd6004803603606081101561036757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c88565b005b610401600480360360208110156103d557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ef3565b60405180821515815260200191505060405180910390f35b6104856004803603606081101561042f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610f4c565b60405180821515815260200191505060405180910390f35b6104a561103a565b60405180821515815260200191505060405180910390f35b6104ff600480360360208110156104d357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611092565b005b6105436004803603602081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506110aa565b005b6105fc6004803603606081101561055b57600080fd5b810190808035906020019064010000000081111561057857600080fd5b82018360208201111561058a57600080fd5b803590602001918460208302840111640100000000831117156105ac57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611370565b005b6106406004803603602081101561061457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611508565b005b6106a46004803603604081101561065857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506115b5565b60405180821515815260200191505060405180910390f35b6106fe600480360360208110156106d257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506115c9565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6107326115db565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6107df6004803603604081101561077457600080fd5b81019080803590602001909291908035906020019064010000000081111561079b57600080fd5b8201836020820111156107ad57600080fd5b803590602001918460018302840111640100000000831117156107cf57600080fd5b9091929391929390505050611601565b60405180821515815260200191505060405180910390f35b6108396004803603602081101561080d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061165a565b005b61089d6004803603604081101561085157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611670565b005b610940600480360360608110156108b557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156108fc57600080fd5b82018360208201111561090e57600080fd5b8035906020019184600183028401116401000000008311171561093057600080fd5b9091929391929390505050611905565b60405180821515815260200191505060405180910390f35b610a196004803603608081101561096e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109d557600080fd5b8201836020820111156109e757600080fd5b80359060200191846001830284011164010000000083111715610a0957600080fd5b9091929391929390505050611971565b005b610a7d60048036036040811015610a3157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506119de565b005b6000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610b20576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806143e46026913960400191505060405180910390fd5b610b2b816001611d0d565b50565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff169050919050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000610c7d86610c7885858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610c6a89898080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050611e4e565b611fb790919063ffffffff16565b612087565b905095945050505050565b610c918361223e565b50610c9b83612685565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610d2f57610d29833283600067ffffffffffffffff81118015610cee57600080fd5b506040519080825280601f01601f191660200182016040528015610d215781602001600182028036833780820191505090505b506000612786565b50610e5d565b6060610dd38484600063a9059cbb60e01b3287604051602401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506000612786565b9050600081511115610e5b57808060200190516020811015610df457600080fd5b8101908080519060200190929190505050610e5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603581526020018061440a6035913960400191505060405180910390fd5b5b505b7feb5511fbef89123439f12682f0e9d0fc9696913b26eee55434c77d62c30aa7ca83328484604051808573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200194505050505060405180910390a1505050565b6000600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff169050919050565b60008060009050610f5d8585612087565b15610f6b576001905061102f565b61102c83600560008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518060400160405290816000820160009054906101000a900460ff16151515158152602001600182015481525050612c5490919063ffffffff16565b90505b809150509392505050565b60008073ffffffffffffffffffffffffffffffffffffffff16600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614905090565b61109b8161223e565b506110a7816001612ca3565b50565b6000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1661114b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806143e46026913960400191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff163273ffffffffffffffffffffffffffffffffffffffff1614156111ed576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f477561726465643a2063616e6e6f742072656d6f76652073656c66000000000081525060200191505060405180910390fd5b6000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166112ab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f477561726465643a20677561726469616e20646f65736e27742065786973740081525060200191505060405180910390fd5b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055507fee943cdb81826d5909c559c6b1ae6908fcaf2dbc16c4b730346736b486283e8b3282604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a150565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163273ffffffffffffffffffffffffffffffffffffffff1614611416576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f815260200180614318602f913960400191505060405180910390fd5b6000600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506114a2848480806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050612f05565b6114ac3083612f5d565b6114b581612f77565b7f908408e307fc569b417f6cbec5d5a06f44a0a505ac0479b47d421a4b2fd6a1e632604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150505050565b6000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166115a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806143e46026913960400191505060405180910390fd5b6115b281612fbb565b50565b60006115c18383612087565b905092915050565b60006115d4826131e2565b9050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006116518484848080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050613238565b90509392505050565b6116638161223e565b5061166d81612685565b50565b600061167b8361223e565b90508073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611702576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b8152602001806143b9602b913960400191505060405180910390fd5b6117b9600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518060400160405290816000820160009054906101000a900460ff161515151581526020016001820154815250506132a5565b61180e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806142b7602c913960400191505060405180910390fd5b43600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055507f1ce3cbbc43ee231e5b950332f2b0b9dd7d349291a3ee411ce5c5c7ed745661bb8383604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a1505050565b60006119678561196285858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505087611fb790919063ffffffff16565b612087565b9050949350505050565b61197a8561223e565b5061198485612685565b6119d685858585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506001612786565b505050505050565b6119e78261223e565b50600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415611a6e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602d815260200180614498602d913960400191505060405180910390fd5b611b25600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518060400160405290816000820160009054906101000a900460ff161515151581526020016001820154815250506132a5565b15611b7b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602d81526020018061446b602d913960400191505060405180910390fd5b6001600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160006101000a81548160ff0219169083151502179055506000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055507f27e282f7712c5b4617277759c834b86d163dfc1aad25c8c3c5926a1c9e5644688282604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a15050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611d93576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b81526020018061421e603b913960400191505060405180910390fd5b81600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508015611e4a577f70d9f5a076620216a6050e493f3ce69749de0b68fa4b839ba2518660ba8b9cf0600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5050565b6000611e5a82516132b8565b826040516020018082805190602001908083835b60208310611e915780518252602082019150602081019050602083039250611e6e565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405160208183030381529060405260405160200180807f19457468657265756d205369676e6564204d6573736167653a0a000000000000815250601a0183805190602001908083835b60208310611f235780518252602082019150602081019050602083039250611f00565b6001836020036101000a03801982511681845116808217855250505050505090500182805190602001908083835b60208310611f745780518252602082019150602081019050602083039250611f51565b6001836020036101000a03801982511681845116808217855250505050505090500192505050604051602081830303815290604052805190602001209050919050565b6000806000905060418351141561207d5760008060006020860151925060408601519150606086015160001a9050601b8160ff161015611ff857601b810190505b601b8160ff16148061200d5750601c8160ff16145b156120795760018782858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa15801561206c573d6000803e3d6000fd5b5050506020604051035193505b5050505b8091505092915050565b600080600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff16156121ac576000600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154149050612234565b6000801b600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154141561223357612202836131e2565b73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161490505b5b8091505092915050565b6000806122496133ff565b9050600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff16156123c0576000600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154146123bb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260388152602001806142596038913960400191505060405180910390fd5b61267c565b6000801b600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101541461245d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260388152602001806142596038913960400191505060405180910390fd5b600081604051602001808273ffffffffffffffffffffffffffffffffffffffff1660601b81526014019150506040516020818303038152906040528051906020012090506124aa81613410565b73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461252d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260388152602001806142596038913960400191505060405180910390fd5b80600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055506001600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160006101000a81548160ff0219169083151502179055507f27e282f7712c5b4617277759c834b86d163dfc1aad25c8c3c5926a1c9e5644688483604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a1505b80915050919050565b600560008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff1661278357612726600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101546001613606565b506001600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160006101000a81548160ff0219169083151502179055505b50565b6060600073ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16141561280e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806143476025913960400191505060405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161415612893576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c81526020018061443f602c913960400191505060405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161415612918576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806142916026913960400191505060405180910390fd5b60608673ffffffffffffffffffffffffffffffffffffffff16633f579f428787876040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156129aa57808201518184015260208101905061298f565b50505050905090810190601f1680156129d75780820380516001836020036101000a031916815260200191505b50945050505050600060405180830381600087803b1580156129f857600080fd5b505af1158015612a0c573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052506020811015612a3657600080fd5b8101908080516040519392919084640100000000821115612a5657600080fd5b83820191506020820185811115612a6c57600080fd5b8251866001820283011164010000000082111715612a8957600080fd5b8083526020830192505050908051906020019080838360005b83811015612abd578082015181840152602081019050612aa2565b50505050905090810190601f168015612aea5780820380516001836020036101000a031916815260200191505b5060405250505090508215612c47577f99eeae1fb8801e2e878e060ec5e5b3557b3c4f8106e35d88cb573f293dae4e248787878785604051808673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018060200180602001838103835285818151815260200191508051906020019080838360005b83811015612ba1578082015181840152602081019050612b86565b50505050905090810190601f168015612bce5780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015612c07578082015181840152602081019050612bec565b50505050905090810190601f168015612c345780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a15b8091505095945050505050565b60008060009050836000015115612c9957600084602001511415612c7b5760019050612c98565b6000831415612c8d5760019050612c97565b8284602001511190505b5b5b8091505092915050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16635c60da1b6040518163ffffffff1660e01b815260040160206040518083038186803b158015612d2257600080fd5b505afa158015612d36573d6000803e3d6000fd5b505050506040513d6020811015612d4c57600080fd5b810190808051906020019092919050505073ffffffffffffffffffffffffffffffffffffffff161415612dca576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b81526020018061438e602b913960400191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff1663d784d426600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b158015612e5557600080fd5b505af1158015612e69573d6000803e3d6000fd5b505050508015612f01577feec27cdb8bd6f55c2c537deb52ab094a2437e85ef5197e6f064f90bff3ff563f82600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a15b5050565b600081511415612f1d57612f1832612fbb565b612f5a565b60008151905060005b81811015612f5757612f4a838281518110612f3d57fe5b6020026020010151612fbb565b8080600101915050612f26565b50505b50565b612f6882600061375a565b612f73816000611d0d565b5050565b80600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141561305e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f477561726465643a2063616e6e6f74206164642030783020677561726469616e81525060200191505060405180910390fd5b6000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161561311d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f477561726465643a20677561726469616e20616c72656164792065786973747381525060200191505060405180910390fd5b60016000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055507fbc3292102fa77e083913064b282926717cdfaede4d35f553d66366c0a3da755a3282604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a150565b60008082604051602001808273ffffffffffffffffffffffffffffffffffffffff1660601b815260140191505060405160208183030381529060405280519060200120905061323081613410565b915050919050565b60008061324e8385611fb790919063ffffffff16565b90506000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1691505092915050565b60006132b18243612c54565b9050919050565b60606000821415613300576040518060400160405280600181526020017f300000000000000000000000000000000000000000000000000000000000000081525090506133fa565b600082905060005b6000821461332a578080600101915050600a828161332257fe5b049150613308565b60608167ffffffffffffffff8111801561334357600080fd5b506040519080825280601f01601f1916602001820160405280156133765781602001600182028036833780820191505090505b50905060006001830390508593505b600084146133f257600a848161339757fe5b0660300160f81b828280600190039350815181106133b157fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600a84816133ea57fe5b049350613385565b819450505050505b919050565b600061340b601461389b565b905090565b600060606040518060200161342490613a8c565b6020820181038252601f19601f82011660405250600060a01b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600060a01b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040516020018086805190602001908083835b602083106134be578051825260208201915060208101905060208303925061349b565b6001836020036101000a0380198251168184511680821785525050505050509050018573ffffffffffffffffffffffffffffffffffffffff19168152600c018473ffffffffffffffffffffffffffffffffffffffff1660601b81526014018373ffffffffffffffffffffffffffffffffffffffff19168152600c018273ffffffffffffffffffffffffffffffffffffffff1660601b8152601401955050505050506040516020818303038152906040529050600060ff60f81b3085848051906020012060405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018473ffffffffffffffffffffffffffffffffffffffff1660601b81526014018381526020018281526020019450505050506040516020818303038152906040528051906020012090508060001c92505050919050565b60008083600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660405161365c90613a8c565b808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff168152602001925050508190604051809103906000f59050801580156136b9573d6000803e3d6000fd5b5090508215613750577f2682a218602b9036c9869f006c5405ee4a1a65267653eb7e286123e5e1afde4c81600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a15b8091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156137e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260358152602001806142e36035913960400191505060405180910390fd5b81600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508015613897577f02e3c47057b8dc27a0929d3c394c314c73aa002d46939c31c9f71d1e77e86c10600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15b5050565b600080600090506138aa613930565b1561392357600083600036905003905061391b600036839060148501926138d393929190613a99565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506139f3565b915050613927565b3390505b80915050919050565b600080600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156139ec57602c600036905010156139e7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061436c6022913960400191505060405180910390fd5b600190505b8091505090565b6000806014835114613a6d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f42797465734c69623a20696e76616c69642064617461206c656e67746800000081525060200191505060405180910390fd5b6c01000000000000000000000000602084015104905080915050919050565b61075180613acd83390190565b60008085851115613aa957600080fd5b83861115613ab657600080fd5b600185028301915084860390509450949250505056fe608060405234801561001057600080fd5b506040516107513803806107518339818101604052604081101561003357600080fd5b810190808051906020019092919080519060200190929190505050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555081600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050610630806101216000396000f3fe60806040526004361061004e5760003560e01c80633f579f42146100be5780635c60da1b146101e75780637b10399914610228578063d784d42614610269578063f77c4791146102ba57610055565b3661005557005b34801561006157600080fd5b50600080369050146100bc576000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050368060008037600080826000855af43d806000803e81600081146100b757816000f35b816000fd5b005b3480156100ca57600080fd5b5061016c600480360360608110156100e157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561012857600080fd5b82018360208201111561013a57600080fd5b8035906020019184600183028401116401000000008311171561015c57600080fd5b90919293919293905050506102fb565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101ac578082015181840152602081019050610191565b50505050905090810190601f1680156101d95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156101f357600080fd5b506101fc61049f565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561023457600080fd5b5061023d6104c5565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561027557600080fd5b506102b86004803603602081101561028c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506104eb565b005b3480156102c657600080fd5b506102cf6105d3565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b606060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146103a1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806105f8602c913960400191505060405180910390fd5b606060008673ffffffffffffffffffffffffffffffffffffffff1686868660405180838380828437808301925050509250505060006040518083038185875af1925050503d8060008114610411576040519150601f19603f3d011682016040523d82523d6000602084013e610416565b606091505b50809350819250505080610492576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f4163636f756e743a207472616e73616374696f6e20726576657274656400000081525060200191505060405180910390fd5b8192505050949350505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461058f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806105f8602c913960400191505060405180910390fd5b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fe436f6e74726f6c6c65643a206d73672e73656e646572206973206e6f742074686520636f6e74726f6c6c6572a164736f6c634300060c000a4163636f756e74436f6e74726f6c6c65723a2063616e6e6f7420736574206163636f756e7420496d706c656d656e746174696f6e20746f20307830506572736f6e616c4163636f756e7452656769737472793a2073656e646572206973206e6f7420746865206163636f756e74206f776e65724163636f756e74436f6e74726f6c6c65723a2063616e6e6f742073656e6420746f2073656c66506572736f6e616c4163636f756e7452656769737472793a206f776e657220646f65736e27742065786973744163636f756e74436f6e74726f6c6c65723a2063616e6e6f7420736574206163636f756e7420726567697374727920746f20307830496e697469616c697a61626c653a2074782e6f726967696e206973206e6f742074686520696e697469616c697a65724163636f756e74436f6e74726f6c6c65723a2063616e6e6f742073656e6420746f2030783047617465776179526563697069656e743a20696e76616c6964206d73672e646174614163636f756e74436f6e74726f6c6c65723a206163636f756e7420616c7265616479207570677261646564506572736f6e616c4163636f756e7452656769737472793a2063616e6e6f742072656d6f76652073656c66477561726465643a2074782e6f726967696e206973206e6f742074686520677561726469616e506572736f6e616c4163636f756e7452656769737472793a204552433230546f6b656e207472616e736665722072657665727465644163636f756e74436f6e74726f6c6c65723a2063616e6e6f742073656e6420746f20636f6e74726f6c6c6572506572736f6e616c4163636f756e7452656769737472793a206f776e657220616c726561647920657869737473506572736f6e616c4163636f756e7452656769737472793a2063616e6e6f742061646420307830206f776e6572a164736f6c634300060c000a
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 26 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
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.