UNPKG

@etherspot/contracts

Version:

Etherspot Solidity contracts

37 lines 72.8 kB
{ "language": "Solidity", "sources": { "src/gateway/GatewayV2.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\npragma experimental ABIEncoderV2;\n\nimport \"../common/access/Guarded.sol\";\nimport \"../common/libs/ECDSALib.sol\";\nimport \"../common/libs/SafeMathLib.sol\";\nimport \"../common/lifecycle/Initializable.sol\";\nimport \"../common/signature/SignatureValidator.sol\";\nimport \"../external/ExternalAccountRegistry.sol\";\nimport \"../personal/PersonalAccountRegistry.sol\";\n\n/**\n * @title Gateway V2 with guarded batching functions\n *\n * @notice GSN replacement\n *\n * @author Utkir Sobirov <sobirovutkir@gmail.io>\n */\ncontract GatewayV2 is Initializable, SignatureValidator, Guarded {\n using ECDSALib for bytes32;\n using SafeMathLib for uint256;\n\n struct DelegatedBatch {\n address account;\n uint256 nonce;\n address[] to;\n bytes[] data;\n }\n\n struct DelegatedBatchWithGasPrice {\n address account;\n uint256 nonce;\n address[] to;\n bytes[] data;\n uint256 gasPrice;\n }\n\n bytes32 private constant HASH_PREFIX_DELEGATED_BATCH = keccak256(\n \"DelegatedBatch(address account,uint256 nonce,address[] to,bytes[] data)\"\n );\n\n bytes32 private constant HASH_PREFIX_DELEGATED_BATCH_WITH_GAS_PRICE = keccak256(\n \"DelegatedBatchWithGasPrice(address account,uint256 nonce,address[] to,bytes[] data,uint256 gasPrice)\"\n );\n\n ExternalAccountRegistry public externalAccountRegistry;\n PersonalAccountRegistry public personalAccountRegistry;\n\n mapping(address => uint256) private accountNonce;\n\n // events\n\n /**\n * @dev Emitted when the single batch is delegated\n * @param sender sender address\n * @param batch batch\n * @param succeeded if succeeded\n */\n event BatchDelegated(\n address sender,\n bytes batch,\n bool succeeded\n );\n\n /**\n * @dev Public constructor\n */\n constructor() public Initializable() SignatureValidator() {}\n\n // external functions\n\n /**\n * @notice Initializes `Gateway` contract\n * @param externalAccountRegistry_ `ExternalAccountRegistry` contract address\n * @param personalAccountRegistry_ `PersonalAccountRegistry` contract address\n */\n function initialize(\n ExternalAccountRegistry externalAccountRegistry_,\n PersonalAccountRegistry personalAccountRegistry_\n )\n external\n onlyInitializer\n {\n externalAccountRegistry = externalAccountRegistry_;\n personalAccountRegistry = personalAccountRegistry_;\n\n address[] memory guardians;\n _initializeGuarded(guardians); // adds tx.origin to guardians list\n }\n\n // public functions\n\n /**\n * @notice Sends batch\n * @dev `GatewayRecipient` context api:\n * `_getContextAccount` will return `msg.sender`\n * `_getContextSender` will return `msg.sender`\n *\n * @param to array of batch recipients contracts\n * @param data array of batch data\n */\n function sendBatch(\n address[] memory to,\n bytes[] memory data\n )\n public\n {\n _sendBatch(\n msg.sender,\n msg.sender,\n to,\n data\n );\n }\n\n /**\n * @notice Sends guarded batch\n * @dev `GatewayRecipient` context api:\n * `_getContextAccount` will return `msg.sender`\n * `_getContextSender` will return `msg.sender`\n *\n * @param to array of batch recipients contracts\n * @param data array of batch data\n */\n function sendBatchGuarded(\n address[] memory to,\n bytes[] memory data\n )\n public\n onlyGuardian\n {\n sendBatch(to, data);\n }\n\n /**\n * @notice Sends batch from the account\n * @dev `GatewayRecipient` context api:\n * `_getContextAccount` will return `account` arg\n * `_getContextSender` will return `msg.sender`\n *\n * @param account account address\n * @param to array of batch recipients contracts\n * @param data array of batch data\n */\n function sendBatchFromAccount(\n address account,\n address[] memory to,\n bytes[] memory data\n )\n public\n {\n _sendBatch(\n account,\n msg.sender,\n to,\n data\n );\n }\n\n /**\n * @notice Sends guarded batch from the account\n * @dev `GatewayRecipient` context api:\n * `_getContextAccount` will return `account` arg\n * `_getContextSender` will return `msg.sender`\n *\n * @param account account address\n * @param to array of batch recipients contracts\n * @param data array of batch data\n */\n function sendBatchFromAccountGuarded(\n address account,\n address[] memory to,\n bytes[] memory data\n )\n public\n onlyGuardian\n {\n sendBatchFromAccount(account, to, data);\n }\n\n /**\n * @notice Delegates batch from the account\n * @dev Use `hashDelegatedBatch` to create sender message payload.\n *\n * `GatewayRecipient` context api:\n * `_getContextAccount` will return `account` arg\n * `_getContextSender` will return recovered address from `senderSignature` arg\n *\n * @param account account address\n * @param nonce next account nonce\n * @param to array of batch recipients contracts\n * @param data array of batch data\n * @param senderSignature sender signature\n */\n function delegateBatch(\n address account,\n uint256 nonce,\n address[] memory to,\n bytes[] memory data,\n bytes memory senderSignature\n )\n public\n {\n require(\n nonce > accountNonce[account],\n \"Gateway: nonce is lower than current account nonce\"\n );\n\n address sender = _hashDelegatedBatch(\n account,\n nonce,\n to,\n data\n ).recoverAddress(senderSignature);\n\n accountNonce[account] = nonce;\n\n _sendBatch(\n account,\n sender,\n to,\n data\n );\n }\n\n /**\n * @notice Delegates guarded batch from the account\n * @dev Use `hashDelegatedBatch` to create sender message payload.\n *\n * `GatewayRecipient` context api:\n * `_getContextAccount` will return `account` arg\n * `_getContextSender` will return recovered address from `senderSignature` arg\n *\n * @param account account address\n * @param nonce next account nonce\n * @param to array of batch recipients contracts\n * @param data array of batch data\n * @param senderSignature sender signature\n */\n function delegateBatchGuarded(\n address account,\n uint256 nonce,\n address[] memory to,\n bytes[] memory data,\n bytes memory senderSignature\n )\n public\n onlyGuardian\n {\n delegateBatch(account, nonce, to, data, senderSignature);\n }\n\n /**\n * @notice Delegates batch from the account (with gas price)\n *\n * @dev Use `hashDelegatedBatchWithGasPrice` to create sender message payload (tx.gasprice as gasPrice)\n *\n * `GatewayRecipient` context api:\n * `_getContextAccount` will return `account` arg\n * `_getContextSender` will return recovered address from `senderSignature` arg\n *\n * @param account account address\n * @param nonce next account nonce\n * @param to array of batch recipients contracts\n * @param data array of batch data\n * @param senderSignature sender signature\n */\n function delegateBatchWithGasPrice(\n address account,\n uint256 nonce,\n address[] memory to,\n bytes[] memory data,\n bytes memory senderSignature\n )\n public\n {\n require(\n nonce > accountNonce[account],\n \"Gateway: nonce is lower than current account nonce\"\n );\n\n address sender = _hashDelegatedBatchWithGasPrice(\n account,\n nonce,\n to,\n data,\n tx.gasprice\n ).recoverAddress(senderSignature);\n\n accountNonce[account] = nonce;\n\n _sendBatch(\n account,\n sender,\n to,\n data\n );\n }\n\n /**\n * @notice Delegates guarded batch from the account (with gas price)\n *\n * @dev Use `hashDelegatedBatchWithGasPrice` to create sender message payload (tx.gasprice as gasPrice)\n *\n * `GatewayRecipient` context api:\n * `_getContextAccount` will return `account` arg\n * `_getContextSender` will return recovered address from `senderSignature` arg\n *\n * @param account account address\n * @param nonce next account nonce\n * @param to array of batch recipients contracts\n * @param data array of batch data\n * @param senderSignature sender signature\n */\n function delegateBatchWithGasPriceGuarded(\n address account,\n uint256 nonce,\n address[] memory to,\n bytes[] memory data,\n bytes memory senderSignature\n )\n public\n onlyGuardian\n {\n delegateBatchWithGasPrice(account, nonce, to, data, senderSignature);\n }\n\n /**\n * @notice Delegates multiple batches\n * @dev It will revert when all batches fail\n * @param batches array of batches\n * @param revertOnFailure reverts on any error\n */\n function delegateBatches(\n bytes[] memory batches,\n bool revertOnFailure\n )\n public\n {\n require(\n batches.length > 0,\n \"Gateway: cannot delegate empty batches\"\n );\n\n bool anySucceeded;\n\n for (uint256 i = 0; i < batches.length; i++) {\n // solhint-disable-next-line avoid-low-level-calls\n (bool succeeded,) = address(this).call(batches[i]);\n\n if (revertOnFailure) {\n require(\n succeeded,\n \"Gateway: batch reverted\"\n );\n } else if (succeeded && !anySucceeded) {\n anySucceeded = true;\n }\n\n emit BatchDelegated(\n msg.sender,\n batches[i],\n succeeded\n );\n }\n\n if (!anySucceeded) {\n revert(\"Gateway: all batches reverted\");\n }\n }\n\n /**\n * @notice Delegates multiple guarded batches\n * @dev It will revert when all batches fail\n * @param batches array of batches\n * @param revertOnFailure reverts on any error\n */\n function delegateBatchesGuarded(\n bytes[] memory batches,\n bool revertOnFailure\n )\n public\n onlyGuardian\n {\n delegateBatches(batches, revertOnFailure);\n }\n\n // public functions (views)\n\n /**\n * @notice Hashes `DelegatedBatch` message payload\n * @param delegatedBatch struct\n * @return hash\n */\n function hashDelegatedBatch(\n DelegatedBatch memory delegatedBatch\n )\n public\n view\n returns (bytes32)\n {\n return _hashDelegatedBatch(\n delegatedBatch.account,\n delegatedBatch.nonce,\n delegatedBatch.to,\n delegatedBatch.data\n );\n }\n\n /**\n * @notice Hashes `DelegatedBatchWithGasPrice` message payload\n * @param delegatedBatch struct\n * @return hash\n */\n function hashDelegatedBatchWithGasPrice(\n DelegatedBatchWithGasPrice memory delegatedBatch\n )\n public\n view\n returns (bytes32)\n {\n return _hashDelegatedBatchWithGasPrice(\n delegatedBatch.account,\n delegatedBatch.nonce,\n delegatedBatch.to,\n delegatedBatch.data,\n delegatedBatch.gasPrice\n );\n }\n\n // external functions (views)\n\n /**\n * @notice Gets next account nonce\n * @param account account address\n * @return next nonce\n */\n function getAccountNextNonce(\n address account\n )\n external\n view\n returns (uint256)\n {\n return accountNonce[account].add(1);\n }\n\n // private functions\n\n function _sendBatch(\n address account,\n address sender,\n address[] memory to,\n bytes[] memory data\n )\n private\n {\n require(\n account != address(0),\n \"Gateway: cannot send from 0x0 account\"\n );\n require(\n to.length > 0,\n \"Gateway: cannot send empty batch\"\n );\n require(\n data.length == to.length,\n \"Gateway: invalid batch\"\n );\n\n if (account != sender) {\n require(\n personalAccountRegistry.verifyAccountOwner(account, sender) ||\n externalAccountRegistry.verifyAccountOwner(account, sender),\n \"Gateway: sender is not the account owner\"\n );\n }\n\n bool succeeded;\n\n for (uint256 i = 0; i < data.length; i++) {\n require(\n to[i] != address(0),\n \"Gateway: cannot send to 0x0\"\n );\n\n // solhint-disable-next-line avoid-low-level-calls\n (succeeded,) = to[i].call(abi.encodePacked(data[i], account, sender));\n\n require(\n succeeded,\n \"Gateway: batch transaction reverted\"\n );\n }\n }\n\n // private functions (views)\n\n function _hashDelegatedBatch(\n address account,\n uint256 nonce,\n address[] memory to,\n bytes[] memory data\n )\n private\n view\n returns (bytes32)\n {\n return _hashMessagePayload(HASH_PREFIX_DELEGATED_BATCH, abi.encodePacked(\n account,\n nonce,\n to,\n _concatBytes(data)\n ));\n }\n\n function _hashDelegatedBatchWithGasPrice(\n address account,\n uint256 nonce,\n address[] memory to,\n bytes[] memory data,\n uint256 gasPrice\n )\n private\n view\n returns (bytes32)\n {\n return _hashMessagePayload(HASH_PREFIX_DELEGATED_BATCH_WITH_GAS_PRICE, abi.encodePacked(\n account,\n nonce,\n to,\n _concatBytes(data),\n gasPrice\n ));\n }\n\n// private functions (pure)\n\n function _concatBytes(bytes[] memory data)\n private\n pure\n returns (bytes memory)\n {\n bytes memory result;\n uint dataLen = data.length;\n\n for (uint i = 0 ; i < dataLen ; i++) {\n result = abi.encodePacked(result, data[i]);\n }\n\n return result;\n }\n}\n" }, "src/common/access/Guarded.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\nimport \"../libs/ECDSALib.sol\";\n\n\n/**\n * @title Guarded\n *\n * @dev Contract module which provides a guardian-type control mechanism.\n * It allows key accounts to have guardians and restricts specific methods to be accessible by guardians only.\n *\n * Each guardian account can remove other guardians\n *\n * Use `_initializeGuarded` to initialize the contract\n *\n * @author Stanisław Głogowski <stan@pillarproject.io>\n */\ncontract Guarded {\n using ECDSALib for bytes32;\n\n mapping(address => bool) private guardians;\n\n // events\n\n /**\n * @dev Emitted when a new guardian is added\n * @param sender sender address\n * @param guardian guardian address\n */\n event GuardianAdded(\n address sender,\n address guardian\n );\n\n /**\n * @dev Emitted when the existing guardian is removed\n * @param sender sender address\n * @param guardian guardian address\n */\n event GuardianRemoved(\n address sender,\n address guardian\n );\n\n // modifiers\n\n /**\n * @dev Throws if tx.origin is not a guardian account\n */\n modifier onlyGuardian() {\n require(\n // solhint-disable-next-line avoid-tx-origin\n guardians[tx.origin],\n \"Guarded: tx.origin is not the guardian\"\n );\n\n _;\n }\n\n /**\n * @dev Internal constructor\n */\n constructor() internal {}\n\n // external functions\n\n /**\n * @notice Adds a new guardian\n * @param guardian guardian address\n */\n function addGuardian(\n address guardian\n )\n external\n onlyGuardian\n {\n _addGuardian(guardian);\n }\n\n /**\n * @notice Removes the existing guardian\n * @param guardian guardian address\n */\n function removeGuardian(\n address guardian\n )\n external\n onlyGuardian\n {\n require(\n // solhint-disable-next-line avoid-tx-origin\n tx.origin != guardian,\n \"Guarded: cannot remove self\"\n );\n\n require(\n guardians[guardian],\n \"Guarded: guardian doesn't exist\"\n );\n\n guardians[guardian] = false;\n\n emit GuardianRemoved(\n // solhint-disable-next-line avoid-tx-origin\n tx.origin,\n guardian\n );\n }\n\n // external functions (views)\n\n /**\n * @notice Check if guardian exists\n * @param guardian guardian address\n * @return true when guardian exists\n */\n function isGuardian(\n address guardian\n )\n external\n view\n returns (bool)\n {\n return guardians[guardian];\n }\n\n /**\n * @notice Verifies guardian signature\n * @param messageHash message hash\n * @param signature signature\n * @return true on correct guardian signature\n */\n function verifyGuardianSignature(\n bytes32 messageHash,\n bytes calldata signature\n )\n external\n view\n returns (bool)\n {\n return _verifyGuardianSignature(\n messageHash,\n signature\n );\n }\n\n // internal functions\n\n /**\n * @notice Initializes `Guarded` contract\n * @dev If `guardians_` array is empty `tx.origin` is added as guardian account\n * @param guardians_ array of guardians addresses\n */\n function _initializeGuarded(\n address[] memory guardians_\n )\n internal\n {\n if (guardians_.length == 0) {\n // solhint-disable-next-line avoid-tx-origin\n _addGuardian(tx.origin);\n } else {\n uint guardiansLen = guardians_.length;\n for (uint i = 0; i < guardiansLen; i++) {\n _addGuardian(guardians_[i]);\n }\n }\n }\n\n\n // internal functions (views)\n\n function _verifyGuardianSignature(\n bytes32 messageHash,\n bytes memory signature\n )\n internal\n view\n returns (bool)\n {\n address guardian = messageHash.recoverAddress(signature);\n\n return guardians[guardian];\n }\n\n // private functions\n\n function _addGuardian(\n address guardian\n )\n private\n {\n require(\n guardian != address(0),\n \"Guarded: cannot add 0x0 guardian\"\n );\n\n require(\n !guardians[guardian],\n \"Guarded: guardian already exists\"\n );\n\n guardians[guardian] = true;\n\n emit GuardianAdded(\n // solhint-disable-next-line avoid-tx-origin\n tx.origin,\n guardian\n );\n }\n}\n" }, "src/common/libs/ECDSALib.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\n/**\n * @title ECDSA library\n *\n * @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/cryptography/ECDSA.sol#L26\n */\nlibrary ECDSALib {\n function recoverAddress(\n bytes32 messageHash,\n bytes memory signature\n )\n internal\n pure\n returns (address)\n {\n address result = address(0);\n\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n\n if (v < 27) {\n v += 27;\n }\n\n if (v == 27 || v == 28) {\n result = ecrecover(messageHash, v, r, s);\n }\n }\n\n return result;\n }\n\n function toEthereumSignedMessageHash(\n bytes32 messageHash\n )\n internal\n pure\n returns (bytes32)\n {\n return keccak256(abi.encodePacked(\n \"\\x19Ethereum Signed Message:\\n32\",\n messageHash\n ));\n }\n}\n" }, "src/common/libs/SafeMathLib.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\n/**\n * @title Safe math library\n *\n * @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/math/SafeMath.sol\n */\nlibrary SafeMathLib {\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 c = a + b;\n\n require(c >= a, \"SafeMathLib: addition overflow\");\n\n return c;\n }\n\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n return sub(a, b, \"SafeMathLib: subtraction overflow\");\n }\n\n function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b <= a, errorMessage);\n\n return a - b;\n }\n\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n }\n\n uint256 c = a * b;\n\n require(c / a == b, \"SafeMathLib: multiplication overflow\");\n\n return c;\n }\n\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\n return div(a, b, \"SafeMathLib: division by zero\");\n }\n\n function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b > 0, errorMessage);\n\n return a / b;\n }\n\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n return mod(a, b, \"SafeMathLib: modulo by zero\");\n }\n\n function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {\n require(b != 0, errorMessage);\n\n return a % b;\n }\n}\n" }, "src/common/lifecycle/Initializable.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\n/**\n * @title Initializable\n *\n * @dev Contract module which provides access control mechanism, where\n * there is the initializer account that can be granted exclusive access to\n * specific functions.\n *\n * The initializer account will be tx.origin during contract deployment and will be removed on first use.\n * Use `onlyInitializer` modifier on contract initialize process.\n *\n * @author Stanisław Głogowski <stan@pillarproject.io>\n */\ncontract Initializable {\n address private initializer;\n\n // events\n\n /**\n * @dev Emitted after `onlyInitializer`\n * @param initializer initializer address\n */\n event Initialized(\n address initializer\n );\n\n // modifiers\n\n /**\n * @dev Throws if tx.origin is not the initializer\n */\n modifier onlyInitializer() {\n require(\n // solhint-disable-next-line avoid-tx-origin\n tx.origin == initializer,\n \"Initializable: tx.origin is not the initializer\"\n );\n\n /// @dev removes initializer\n initializer = address(0);\n\n _;\n\n emit Initialized(\n // solhint-disable-next-line avoid-tx-origin\n tx.origin\n );\n }\n\n /**\n * @dev Internal constructor\n */\n constructor()\n internal\n {\n // solhint-disable-next-line avoid-tx-origin\n initializer = tx.origin;\n }\n\n // external functions (views)\n\n /**\n * @notice Check if contract is initialized\n * @return true when contract is initialized\n */\n function isInitialized()\n external\n view\n returns (bool)\n {\n return initializer == address(0);\n }\n}\n" }, "src/common/signature/SignatureValidator.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\nimport \"../libs/ECDSALib.sol\";\n\n/**\n * @title Signature validator\n *\n * @author Stanisław Głogowski <stan@pillarproject.io>\n */\ncontract SignatureValidator {\n using ECDSALib for bytes32;\n\n uint256 public chainId;\n\n /**\n * @dev internal constructor\n */\n constructor() internal {\n uint256 chainId_;\n\n // solhint-disable-next-line no-inline-assembly\n assembly {\n chainId_ := chainid()\n }\n\n chainId = chainId_;\n }\n\n // internal functions\n\n function _hashMessagePayload(\n bytes32 messagePrefix,\n bytes memory messagePayload\n )\n internal\n view\n returns (bytes32)\n {\n return keccak256(abi.encodePacked(\n chainId,\n address(this),\n messagePrefix,\n messagePayload\n )).toEthereumSignedMessageHash();\n }\n}\n" }, "src/external/ExternalAccountRegistry.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\nimport \"../common/libs/BlockLib.sol\";\n\n\n/**\n * @title External account registry\n *\n * @notice Global registry for keys and external (outside of the platform) contract based wallets\n *\n * @dev An account can call the registry to add (`addAccountOwner`) or remove (`removeAccountOwner`) its own owners.\n * When the owner has been added, information about that fact will live in the registry forever.\n * Removing an owner only affects the future blocks (until the owner is re-added).\n *\n * Given the fact, there is no way to sign the data using a contract based wallet,\n * we created a registry to store signed by the key wallet proofs.\n * ERC-1271 allows removing a signer after the signature was created. Thus store the signature for the later use\n * doesn't guarantee the signer is still has access to that smart account.\n * Because of that, the ERC1271's `isValidSignature()` cannot be used in e.g. `PaymentRegistry`.*\n *\n * An account can call the registry to add (`addAccountProof`) or remove (`removeAccountProof`) proof hash.\n * When the proof has been added, information about that fact will live in the registry forever.\n * Removing a proof only affects the future blocks (until the proof is re-added).\n *\n * @author Stanisław Głogowski <stan@pillarproject.io>\n */\ncontract ExternalAccountRegistry {\n using BlockLib for BlockLib.BlockRelated;\n\n struct Account {\n mapping(address => BlockLib.BlockRelated) owners;\n mapping(bytes32 => BlockLib.BlockRelated) proofs;\n }\n\n mapping(address => Account) private accounts;\n\n // events\n\n /**\n * @dev Emitted when the new owner is added\n * @param account account address\n * @param owner owner address\n */\n event AccountOwnerAdded(\n address account,\n address owner\n );\n\n /**\n * @dev Emitted when the existing owner is removed\n * @param account account address\n * @param owner owner address\n */\n event AccountOwnerRemoved(\n address account,\n address owner\n );\n\n /**\n * @dev Emitted when the new proof is added\n * @param account account address\n * @param hash proof hash\n */\n event AccountProofAdded(\n address account,\n bytes32 hash\n );\n\n /**\n * @dev Emitted when the existing proof is removed\n * @param account account address\n * @param hash proof hash\n */\n event AccountProofRemoved(\n address account,\n bytes32 hash\n );\n\n // external functions\n\n /**\n * @notice Adds a new account owner\n * @param owner owner address\n */\n function addAccountOwner(\n address owner\n )\n external\n {\n require(\n owner != address(0),\n \"ExternalAccountRegistry: cannot add 0x0 owner\"\n );\n\n require(\n !accounts[msg.sender].owners[owner].verifyAtCurrentBlock(),\n \"ExternalAccountRegistry: owner already exists\"\n );\n\n accounts[msg.sender].owners[owner].added = true;\n accounts[msg.sender].owners[owner].removedAtBlockNumber = 0;\n\n emit AccountOwnerAdded(\n msg.sender,\n owner\n );\n }\n\n /**\n * @notice Removes existing account owner\n * @param owner owner address\n */\n function removeAccountOwner(\n address owner\n )\n external\n {\n require(\n accounts[msg.sender].owners[owner].verifyAtCurrentBlock(),\n \"ExternalAccountRegistry: owner doesn't exist\"\n );\n\n accounts[msg.sender].owners[owner].removedAtBlockNumber = block.number;\n\n emit AccountOwnerRemoved(\n msg.sender,\n owner\n );\n }\n\n /**\n * @notice Adds a new account proof\n * @param hash proof hash\n */\n function addAccountProof(\n bytes32 hash\n )\n external\n {\n require(\n !accounts[msg.sender].proofs[hash].verifyAtCurrentBlock(),\n \"ExternalAccountRegistry: proof already exists\"\n );\n\n accounts[msg.sender].proofs[hash].added = true;\n accounts[msg.sender].proofs[hash].removedAtBlockNumber = 0;\n\n emit AccountProofAdded(\n msg.sender,\n hash\n );\n }\n\n /**\n * @notice Removes existing account proof\n * @param hash proof hash\n */\n function removeAccountProof(\n bytes32 hash\n )\n external\n {\n require(\n accounts[msg.sender].proofs[hash].verifyAtCurrentBlock(),\n \"ExternalAccountRegistry: proof doesn't exist\"\n );\n\n accounts[msg.sender].proofs[hash].removedAtBlockNumber = block.number;\n\n emit AccountProofRemoved(\n msg.sender,\n hash\n );\n }\n\n // external functions (views)\n\n /**\n * @notice Verifies the owner of the account at current block\n * @param account account address\n * @param owner owner address\n * @return true on correct account owner\n */\n function verifyAccountOwner(\n address account,\n address owner\n )\n external\n view\n returns (bool)\n {\n return accounts[account].owners[owner].verifyAtCurrentBlock();\n }\n\n /**\n * @notice Verifies the owner of the account at specific block\n * @param account account address\n * @param owner owner address\n * @param blockNumber block number to verify\n * @return true on correct account owner\n */\n function verifyAccountOwnerAtBlock(\n address account,\n address owner,\n uint256 blockNumber\n )\n external\n view\n returns (bool)\n {\n return accounts[account].owners[owner].verifyAtBlock(blockNumber);\n }\n\n /**\n * @notice Verifies the proof of the account at current block\n * @param account account address\n * @param hash proof hash\n * @return true on correct account proof\n */\n function verifyAccountProof(\n address account,\n bytes32 hash\n )\n external\n view\n returns (bool)\n {\n return accounts[account].proofs[hash].verifyAtCurrentBlock();\n }\n\n /**\n * @notice Verifies the proof of the account at specific block\n * @param account account address\n * @param hash proof hash\n * @param blockNumber block number to verify\n * @return true on correct account proof\n */\n function verifyAccountProofAtBlock(\n address account,\n bytes32 hash,\n uint256 blockNumber\n )\n external\n view\n returns (bool)\n {\n return accounts[account].proofs[hash].verifyAtBlock(blockNumber);\n }\n}\n" }, "src/personal/PersonalAccountRegistry.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\nimport \"../common/access/Guarded.sol\";\nimport \"../common/account/AccountController.sol\";\nimport \"../common/account/AccountRegistry.sol\";\nimport \"../common/libs/BlockLib.sol\";\nimport \"../common/libs/ECDSALib.sol\";\nimport \"../common/libs/ECDSAExtendedLib.sol\";\nimport \"../common/libs/SafeMathLib.sol\";\nimport \"../common/lifecycle/Initializable.sol\";\nimport \"../common/token/ERC20Token.sol\";\nimport \"../gateway/GatewayRecipient.sol\";\n\n\n/**\n * @title Personal account registry\n *\n * @notice A registry for personal (controlled by owners) accounts\n *\n * @author Stanisław Głogowski <stan@pillarproject.io>\n */\ncontract PersonalAccountRegistry is Guarded, AccountController, AccountRegistry, Initializable, GatewayRecipient {\n using BlockLib for BlockLib.BlockRelated;\n using SafeMathLib for uint256;\n using ECDSALib for bytes32;\n using ECDSAExtendedLib for bytes;\n\n struct Account {\n bool deployed;\n bytes32 salt;\n mapping(address => BlockLib.BlockRelated) owners;\n }\n\n mapping(address => Account) private accounts;\n\n // events\n\n /**\n * @dev Emitted when the new owner is added\n * @param account account address\n * @param owner owner address\n */\n event AccountOwnerAdded(\n address account,\n address owner\n );\n\n /**\n * @dev Emitted when the existing owner is removed\n * @param account account address\n * @param owner owner address\n */\n event AccountOwnerRemoved(\n address account,\n address owner\n );\n\n /**\n * @dev Emitted when the call is refunded\n * @param account account address\n * @param beneficiary beneficiary address\n * @param token token address\n * @param value value\n */\n event AccountCallRefunded(\n address account,\n address beneficiary,\n address token,\n uint256 value\n );\n\n /**\n * @dev Public constructor\n */\n constructor() public Initializable() {}\n\n // external functions\n\n /**\n * @notice Initializes `PersonalAccountRegistry` contract\n * @param guardians_ array of guardians addresses\n * @param accountImplementation_ account implementation address\n * @param gateway_ `Gateway` contract address\n */\n function initialize(\n address[] calldata guardians_,\n address accountImplementation_,\n address gateway_\n )\n external\n onlyInitializer\n {\n // Guarded\n _initializeGuarded(guardians_);\n\n // AccountController\n _initializeAccountController(address(this), accountImplementation_);\n\n // GatewayRecipient\n _initializeGatewayRecipient(gateway_);\n }\n\n /**\n * @notice Upgrades `PersonalAccountRegistry` contract\n * @param accountImplementation_ account implementation address\n */\n function upgrade(\n address accountImplementation_\n )\n external\n onlyGuardian\n {\n _setAccountImplementation(accountImplementation_, true);\n }\n\n /**\n * @notice Deploys account\n * @param account account address\n */\n function deployAccount(\n address account\n )\n external\n {\n _verifySender(account);\n _deployAccount(account);\n }\n\n /**\n * @notice Upgrades account\n * @param account account address\n */\n function upgradeAccount(\n address account\n )\n external\n {\n _verifySender(account);\n _upgradeAccount(account, true);\n }\n\n /**\n * @notice Adds a new account owner\n * @param account account address\n * @param owner owner address\n */\n function addAccountOwner(\n address account,\n address owner\n )\n external\n {\n _verifySender(account);\n\n require(\n owner != address(0),\n \"PersonalAccountRegistry: cannot add 0x0 owner\"\n );\n\n require(\n !accounts[account].owners[owner].verifyAtCurrentBlock(),\n \"PersonalAccountRegistry: owner already exists\"\n );\n\n accounts[account].owners[owner].added = true;\n accounts[account].owners[owner].removedAtBlockNumber = 0;\n\n emit AccountOwnerAdded(\n account,\n owner\n );\n }\n\n /**\n * @notice Removes the existing account owner\n * @param account account address\n * @param owner owner address\n */\n function removeAccountOwner(\n address account,\n address owner\n )\n external\n {\n address sender = _verifySender(account);\n\n require(\n owner != sender,\n \"PersonalAccountRegistry: cannot remove self\"\n );\n\n require(\n accounts[account].owners[owner].verifyAtCurrentBlock(),\n \"PersonalAccountRegistry: owner doesn't exist\"\n );\n\n accounts[account].owners[owner].removedAtBlockNumber = block.number;\n\n emit AccountOwnerRemoved(\n account,\n owner\n );\n }\n\n /**\n * @notice Executes account transaction\n * @dev Deploys an account if not deployed yet\n * @param account account address\n * @param to to address\n * @param value value\n * @param data data\n */\n function executeAccountTransaction(\n address account,\n address to,\n uint256 value,\n bytes calldata data\n )\n external\n {\n _verifySender(account);\n\n _deployAccount(account);\n\n _executeAccountTransaction(\n account,\n to,\n value,\n data,\n true\n );\n }\n\n /**\n * @notice Refunds account call\n * @dev Deploys an account if not deployed yet\n * @param account account address\n * @param token token address\n * @param value value\n */\n function refundAccountCall(\n address account,\n address token,\n uint256 value\n )\n external\n {\n _verifySender(account);\n\n _deployAccount(account);\n\n /* solhint-disable avoid-tx-origin */\n\n if (token == address(0)) {\n _executeAccountTransaction(\n account,\n tx.origin,\n value,\n new bytes(0),\n false\n );\n } else {\n bytes memory response = _executeAccountTransaction(\n account,\n token,\n 0,\n abi.encodeWithSelector(\n ERC20Token(token).transfer.selector,\n tx.origin,\n value\n ),\n false\n );\n\n if (response.length > 0) {\n require(\n abi.decode(response, (bool)),\n \"PersonalAccountRegistry: ERC20Token transfer reverted\"\n );\n }\n }\n\n emit AccountCallRefunded(\n account,\n tx.origin,\n token,\n value\n );\n\n /* solhint-enable avoid-tx-origin */\n }\n\n // external functions (views)\n\n /**\n * @notice Computes account address\n * @param saltOwner salt owner address\n * @return account address\n */\n function computeAccountAddress(\n address saltOwner\n )\n external\n view\n returns (address)\n {\n return _computeAccountAddress(saltOwner);\n }\n\n /**\n * @notice Checks if account is deployed\n * @param account account address\n * @return true when account is deployed\n */\n function isAccountDeployed(\n address account\n )\n external\n view\n returns (bool)\n {\n return accounts[account].deployed;\n }\n\n /**\n * @notice Verifies the owner of the account at the current block\n * @param account account address\n * @param owner owner address\n * @return true on correct account owner\n */\n function verifyAccountOwner(\n address account,\n address owner\n )\n external\n view\n returns (bool)\n {\n return _verifyAccountOwner(account, owner);\n }\n\n /**\n * @notice Verifies the owner of the account at a specific block\n * @param account account address\n * @param owner owner address\n * @param blockNumber block number to verify\n * @return true on correct account owner\n */\n function verifyAccountOwnerAtBlock(\n address account,\n address owner,\n uint256 blockNumber\n )\n external\n view\n returns (bool)\n {\n bool result = false;\n\n if (_verifyAccountOwner(account, owner)) {\n result = true;\n } else {\n result = accounts[account].owners[owner].verifyAtBlock(blockNumber);\n }\n\n return result;\n }\n\n /**\n * @notice Verifies account signature\n * @param account account address\n * @param messageHash message hash\n * @param signature signature\n * @return magic hash if valid\n */\n function isValidAccountSignature(\n address account,\n bytes32 messageHash,\n bytes calldata signature\n )\n override\n external\n view\n returns (bool)\n {\n return _verifyAccountOwner(\n account,\n messageHash.recoverAddress(signature)\n );\n }\n\n /**\n * @notice Verifies account signature\n * @param account account address\n * @param message message\n * @param signature signature\n * @return magic hash if valid\n */\n function isValidAccountSignature(\n address account,\n bytes calldata message,\n bytes calldata signature\n )\n override\n external\n view\n returns (bool)\n {\n return _verifyAccountOwner(\n account,\n message.toEthereumSignedMessageHash().recoverAddress(signature)\n );\n }\n\n // private functions\n\n function _verifySender(\n address account\n )\n private\n returns (address)\n {\n address sender = _getContextSender();\n\n if (accounts[account].owners[sender].added) {\n require(\n accounts[account].owners[sender].removedAtBlockNumber == 0,\n \"PersonalAccountRegistry: sender is not the account owner\"\n );\n } else {\n require(\n accounts[account].salt == 0,\n \"PersonalAccountRegistry: sender is not the account owner\"\n );\n\n bytes32 salt = keccak256(\n abi.encodePacked(sender)\n );\n\n require(\n account == _computeAccountAddress(salt),\n \"PersonalAccountRegistry: sender is not the account owner\"\n );\n\n accounts[account].salt = salt;\n accounts[account].owners[sender].added = true;\n\n emit AccountOwnerAdded(\n account,\n sender\n );\n }\n\n return sender;\n }\n\n function _deployAccount(\n address account\n )\n internal\n {\n if (!accounts[account].deployed) {\n _deployAccount(\n accounts[account].salt,\n true\n );\n\n accounts[account].deployed = true;\n }\n }\n\n // private functions (views)\n\n function _computeAccountAddress(\n address saltOwner\n )\n private\n view\n returns (address)\n {\n bytes32 salt = keccak256(\n abi.encodePacked(saltOwner)\n );\n\n return _computeAccountAddress(salt);\n }\n\n function _verifyAccountOwner(\n address account,\n address owner\n )\n private\n view\n returns (bool)\n {\n bool result;\n\n if (accounts[account].owners[owner].added) {\n result = accounts[account].owners[owner].removedAtBlockNumber == 0;\n } else if (accounts[account].salt == 0) {\n result = account == _computeAccountAddress(owner);\n }\n\n return result;\n }\n}\n" }, "src/common/libs/BlockLib.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\n/**\n * @title Block library\n *\n * @author Stanisław Głogowski <stan@pillarproject.io>\n */\nlibrary BlockLib {\n struct BlockRelated {\n bool added;\n uint256 removedAtBlockNumber;\n }\n\n /**\n * @notice Verifies self struct at current block\n * @param self self struct\n * @return true on correct self struct\n */\n function verifyAtCurrentBlock(\n BlockRelated memory self\n )\n internal\n view\n returns (bool)\n {\n return verifyAtBlock(self, block.number);\n }\n\n /**\n * @notice Verifies self struct at any block\n * @param self self struct\n * @return true on correct self struct\n */\n function verifyAtAnyBlock(\n BlockRelated memory self\n )\n internal\n pure\n returns (bool)\n {\n return verifyAtBlock(self, 0);\n }\n\n /**\n * @notice Verifies self struct at specific block\n * @param self self struct\n * @param blockNumber block number to verify\n * @return true on correct self struct\n */\n function verifyAtBlock(\n BlockRelated memory self,\n uint256 blockNumber\n )\n internal\n pure\n returns (bool)\n {\n bool result = false;\n\n if (self.added) {\n if (self.removedAtBlockNumber == 0) {\n result = true;\n } else if (blockNumber == 0) {\n result = true;\n } else {\n result = self.removedAtBlockNumber > blockNumber;\n }\n }\n\n return result;\n }\n}\n" }, "src/common/account/AccountController.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\nimport \"./Account.sol\";\n\n\n/**\n * @title Account controller\n *\n * @dev Contract module which provides Account deployment mechanism\n *\n * @author Stanisław Głogowski <stan@pillarproject.io>\n */\ncontract AccountController {\n address public accountRegistry;\n address public accountImplementation;\n\n // events\n\n /**\n * @dev Emitted when the account registry is updated\n * @param accountRegistry account registry address\n */\n event AccountRegistryUpdated(\n address accountRegistry\n );\n\n /**\n * @dev Emitted when the account implementation is updated\n * @param accountImplementation account implementation address\n */\n event AccountImplementationUpdated(\n address accountImplementation\n );\n\n /**\n * @dev Emitted when the account is deployed\n * @param account account address\n * @param accountImplementation account implementation address\n */\n event AccountDeployed(\n address account,\n address accountImplementation\n );\n\n /**\n * @dev Emitted when the account is upgraded\n * @param account account address\n * @param accountImplementation account implementation address\n */\n event AccountUpgraded(\n address account,\n address accountImplementation\n );\n\n /**\n * @dev Emitted when the transaction is executed\n * @param account account address\n * @param to to address\n * @param value value\n * @param data data\n * @param response response\n */\n event AccountTransactionExecuted(\n address account,\n address to,\n uint256 value,\n bytes data,\n bytes response\n );\n\n /**\n * @dev Internal constructor\n */\n constructor() internal {}\n\n // internal functions\n\n /**\n * @notice Initializes `AccountController` contract\n * @param accountRegistry_ account registry address\n * @param accountImplementation_ account implementation address\n */\n function _initializeAccountController(\n address accountRegistry_,\n address accountImplementation_\n )\n internal\n {\n _setAccountRegistry(accountRegistry_, false);\n _setAccountImplementation(accountImplementation_, false);\n }\n\n /**\n * @notice Sets account registry\n * @param accountRegistry_ account registry address\n * @param emitEvent it will emit event when flag is set to true\n */\n function _setAccountRegistry(\n address accountRegistry_,\n bool emitEvent\n )\n internal\n {\n require(\n accountRegistry_ != address(0),\n \"AccountController: cannot set account registry to 0x0\"\n );\n\n accountRegistry = accountRegistry_;\n\n if (emitEvent) {\n emit AccountRegistryUpdated(accountRegistry);\n }\n }\n\n /**\n * @notice Sets account implementation\n * @param accountImplementation_ account implementation address\n * @param emitEvent it will emit event when flag is set to true\n */\n function _setAccountImplementation(\n address accountImplementation_,\n bool emitEvent\n )\n internal\n {\n require(\n accountImplementation_ != address(0),\n \"AccountController: cannot set account Implementation to 0x0\"\n );\n\n accountImplementation = accountImplementation_;\n\n if (emitEvent) {\n emit AccountImplementationUpdated(accountImplementation);\n }\n }\n\n /**\n * @notice Deploys account\n * @param salt CREATE2 salt\n * @param emitEvent it will emit event when flag is set to true\n * @return account address\n */\n function _deployAccount(\n bytes32 salt,\n bool emitEvent\n )\n internal\n returns (address)\n {\n address account = address(new Account{salt: salt}(\n accountRegistry,\n accountImplementation\n ));\n\n if (emitEvent) {\n emit AccountDeployed(\n account,\n accountImplementation\n );\n }\n\n return account;\n }\n\n /**\n * @notice Upgrades account\n * @param account account address\n * @param emitEvent it will emit event when flag is set to true\n */\n function _upgradeAccount(\n address account,\n bool emitEvent\n )\n internal\n {\n require(\n Account(payable(account)).implementation() != accountImplementation,\n \"AccountController: account already upgraded\"\n );\n\n Account(payable(account)).setImplementation(accountImplementation);\n\n if (emitEvent) {\n emit AccountUpgraded(\n account,\n accountImplementation\n );\n }\n }\n\n /**\n * @notice Executes transaction from the account\n * @param account account address\n * @param to to address\n * @param value value\n * @param data data\n * @param emitEvent it will emit event when flag is set to true\n * @return transaction result\n */\n function _executeAccountTransaction(\n address account,\n address to,\n uint256 value,\n bytes memory data,\n bool emitEvent\n )\n internal\n returns (bytes memory)\n {\n require(\n to != address(0),\n \"AccountController: cannot send to 0x0\"\n );\n\n require(\n to != address(this),\n \"AccountController: cannot send to controller\"\n );\n\n require(\n to != account,\n \"AccountController: cannot send to self\"\n );\n\n bytes memory response = Account(payable(account)).executeTransaction(\n to,\n value,\n data\n );\n\n if (emitEvent) {\n emit AccountTransactionExecuted(\n account,\n to,\n value,\n data,\n response\n );\n }\n\n return response;\n }\n\n // internal functions (views)\n\n /**\n * @notice Computes account CREATE2 address\n * @param salt CREATE2 salt\n * @return account address\n */\n function _computeAccountAddress(\n bytes32 salt\n )\n internal\n view\n returns (address)\n {\n bytes memory creationCode = abi.encodePacked(\n type(Account).creationCode,\n bytes12(0),\n accountRegistry,\n bytes12(0),\n accountImplementation\n );\n\n bytes32 data = keccak256(\n abi.encodePacked(\n bytes1(0xff),\n address(this),\n salt,\n keccak256(creationCode)\n )\n );\n\n return address(uint160(uint256(data)));\n }\n}\n" }, "src/common/account/AccountRegistry.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.6.12;\n\nimport \"./Account.sol\";\n\n\n/**\n * @title Account registry\n *\n * @author Stanisław Głogowski <stan@pillarproject.io>\n */\nabstract contract AccountRegistry {\n /**\n * @notice Verifies account signature\n * @param account account address\n * @param messageHash message hash\n * @param signature signature\n * @return true if valid\n */\n function isValidAccountSignature(\n address account,\n bytes32 messageHash,\n bytes calldata signature\n )\n virtual\n external\n view\n returns (bool);\n\n /**\n * @notice Verifies account signature\n * @param account account address\n * @param message message\n * @param signature signature\n * @return true if valid\n */\n function isValidAccountSignature(\n address account,\n bytes calldata message,\n bytes calldata signature\n )\n virtual\n external\n view\n returns (bool);\n}\n" }, "src/common/libs/ECDSAExtendedLib.so