├── .gitignore ├── LICENSE ├── README.md ├── audits ├── 20200218_Samczsum_Authereum_Disclosure.pdf ├── 20200219_Quantstamp_Authereum_Audit.pdf └── 20200617_G0Group_Authereum_Audit.pdf ├── brownie-config.json ├── contracts ├── Migrations.sol ├── account │ ├── AccountUpgradeability.sol │ ├── AuthKeyMetaTxAccount.sol │ ├── AuthereumAccount.sol │ ├── BaseAccount.sol │ ├── BaseMetaTxAccount.sol │ ├── ERC1271Account.sol │ ├── LoginKeyMetaTxAccount.sol │ ├── TokenReceiverHooks.sol │ ├── event │ │ └── AccountEvents.sol │ ├── initializer │ │ ├── AccountInitialize.sol │ │ ├── AccountInitializeV1.sol │ │ ├── AccountInitializeV2.sol │ │ └── AccountInitializeV3.sol │ └── state │ │ ├── AccountState.sol │ │ └── AccountStateV1.sol ├── admin │ └── Timelock.sol ├── base │ ├── Managed.sol │ └── Owned.sol ├── ens │ ├── AuthereumENSManager.sol │ ├── AuthereumENSResolver.sol │ └── state │ │ ├── AuthereumEnsResolverState.sol │ │ └── AuthereumEnsResolverStateV1.sol ├── firewall │ └── TransactionLimit.sol ├── interfaces │ ├── IAuthereumAccount.sol │ ├── IERC1155TokenReceiver.sol │ ├── IERC1271.sol │ ├── IERC1820ImplementerInterface.sol │ ├── IERC1820Registry.sol │ ├── IERC20.sol │ ├── IERC721Receiver.sol │ ├── IERC777TokensRecipient.sol │ └── ILoginKeyTransactionValidator.sol ├── libs │ ├── Address.sol │ ├── BytesLib.sol │ ├── ECDSA.sol │ ├── SafeMath.sol │ └── strings.sol ├── modules │ ├── AuthereumDelegateKeyModule.sol │ └── AuthereumRecoveryModule.sol ├── test │ ├── BadTransaction.sol │ ├── ERC1820Registry.sol │ ├── ERC777Token.sol │ ├── EnsBaseRegistrar.sol │ ├── EnsRegistry.sol │ ├── EnsResolver.sol │ ├── EnsReverseRegistrar.sol │ ├── ParseCallData.sol │ ├── ResetManager.sol │ ├── ReturnTransaction.sol │ ├── TestERC20.sol │ ├── UpgradeAccount.sol │ ├── UpgradeAccountBadInit.sol │ └── UpgradeAccountWithInit.sol ├── upgradeability │ ├── AuthereumEnsResolverProxy.sol │ ├── AuthereumProxy.sol │ └── AuthereumProxyFactory.sol └── validation │ └── AuthereumLoginKeyValidator.sol ├── jest.config.js ├── migrations └── 1_initial_migration.js ├── package.json ├── test ├── account │ ├── AccountUpgradeability.js │ ├── AuthKeyMetaTxAccount.js │ ├── AuthereumAccount.js │ ├── BaseAccount.js │ ├── BaseMetaTxAccount.js │ ├── ERC1271Account.js │ └── LoginKeyMetaTxAccount.js ├── admin │ └── timelock.js ├── base │ ├── Managed.js │ └── Owned.js ├── ens │ ├── AuthereumENSManager.js │ └── AuthereumEnsResolver.js ├── firewall │ └── TransactionLimit.js ├── modules │ ├── AuthereumDelegateKeyModule.js │ └── AuthereumRecoveryModule.js ├── test │ ├── GanacheEnvironment.js │ └── ParseCallData.js ├── upgradeability │ ├── AuthereumEnsResolverProxy.js │ ├── AuthereumProxy.js │ └── AuthereumProxyFactory.js ├── utils │ ├── constants.js │ ├── time.js │ └── utils.js └── validation │ └── AuthereumLoginKeyValidator.js ├── truffle.js ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | build/contracts 4 | dist/ 5 | .idea/ 6 | node_modules 7 | package-lock.json 8 | contracts/token/projects/ 9 | test/token/projects/ 10 | truffle-config.js 11 | .env 12 | *~ 13 | *.swp 14 | *.swo 15 | *.log 16 | .openzeppelin/.session 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT license 2 | 3 | Copyright (C) 2019 Authereum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /audits/20200218_Samczsum_Authereum_Disclosure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/authereum/contracts/23b3eb93a079e6b0577f930a2ad4314b9dd79545/audits/20200218_Samczsum_Authereum_Disclosure.pdf -------------------------------------------------------------------------------- /audits/20200219_Quantstamp_Authereum_Audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/authereum/contracts/23b3eb93a079e6b0577f930a2ad4314b9dd79545/audits/20200219_Quantstamp_Authereum_Audit.pdf -------------------------------------------------------------------------------- /audits/20200617_G0Group_Authereum_Audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/authereum/contracts/23b3eb93a079e6b0577f930a2ad4314b9dd79545/audits/20200617_G0Group_Authereum_Audit.pdf -------------------------------------------------------------------------------- /brownie-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "network_defaults":{ 3 | "name": "development", 4 | "gas_limit": false, 5 | "gas_price": false 6 | }, 7 | "networks": { 8 | "development": { 9 | "test-rpc": "ganache-cli", 10 | "host": "http://127.0.0.1:8545", 11 | "broadcast_reverting_tx": true 12 | }, 13 | "ropsten": { 14 | "host": "https://ropsten.infura.io/", 15 | "broadcast_reverting_tx": false 16 | } 17 | }, 18 | "test":{ 19 | "gas_limit": 6721975, 20 | "default_contract_owner": false, 21 | "broadcast_reverting_tx": true 22 | }, 23 | "solc":{ 24 | "optimize": true, 25 | "runs": 200, 26 | "version": "0.5.7" 27 | }, 28 | "logging": { 29 | "test": { 30 | "tx": 0, 31 | "exc": [1, 2] 32 | 33 | }, 34 | "run": { 35 | "tx": [1, 2], 36 | "exc": 2 37 | }, 38 | "console": { 39 | "tx": 1, 40 | "exc": 2 41 | }, 42 | "coverage": { 43 | "tx": 0, 44 | "exc": [1, 2] 45 | } 46 | }, 47 | "colors":{ 48 | "key": "", 49 | "value": "bright blue", 50 | "callable": "bright cyan", 51 | "module": "bright blue", 52 | "contract": "bright magenta", 53 | "contract_method": "bright magenta", 54 | "string": "bright magenta", 55 | "dull": "dark white", 56 | "error": "bright red", 57 | "success": "bright green", 58 | "pending": "bright yellow" 59 | } 60 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/account/AccountUpgradeability.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import '../libs/Address.sol'; 4 | import './BaseAccount.sol'; 5 | 6 | /** 7 | * @title AccountUpgradeability 8 | * @author Authereum Labs, Inc. 9 | * @dev The upgradeability logic for an Authereum account. 10 | */ 11 | 12 | contract AccountUpgradeability is BaseAccount { 13 | /// @dev Storage slot with the address of the current implementation 14 | /// @notice This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted 15 | /// @notice by 1, and is validated in the constructor 16 | bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 17 | 18 | /** 19 | * Public functions 20 | */ 21 | 22 | /// @dev Returns the current implementation 23 | /// @notice This is meant to be called through the proxy to retrieve it's implementation address 24 | /// @return Address of the current implementation 25 | function implementation() public view returns (address impl) { 26 | bytes32 slot = IMPLEMENTATION_SLOT; 27 | assembly { 28 | impl := sload(slot) 29 | } 30 | } 31 | 32 | /// @dev Upgrades the proxy to the newest implementation of a contract and 33 | /// @dev forwards a function call to it 34 | /// @notice This is useful to initialize the proxied contract 35 | /// @param _newImplementation Address of the new implementation 36 | /// @param _data Array of initialize data 37 | function upgradeToAndCall( 38 | address _newImplementation, 39 | bytes memory _data 40 | ) 41 | public 42 | onlySelf 43 | { 44 | _setImplementation(_newImplementation); 45 | (bool success, bytes memory res) = _newImplementation.delegatecall(_data); 46 | 47 | // Get the revert message of the call and revert with it if the call failed 48 | string memory _revertMsg = _getRevertMsgFromRes(res); 49 | require(success, _revertMsg); 50 | emit Upgraded(_newImplementation); 51 | } 52 | 53 | /** 54 | * Internal functions 55 | */ 56 | 57 | /// @dev Sets the implementation address of the proxy 58 | /// @notice This is only meant to be called when upgrading self 59 | /// @notice The initial setImplementation for a proxy is set during 60 | /// @notice the proxy's initialization, not with this call 61 | /// @param _newImplementation Address of the new implementation 62 | function _setImplementation(address _newImplementation) internal { 63 | require(OpenZeppelinUpgradesAddress.isContract(_newImplementation), "AU: Cannot set a proxy implementation to a non-contract address"); 64 | 65 | bytes32 slot = IMPLEMENTATION_SLOT; 66 | 67 | assembly { 68 | sstore(slot, _newImplementation) 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /contracts/account/AuthKeyMetaTxAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./BaseMetaTxAccount.sol"; 5 | 6 | /** 7 | * @title AuthKeyMetaTxAccount 8 | * @author Authereum Labs, Inc. 9 | * @dev Contract used by auth keys to send transactions. 10 | */ 11 | 12 | contract AuthKeyMetaTxAccount is BaseMetaTxAccount { 13 | 14 | /// @dev Execute multiple authKey meta transactions 15 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 16 | /// @param _gasPrice Gas price set by the user 17 | /// @param _gasOverhead Gas overhead of the transaction calculated offchain 18 | /// @param _feeTokenAddress Address of the token used to pay a fee 19 | /// @param _feeTokenRate Rate of the token (in tokenGasPrice/ethGasPrice) used to pay a fee 20 | /// @param _transactionMessageHashSignature Signed transaction data 21 | /// @return Return values of the call 22 | function executeMultipleAuthKeyMetaTransactions( 23 | bytes[] memory _transactions, 24 | uint256 _gasPrice, 25 | uint256 _gasOverhead, 26 | address _feeTokenAddress, 27 | uint256 _feeTokenRate, 28 | bytes memory _transactionMessageHashSignature 29 | ) 30 | public 31 | returns (bytes[] memory) 32 | { 33 | uint256 _startGas = gasleft(); 34 | 35 | // Hash the parameters 36 | bytes32 _transactionMessageHash = keccak256(abi.encode( 37 | address(this), 38 | msg.sig, 39 | getChainId(), 40 | nonce, 41 | _transactions, 42 | _gasPrice, 43 | _gasOverhead, 44 | _feeTokenAddress, 45 | _feeTokenRate 46 | )).toEthSignedMessageHash(); 47 | 48 | // Validate the signer 49 | // NOTE: This must be done prior to the _atomicExecuteMultipleMetaTransactions() call for security purposes 50 | _validateAuthKeyMetaTransactionSigs( 51 | _transactionMessageHash, _transactionMessageHashSignature 52 | ); 53 | 54 | (, bytes[] memory _returnValues) = _atomicExecuteMultipleMetaTransactions( 55 | _transactions, 56 | _gasPrice 57 | ); 58 | 59 | _issueRefund(_startGas, _gasPrice, _gasOverhead, _feeTokenAddress, _feeTokenRate); 60 | 61 | return _returnValues; 62 | } 63 | 64 | /** 65 | * Internal functions 66 | */ 67 | 68 | /// @dev Validate signatures from an auth key meta transaction 69 | /// @param _transactionMessageHash Ethereum signed message of the transaction 70 | /// @param _transactionMessageHashSignature Signed transaction data 71 | /// @return Address of the auth key that signed the data 72 | function _validateAuthKeyMetaTransactionSigs( 73 | bytes32 _transactionMessageHash, 74 | bytes memory _transactionMessageHashSignature 75 | ) 76 | internal 77 | view 78 | { 79 | address _authKey = _transactionMessageHash.recover(_transactionMessageHashSignature); 80 | require(_isValidAuthKey(_authKey), "AKMTA: Auth key is invalid"); 81 | } 82 | } -------------------------------------------------------------------------------- /contracts/account/AuthereumAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./BaseAccount.sol"; 5 | import "./ERC1271Account.sol"; 6 | import "./LoginKeyMetaTxAccount.sol"; 7 | import "./AuthKeyMetaTxAccount.sol"; 8 | import "./AccountUpgradeability.sol"; 9 | import "../interfaces/IAuthereumAccount.sol"; 10 | 11 | /** 12 | * @title AuthereumAccount 13 | * @author Authereum Labs, Inc. 14 | * @dev Top-level contract used when creating an Authereum account. 15 | * @dev This contract is meant to only hold the version. All other logic is inherited. 16 | */ 17 | 18 | contract AuthereumAccount is 19 | IAuthereumAccount, 20 | BaseAccount, 21 | ERC1271Account, 22 | LoginKeyMetaTxAccount, 23 | AuthKeyMetaTxAccount, 24 | AccountUpgradeability 25 | { 26 | string constant public name = "Authereum Account"; 27 | string constant public version = "2020070100"; 28 | } 29 | -------------------------------------------------------------------------------- /contracts/account/BaseAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./initializer/AccountInitialize.sol"; 5 | import "./state/AccountState.sol"; 6 | import "./TokenReceiverHooks.sol"; 7 | import "../interfaces/IERC20.sol"; 8 | import "../libs/ECDSA.sol"; 9 | import "../libs/SafeMath.sol"; 10 | import "../libs/BytesLib.sol"; 11 | import "../libs/strings.sol"; 12 | 13 | /** 14 | * @title BaseAccount 15 | * @author Authereum Labs, Inc. 16 | * @dev Base account contract. Performs most of the functionality 17 | * @dev of an Authereum account contract. 18 | */ 19 | 20 | contract BaseAccount is AccountState, AccountInitialize, TokenReceiverHooks { 21 | using SafeMath for uint256; 22 | using ECDSA for bytes32; 23 | using BytesLib for bytes; 24 | using strings for *; 25 | 26 | string constant public CALL_REVERT_PREFIX = "Authereum Call Revert: "; 27 | 28 | modifier onlyAuthKey { 29 | require(_isValidAuthKey(msg.sender), "BA: Only auth key allowed"); 30 | _; 31 | } 32 | 33 | modifier onlySelf { 34 | require(msg.sender == address(this), "BA: Only self allowed"); 35 | _; 36 | } 37 | 38 | modifier onlyAuthKeyOrSelf { 39 | require(_isValidAuthKey(msg.sender) || msg.sender == address(this), "BA: Only auth key or self allowed"); 40 | _; 41 | } 42 | 43 | // Initialize logic contract via the constructor so it does not need to be done manually 44 | // after the deployment of the logic contract. Using max uint ensures that the true 45 | // lastInitializedVersion is never reached. 46 | constructor () public { 47 | lastInitializedVersion = uint256(-1); 48 | } 49 | 50 | // This is required for funds sent to this contract 51 | function () external payable {} 52 | 53 | /** 54 | * Getters 55 | */ 56 | 57 | /// @dev Get the chain ID constant 58 | /// @return The chain id 59 | function getChainId() public pure returns (uint256) { 60 | uint256 id; 61 | assembly { 62 | id := chainid() 63 | } 64 | return id; 65 | } 66 | 67 | /** 68 | * Public functions 69 | */ 70 | 71 | /// @dev Add an auth key to the list of auth keys 72 | /// @param _authKey Address of the auth key to add 73 | function addAuthKey(address _authKey) external onlyAuthKeyOrSelf { 74 | require(authKeys[_authKey] == false, "BA: Auth key already added"); 75 | require(_authKey != address(this), "BA: Cannot add self as an auth key"); 76 | authKeys[_authKey] = true; 77 | numAuthKeys += 1; 78 | emit AuthKeyAdded(_authKey); 79 | } 80 | 81 | /// @dev Remove an auth key from the list of auth keys 82 | /// @param _authKey Address of the auth key to remove 83 | function removeAuthKey(address _authKey) external onlyAuthKeyOrSelf { 84 | require(authKeys[_authKey] == true, "BA: Auth key not yet added"); 85 | require(numAuthKeys > 1, "BA: Cannot remove last auth key"); 86 | authKeys[_authKey] = false; 87 | numAuthKeys -= 1; 88 | emit AuthKeyRemoved(_authKey); 89 | } 90 | 91 | /** 92 | * Internal functions 93 | */ 94 | 95 | /// @dev Check if an auth key is valid 96 | /// @param _authKey Address of the auth key to validate 97 | /// @return True if the auth key is valid 98 | function _isValidAuthKey(address _authKey) internal view returns (bool) { 99 | return authKeys[_authKey]; 100 | } 101 | 102 | /// @dev Execute a transaction without a refund 103 | /// @notice This is the transaction sent from the CBA 104 | /// @param _to To address of the transaction 105 | /// @param _value Value of the transaction 106 | /// @param _gasLimit Gas limit of the transaction 107 | /// @param _data Data of the transaction 108 | /// @return Response of the call 109 | function _executeTransaction( 110 | address _to, 111 | uint256 _value, 112 | uint256 _gasLimit, 113 | bytes memory _data 114 | ) 115 | internal 116 | returns (bytes memory) 117 | { 118 | (bool success, bytes memory res) = _to.call.gas(_gasLimit).value(_value)(_data); 119 | 120 | // Get the revert message of the call and revert with it if the call failed 121 | if (!success) { 122 | revert(_getPrefixedRevertMsg(res)); 123 | } 124 | 125 | return res; 126 | } 127 | 128 | /// @dev Get the revert message from a call 129 | /// @notice This is needed in order to get the human-readable revert message from a call 130 | /// @param _res Response of the call 131 | /// @return Revert message string 132 | function _getRevertMsgFromRes(bytes memory _res) internal pure returns (string memory) { 133 | // If the _res length is less than 68, then the transaction failed silently (without a revert message) 134 | if (_res.length < 68) return 'BA: Transaction reverted silently'; 135 | bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes 136 | return abi.decode(revertData, (string)); // All that remains is the revert string 137 | } 138 | 139 | /// @dev Get the prefixed revert message from a call 140 | /// @param _res Response of the call 141 | /// @return Prefixed revert message string 142 | function _getPrefixedRevertMsg(bytes memory _res) internal pure returns (string memory) { 143 | string memory _revertMsg = _getRevertMsgFromRes(_res); 144 | return string(abi.encodePacked(CALL_REVERT_PREFIX, _revertMsg)); 145 | } 146 | } -------------------------------------------------------------------------------- /contracts/account/BaseMetaTxAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./BaseAccount.sol"; 5 | 6 | /** 7 | * @title BaseMetaTxAccount 8 | * @author Authereum Labs, Inc. 9 | * @dev Contract that lays the foundations for meta transactions 10 | * @dev are performed in this contract as well. 11 | */ 12 | 13 | contract BaseMetaTxAccount is BaseAccount { 14 | 15 | /** 16 | * Public functions 17 | */ 18 | 19 | /// @dev Execute multiple transactions 20 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 21 | /// @return The responses of the calls 22 | function executeMultipleTransactions(bytes[] memory _transactions) public onlyAuthKey returns (bytes[] memory) { 23 | bool isMetaTransaction = false; 24 | return _executeMultipleTransactions(_transactions, isMetaTransaction); 25 | } 26 | 27 | /// @dev Execute multiple meta transactions 28 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 29 | /// @return The responses of the calls 30 | function executeMultipleMetaTransactions(bytes[] memory _transactions) public onlySelf returns (bytes[] memory) { 31 | bool isMetaTransaction = true; 32 | return _executeMultipleTransactions(_transactions, isMetaTransaction); 33 | } 34 | 35 | /** 36 | * Internal functions 37 | */ 38 | 39 | /// @dev Atomically execute a meta transaction 40 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 41 | /// @param _gasPrice Gas price set by the user 42 | /// @return A success boolean and the responses of the calls 43 | function _atomicExecuteMultipleMetaTransactions( 44 | bytes[] memory _transactions, 45 | uint256 _gasPrice 46 | ) 47 | internal 48 | returns (bool, bytes[] memory) 49 | { 50 | // Verify that the relayer gasPrice is acceptable 51 | require(_gasPrice <= tx.gasprice, "BMTA: Not a large enough tx.gasprice"); 52 | 53 | // Increment nonce by the number of transactions being processed 54 | // NOTE: The nonce will still increment even if batched transactions fail atomically 55 | // NOTE: The reason for this is to mimic an EOA as closely as possible 56 | nonce = nonce.add(_transactions.length); 57 | 58 | bytes memory _encodedTransactions = abi.encodeWithSelector( 59 | this.executeMultipleMetaTransactions.selector, 60 | _transactions 61 | ); 62 | 63 | (bool success, bytes memory res) = address(this).call(_encodedTransactions); 64 | 65 | // Check if any of the atomic transactions failed, if not, decode return data 66 | bytes[] memory _returnValues; 67 | if (!success) { 68 | // If there is no prefix to the reversion reason, we know it was an OOG error 69 | if (res.length == 0) { 70 | revert("BMTA: Atomic call ran out of gas"); 71 | } 72 | 73 | string memory _revertMsg = _getRevertMsgFromRes(res); 74 | emit CallFailed(_revertMsg); 75 | } else { 76 | _returnValues = abi.decode(res, (bytes[])); 77 | } 78 | 79 | return (success, _returnValues); 80 | } 81 | 82 | /// @dev Executes multiple transactions 83 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 84 | /// @param _isMetaTransaction True if the transaction is a meta transaction 85 | /// @return The responses of the calls 86 | function _executeMultipleTransactions(bytes[] memory _transactions, bool _isMetaTransaction) internal returns (bytes[] memory) { 87 | // Execute transactions individually 88 | bytes[] memory _returnValues = new bytes[](_transactions.length); 89 | for(uint i = 0; i < _transactions.length; i++) { 90 | // Execute the transaction 91 | _returnValues[i] = _decodeAndExecuteTransaction(_transactions[i], _isMetaTransaction); 92 | } 93 | 94 | return _returnValues; 95 | } 96 | 97 | /// @dev Decode and execute a meta transaction 98 | /// @param _transaction Transaction (to, value, gasLimit, data) 99 | /// @param _isMetaTransaction True if the transaction is a meta transaction 100 | /// @return Success status and response of the call 101 | function _decodeAndExecuteTransaction(bytes memory _transaction, bool _isMetaTransaction) internal returns (bytes memory) { 102 | (address _to, uint256 _value, uint256 _gasLimit, bytes memory _data) = _decodeTransactionData(_transaction); 103 | 104 | if (_isMetaTransaction) { 105 | // Require that there will be enough gas to complete the atomic transaction 106 | // We use 64/63 of the to account for EIP-150 and validate that there will be enough remaining gas 107 | // We use 34700 as the max possible cost for a call 108 | // We add 100 as a buffer for additional logic costs 109 | // NOTE: An out of gas failure after the completion of the call is the concern of the relayer 110 | // NOTE: This check CANNOT have a revert reason, as the parent caller relies on a non-prefixed message for this reversion 111 | require(gasleft() > _gasLimit.mul(64).div(63).add(34800)); 112 | } 113 | 114 | // Execute the transaction 115 | return _executeTransaction(_to, _value, _gasLimit, _data); 116 | } 117 | 118 | /// @dev Decode transaction data 119 | /// @param _transaction Transaction (to, value, gasLimit, data) 120 | /// @return Decoded transaction 121 | function _decodeTransactionData(bytes memory _transaction) internal pure returns (address, uint256, uint256, bytes memory) { 122 | return abi.decode(_transaction, (address, uint256, uint256, bytes)); 123 | } 124 | 125 | /// @dev Issue a refund 126 | /// @param _startGas Starting gas at the beginning of the transaction 127 | /// @param _gasPrice Gas price to use when sending a refund 128 | /// @param _gasOverhead Gas overhead of the transaction calculated offchain 129 | /// @param _feeTokenAddress Address of the token used to pay a fee 130 | /// @param _feeTokenRate Rate of the token (in tokenGasPrice/ethGasPrice) used to pay a fee 131 | function _issueRefund( 132 | uint256 _startGas, 133 | uint256 _gasPrice, 134 | uint256 _gasOverhead, 135 | address _feeTokenAddress, 136 | uint256 _feeTokenRate 137 | ) 138 | internal 139 | { 140 | uint256 _gasUsed = _startGas.sub(gasleft()).add(_gasOverhead); 141 | 142 | // Pay refund in ETH if _feeTokenAddress is 0. Else, pay in the token 143 | if (_feeTokenAddress == address(0)) { 144 | uint256 totalEthFee = _gasUsed.mul(_gasPrice); 145 | 146 | // Don't refund if there is nothing to refund 147 | if (totalEthFee == 0) return; 148 | require(totalEthFee <= address(this).balance, "BA: Insufficient gas (ETH) for refund"); 149 | 150 | // NOTE: The return value is not checked because the relayer should not propagate a transaction that will revert 151 | // NOTE: and malicious behavior by the relayer here will cost the relayer, as the fee is already calculated 152 | msg.sender.call.value(totalEthFee)(""); 153 | } else { 154 | IERC20 feeToken = IERC20(_feeTokenAddress); 155 | uint256 totalTokenFee = _gasUsed.mul(_feeTokenRate); 156 | 157 | // Don't refund if there is nothing to refund 158 | if (totalTokenFee == 0) return; 159 | require(totalTokenFee <= feeToken.balanceOf(address(this)), "BA: Insufficient gas (token) for refund"); 160 | 161 | // NOTE: The return value is not checked because the relayer should not propagate a transaction that will revert 162 | feeToken.transfer(msg.sender, totalTokenFee); 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /contracts/account/ERC1271Account.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./BaseAccount.sol"; 4 | import "../interfaces/IERC1271.sol"; 5 | 6 | /** 7 | * @title ERC1271Account 8 | * @author Authereum Labs, Inc. 9 | * @dev Implements isValidSignature for ERC1271 compatibility 10 | */ 11 | 12 | contract ERC1271Account is IERC1271, BaseAccount { 13 | 14 | // NOTE: Valid magic value bytes4(keccak256("isValidSignature(bytes32,bytes)") 15 | bytes4 constant private VALID_SIG = 0x1626ba7e; 16 | // NOTE: Valid magic value bytes4(keccak256("isValidSignature(bytes,bytes)") 17 | bytes4 constant private VALID_SIG_BYTES = 0x20c13b0b; 18 | // NOTE: Invalid magic value 19 | bytes4 constant private INVALID_SIG = 0xffffffff; 20 | 21 | /** 22 | * Public functions 23 | */ 24 | 25 | /// @dev Check if a message hash and signature pair is valid 26 | /// @notice The _signature parameter can either be one auth key signature or it can 27 | /// @notice be a login key signature and an auth key signature (signed login key) 28 | /// @param _messageHash Hash of the data that was signed 29 | /// @param _signature Signature(s) of the data. Either a single signature (login) or two (login and auth) 30 | /// @return VALID_SIG or INVALID_SIG hex data 31 | function isValidSignature( 32 | bytes32 _messageHash, 33 | bytes memory _signature 34 | ) 35 | public 36 | view 37 | returns (bytes4) 38 | { 39 | bool isValid = _isValidSignature(_messageHash, _signature); 40 | return isValid ? VALID_SIG : INVALID_SIG; 41 | } 42 | 43 | /// @dev Check if a message and signature pair is valid 44 | /// @notice The _signature parameter can either be one auth key signature or it can 45 | /// @notice be a login key signature and an auth key signature (signed login key) 46 | /// @param _data Data that was signed 47 | /// @param _signature Signature(s) of the data. Either a single signature (login) or two (login and auth) 48 | /// @return VALID_SIG or INVALID_SIG hex data 49 | function isValidSignature( 50 | bytes memory _data, 51 | bytes memory _signature 52 | ) 53 | public 54 | view 55 | returns (bytes4) 56 | { 57 | bytes32 messageHash = _getEthSignedMessageHash(_data); 58 | bool isValid = _isValidSignature(messageHash, _signature); 59 | return isValid ? VALID_SIG_BYTES : INVALID_SIG; 60 | } 61 | 62 | /// @dev Check if a message and auth key signature pair is valid 63 | /// @param _messageHash Message hash that was signed 64 | /// @param _signature Signature of the data signed by the authkey 65 | /// @return True if the signature is valid 66 | function isValidAuthKeySignature( 67 | bytes32 _messageHash, 68 | bytes memory _signature 69 | ) 70 | public 71 | view 72 | returns (bool) 73 | { 74 | require(_signature.length == 65, "ERC1271: Invalid isValidAuthKeySignature _signature length"); 75 | 76 | address authKeyAddress = _messageHash.recover( 77 | _signature 78 | ); 79 | 80 | return _isValidAuthKey(authKeyAddress); 81 | } 82 | 83 | /// @dev Check if a message and login key signature pair is valid, as well as a signed login key by an auth key 84 | /// @param _messageHash Message hash that was signed 85 | /// @param _signature Signature of the data. Signed msg data by the login key and signed login key by auth key 86 | /// @return True if the signature is valid 87 | function isValidLoginKeySignature( 88 | bytes32 _messageHash, 89 | bytes memory _signature 90 | ) 91 | public 92 | view 93 | returns (bool) 94 | { 95 | require(_signature.length >= 130, "ERC1271: Invalid isValidLoginKeySignature _signature length"); 96 | 97 | bytes memory msgHashSignature = _signature.slice(0, 65); 98 | bytes memory loginKeyAttestationSignature = _signature.slice(65, 65); 99 | uint256 restrictionDataLength = _signature.length.sub(130); 100 | bytes memory loginKeyRestrictionsData = _signature.slice(130, restrictionDataLength); 101 | 102 | address _loginKeyAddress = _messageHash.recover( 103 | msgHashSignature 104 | ); 105 | 106 | // NOTE: The OpenZeppelin toEthSignedMessageHash is used here (and not above) 107 | // NOTE: because the length is hard coded at 32 and we know that this will always 108 | // NOTE: be true for this line. 109 | bytes32 loginKeyAttestationMessageHash = keccak256(abi.encode( 110 | _loginKeyAddress, loginKeyRestrictionsData 111 | )).toEthSignedMessageHash(); 112 | 113 | address _authKeyAddress = loginKeyAttestationMessageHash.recover( 114 | loginKeyAttestationSignature 115 | ); 116 | 117 | return _isValidAuthKey(_authKeyAddress); 118 | } 119 | 120 | /** 121 | * Internal functions 122 | */ 123 | 124 | /// @dev Identify the key type and check if a message hash and signature pair is valid 125 | /// @notice The _signature parameter can either be one auth key signature or it can 126 | /// @notice be a login key signature and an auth key signature (signed login key) 127 | /// @param _messageHash Hash of the data that was signed 128 | /// @param _signature Signature(s) of the data. Either a single signature (login) or two (login and auth) 129 | /// @return True if the signature is valid 130 | function _isValidSignature( 131 | bytes32 _messageHash, 132 | bytes memory _signature 133 | ) 134 | public 135 | view 136 | returns (bool) 137 | { 138 | if (_signature.length == 65) { 139 | return isValidAuthKeySignature(_messageHash, _signature); 140 | } else if (_signature.length >= 130) { 141 | return isValidLoginKeySignature(_messageHash, _signature); 142 | } else { 143 | revert("ERC1271: Invalid isValidSignature _signature length"); 144 | } 145 | } 146 | 147 | /// @dev Adds ETH signed message prefix to bytes message and hashes it 148 | /// @param _data Bytes data before adding the prefix 149 | /// @return Prefixed and hashed message 150 | function _getEthSignedMessageHash(bytes memory _data) internal pure returns (bytes32) { 151 | return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", _uint2str(_data.length), _data)); 152 | } 153 | 154 | /// @dev Convert uint to string 155 | /// @param _num Uint to be converted 156 | /// @return String equivalent of the uint 157 | function _uint2str(uint _num) private pure returns (string memory _uintAsString) { 158 | if (_num == 0) { 159 | return "0"; 160 | } 161 | uint i = _num; 162 | uint j = _num; 163 | uint len; 164 | while (j != 0) { 165 | len++; 166 | j /= 10; 167 | } 168 | bytes memory bstr = new bytes(len); 169 | uint k = len - 1; 170 | while (i != 0) { 171 | bstr[k--] = byte(uint8(48 + i % 10)); 172 | i /= 10; 173 | } 174 | return string(bstr); 175 | } 176 | } -------------------------------------------------------------------------------- /contracts/account/LoginKeyMetaTxAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./BaseMetaTxAccount.sol"; 5 | import "../interfaces/ILoginKeyTransactionValidator.sol"; 6 | 7 | /** 8 | * @title LoginKeyMetaTxAccount 9 | * @author Authereum Labs, Inc. 10 | * @dev Contract used by login keys to send transactions. Login key firewall checks 11 | * @dev are performed in this contract as well. 12 | */ 13 | 14 | contract LoginKeyMetaTxAccount is BaseMetaTxAccount { 15 | 16 | /// @dev Execute an loginKey meta transaction 17 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 18 | /// @param _gasPrice Gas price set by the user 19 | /// @param _gasOverhead Gas overhead of the transaction calculated offchain 20 | /// @param _loginKeyRestrictionsData Contains restrictions to the loginKey's functionality 21 | /// @param _feeTokenAddress Address of the token used to pay a fee 22 | /// @param _feeTokenRate Rate of the token (in tokenGasPrice/ethGasPrice) used to pay a fee 23 | /// @param _transactionMessageHashSignature Signed transaction data 24 | /// @param _loginKeyAttestationSignature Signed loginKey 25 | /// @return Return values of the call 26 | function executeMultipleLoginKeyMetaTransactions( 27 | bytes[] memory _transactions, 28 | uint256 _gasPrice, 29 | uint256 _gasOverhead, 30 | bytes memory _loginKeyRestrictionsData, 31 | address _feeTokenAddress, 32 | uint256 _feeTokenRate, 33 | bytes memory _transactionMessageHashSignature, 34 | bytes memory _loginKeyAttestationSignature 35 | ) 36 | public 37 | returns (bytes[] memory) 38 | { 39 | uint256 startGas = gasleft(); 40 | 41 | _validateDestinations(_transactions); 42 | _validateRestrictionDataPreHook(_transactions, _loginKeyRestrictionsData); 43 | 44 | // Hash the parameters 45 | bytes32 _transactionMessageHash = keccak256(abi.encode( 46 | address(this), 47 | msg.sig, 48 | getChainId(), 49 | nonce, 50 | _transactions, 51 | _gasPrice, 52 | _gasOverhead, 53 | _feeTokenAddress, 54 | _feeTokenRate 55 | )).toEthSignedMessageHash(); 56 | 57 | // Validate the signers 58 | // NOTE: This must be done prior to the _atomicExecuteMultipleMetaTransactions() call for security purposes 59 | _validateLoginKeyMetaTransactionSigs( 60 | _transactionMessageHash, _transactionMessageHashSignature, _loginKeyRestrictionsData, _loginKeyAttestationSignature 61 | ); 62 | 63 | (bool success, bytes[] memory _returnValues) = _atomicExecuteMultipleMetaTransactions( 64 | _transactions, 65 | _gasPrice 66 | ); 67 | 68 | // If transaction batch succeeded 69 | if (success) { 70 | _validateRestrictionDataPostHook(_transactions, _loginKeyRestrictionsData); 71 | } 72 | 73 | // Refund gas costs 74 | _issueRefund(startGas, _gasPrice, _gasOverhead, _feeTokenAddress, _feeTokenRate); 75 | 76 | return _returnValues; 77 | } 78 | 79 | /** 80 | * Internal functions 81 | */ 82 | 83 | /// @dev Decodes the loginKeyRestrictionsData and calls the ILoginKeyTransactionValidator contract's pre-execution hook 84 | /// @param _transactions The encoded transactions being executed 85 | /// @param _loginKeyRestrictionsData The encoded data used by the ILoginKeyTransactionValidator contract 86 | function _validateRestrictionDataPreHook( 87 | bytes[] memory _transactions, 88 | bytes memory _loginKeyRestrictionsData 89 | ) 90 | internal 91 | { 92 | (address validationContract, bytes memory validationData) = abi.decode(_loginKeyRestrictionsData, (address, bytes)); 93 | if (validationContract != address(0)) { 94 | ILoginKeyTransactionValidator(validationContract).validateTransactions(_transactions, validationData, msg.sender); 95 | } 96 | } 97 | 98 | /// @dev Decodes the loginKeyRestrictionsData and calls the ILoginKeyTransactionValidator contract's post-execution hook 99 | /// @param _transactions The encoded transactions being executed 100 | /// @param _loginKeyRestrictionsData The encoded data used by the ILoginKeyTransactionValidator contract 101 | function _validateRestrictionDataPostHook( 102 | bytes[] memory _transactions, 103 | bytes memory _loginKeyRestrictionsData 104 | ) 105 | internal 106 | { 107 | (address validationContract, bytes memory validationData) = abi.decode(_loginKeyRestrictionsData, (address, bytes)); 108 | if (validationContract != address(0)) { 109 | ILoginKeyTransactionValidator(validationContract).transactionsDidExecute(_transactions, validationData, msg.sender); 110 | } 111 | } 112 | 113 | /// @dev Validates all loginKey Restrictions 114 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 115 | function _validateDestinations( 116 | bytes[] memory _transactions 117 | ) 118 | internal 119 | view 120 | { 121 | // Check that calls made to self and auth keys have no data and are limited in gas 122 | address to; 123 | uint256 gasLimit; 124 | bytes memory data; 125 | for (uint i = 0; i < _transactions.length; i++) { 126 | (to,,gasLimit,data) = _decodeTransactionData(_transactions[i]); 127 | 128 | if (data.length != 0 || gasLimit > 2300) { 129 | require(to != address(this), "LKMTA: Login key is not able to call self"); 130 | require(!authKeys[to], "LKMTA: Login key is not able to call an Auth key"); 131 | } 132 | } 133 | } 134 | 135 | /// @dev Validate signatures from an auth key meta transaction 136 | /// @param _transactionsMessageHash Ethereum signed message of the transaction 137 | /// @param _transactionMessageHashSignature Signed transaction data 138 | /// @param _loginKeyRestrictionsData Contains restrictions to the loginKey's functionality 139 | /// @param _loginKeyAttestationSignature Signed loginKey 140 | /// @return Address of the login key that signed the data 141 | function _validateLoginKeyMetaTransactionSigs( 142 | bytes32 _transactionsMessageHash, 143 | bytes memory _transactionMessageHashSignature, 144 | bytes memory _loginKeyRestrictionsData, 145 | bytes memory _loginKeyAttestationSignature 146 | ) 147 | internal 148 | view 149 | { 150 | address _transactionMessageSigner = _transactionsMessageHash.recover( 151 | _transactionMessageHashSignature 152 | ); 153 | 154 | bytes32 loginKeyAttestationMessageHash = keccak256(abi.encode( 155 | _transactionMessageSigner, 156 | _loginKeyRestrictionsData 157 | )).toEthSignedMessageHash(); 158 | 159 | address _authKeyAddress = loginKeyAttestationMessageHash.recover( 160 | _loginKeyAttestationSignature 161 | ); 162 | 163 | require(_isValidAuthKey(_authKeyAddress), "LKMTA: Auth key is invalid"); 164 | } 165 | } -------------------------------------------------------------------------------- /contracts/account/TokenReceiverHooks.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract TokenReceiverHooks { 4 | bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b; 5 | bytes32 constant private ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")); 6 | 7 | /** 8 | * ERC721 9 | */ 10 | 11 | /** 12 | * @notice Handle the receipt of an NFT 13 | * @dev The ERC721 smart contract calls this function on the recipient 14 | * after a {IERC721-safeTransferFrom}. This function MUST return the function selector, 15 | * otherwise the caller will revert the transaction. The selector to be 16 | * returned can be obtained as `this.onERC721Received.selector`. This 17 | * function MAY throw to revert and reject the transfer. 18 | * Note: the ERC721 contract address is always the message sender. 19 | * param operator The address which called `safeTransferFrom` function 20 | * param from The address which previously owned the token 21 | * param tokenId The NFT identifier which is being transferred 22 | * param data Additional data with no specified format 23 | * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` 24 | */ 25 | function onERC721Received(address, address, uint256, bytes memory) public returns (bytes4) { 26 | return this.onERC721Received.selector; 27 | } 28 | 29 | /** 30 | * ERC1155 31 | */ 32 | 33 | function onERC1155Received(address, address, uint256, uint256, bytes calldata) external returns(bytes4) { 34 | return this.onERC1155Received.selector; 35 | } 36 | 37 | /** 38 | * @notice Handle the receipt of multiple ERC1155 token types. 39 | * @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. 40 | * This function MUST return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` (i.e. 0xbc197c81) if it accepts the transfer(s). 41 | * This function MUST revert if it rejects the transfer(s). 42 | * Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. 43 | * param _operator The address which initiated the batch transfer (i.e. msg.sender) 44 | * param _from The address which previously owned the token 45 | * param _ids An array containing ids of each token being transferred (order and length must match _values array) 46 | * param _values An array containing amounts of each token being transferred (order and length must match _ids array) 47 | * param _data Additional data with no specified format 48 | * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` 49 | */ 50 | function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) external returns(bytes4) { 51 | return this.onERC1155BatchReceived.selector; 52 | } 53 | 54 | /** 55 | * ERC777 56 | */ 57 | 58 | /// @dev Notify a send or mint (if from is 0x0) of amount tokens from the from address to the 59 | /// to address by the operator address. 60 | /// param operator Address which triggered the balance increase (through sending or minting). 61 | /// param from Holder whose tokens were sent (or 0x0 for a mint). 62 | /// param to Recipient of the tokens. 63 | /// param amount Number of tokens the recipient balance is increased by. 64 | /// param data Information provided by the holder. 65 | /// param operatorData Information provided by the operator. 66 | function tokensReceived( 67 | address, 68 | address, 69 | address, 70 | uint256, 71 | bytes calldata, 72 | bytes calldata 73 | ) external { } 74 | 75 | /** 76 | * ERC1820 77 | */ 78 | 79 | /// @dev Indicates whether the contract implements the interface `interfaceHash` for the address `addr` or not. 80 | /// @param interfaceHash keccak256 hash of the name of the interface 81 | /// @param addr Address for which the contract will implement the interface 82 | /// @return ERC1820_ACCEPT_MAGIC only if the contract implements `interfaceHash` for the address `addr`. 83 | function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32) { 84 | if (interfaceHash == TOKENS_RECIPIENT_INTERFACE_HASH && addr == address(this)) { 85 | return ERC1820_ACCEPT_MAGIC; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /contracts/account/event/AccountEvents.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity 0.5.17; 3 | 4 | /** 5 | * @title AccountEvents 6 | * @author Authereum Labs, Inc. 7 | * @dev This contract holds the events used by the Authereum contracts. 8 | * @dev This abstraction exists in order to retain the order to give initialization functions 9 | * @dev access to events. 10 | * @dev This contract can be overwritten with no changes to the upgradeability. 11 | */ 12 | 13 | contract AccountEvents { 14 | 15 | /** 16 | * BaseAccount.sol 17 | */ 18 | 19 | event AuthKeyAdded(address indexed authKey); 20 | event AuthKeyRemoved(address indexed authKey); 21 | event CallFailed(string reason); 22 | 23 | /** 24 | * AccountUpgradeability.sol 25 | */ 26 | 27 | event Upgraded(address indexed implementation); 28 | } -------------------------------------------------------------------------------- /contracts/account/initializer/AccountInitialize.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./AccountInitializeV1.sol"; 4 | import "./AccountInitializeV2.sol"; 5 | import "./AccountInitializeV3.sol"; 6 | 7 | /** 8 | * @title AccountInitialize 9 | * @author Authereum Labs, Inc. 10 | * @dev This contract holds the initialize functions used by the account contracts. 11 | * @dev This exists as the main contract to hold these functions. This contract is inherited 12 | * @dev by AuthereumAccount.sol, which will not care about initialization functions as long as it inherits 13 | * @dev AccountInitialize.sol. Any initialization function additions will be made to the various 14 | * @dev versions of AccountInitializeVx that this contract will inherit. 15 | */ 16 | 17 | contract AccountInitialize is AccountInitializeV1, AccountInitializeV2, AccountInitializeV3 {} -------------------------------------------------------------------------------- /contracts/account/initializer/AccountInitializeV1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../state/AccountState.sol"; 4 | import "../event/AccountEvents.sol"; 5 | 6 | /** 7 | * @title AccountInitializeV1 8 | * @author Authereum Labs, Inc. 9 | * @dev This contract holds the initialize function used by the account contracts. 10 | * @dev This abstraction exists in order to retain the order of the initialization functions. 11 | */ 12 | 13 | contract AccountInitializeV1 is AccountState, AccountEvents { 14 | 15 | /// @dev Initialize the Authereum Account 16 | /// @param _authKey authKey that will own this account 17 | function initializeV1( 18 | address _authKey 19 | ) 20 | public 21 | { 22 | require(lastInitializedVersion == 0, "AI: Improper initialization order"); 23 | lastInitializedVersion = 1; 24 | 25 | // Add first authKey 26 | authKeys[_authKey] = true; 27 | numAuthKeys += 1; 28 | emit AuthKeyAdded(_authKey); 29 | } 30 | } -------------------------------------------------------------------------------- /contracts/account/initializer/AccountInitializeV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../state/AccountState.sol"; 4 | import "../../interfaces/IERC20.sol"; 5 | 6 | /** 7 | * @title AccountInitializeV2 8 | * @author Authereum Labs, Inc. 9 | * @dev This contract holds the initialize function used by the account contracts. 10 | * @dev This abstraction exists in order to retain the order of the initialization functions. 11 | */ 12 | 13 | contract AccountInitializeV2 is AccountState { 14 | 15 | /// @dev Add the ability to refund the contract for a deployment 16 | /// @param _deploymentCost Cost of the deployment 17 | /// @param _deploymentFeeTokenAddress Address of the token used to pay a deployment fee 18 | function initializeV2( 19 | uint256 _deploymentCost, 20 | address _deploymentFeeTokenAddress 21 | ) 22 | public 23 | { 24 | require(lastInitializedVersion == 1, "AI2: Improper initialization order"); 25 | lastInitializedVersion = 2; 26 | 27 | if (_deploymentCost != 0) { 28 | if (_deploymentFeeTokenAddress == address(0)) { 29 | uint256 amountToTransfer = _deploymentCost < address(this).balance ? _deploymentCost : address(this).balance; 30 | tx.origin.transfer(amountToTransfer); 31 | } else { 32 | IERC20 deploymentFeeToken = IERC20(_deploymentFeeTokenAddress); 33 | uint256 userBalanceOf = deploymentFeeToken.balanceOf(address(this)); 34 | uint256 amountToTransfer = _deploymentCost < userBalanceOf ? _deploymentCost : userBalanceOf; 35 | deploymentFeeToken.transfer(tx.origin, amountToTransfer); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /contracts/account/initializer/AccountInitializeV3.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../state/AccountState.sol"; 4 | import "../../interfaces/IERC1820Registry.sol"; 5 | 6 | /** 7 | * @title AccountInitializeV3 8 | * @author Authereum Labs, Inc. 9 | * @dev This contract holds the initialize function used by the account contracts. 10 | * @dev This abstraction exists in order to retain the order of the initialization functions. 11 | */ 12 | 13 | contract AccountInitializeV3 is AccountState { 14 | 15 | address constant private ERC1820_REGISTRY_ADDRESS = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24; 16 | bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = 0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b; 17 | 18 | /// @dev Initialize the Authereum Account with the 1820 registry 19 | function initializeV3() public { 20 | require(lastInitializedVersion == 2, "AI3: Improper initialization order"); 21 | lastInitializedVersion = 3; 22 | 23 | IERC1820Registry registry = IERC1820Registry(ERC1820_REGISTRY_ADDRESS); 24 | registry.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/account/state/AccountState.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./AccountStateV1.sol"; 4 | 5 | /** 6 | * @title AccountState 7 | * @author Authereum Labs, Inc. 8 | * @dev This contract holds the state variables used by the account contracts. 9 | * @dev This exists as the main contract to hold state. This contract is inherited 10 | * @dev by Account.sol, which will not care about state as long as it inherits 11 | * @dev AccountState.sol. Any state variable additions will be made to the various 12 | * @dev versions of AccountStateVX that this contract will inherit. 13 | */ 14 | 15 | contract AccountState is AccountStateV1 {} -------------------------------------------------------------------------------- /contracts/account/state/AccountStateV1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @title AccountStateV1 5 | * @author Authereum Labs, Inc. 6 | * @dev This contract holds the state variables used by the account contracts. 7 | * @dev This abstraction exists in order to retain the order of the state variables. 8 | */ 9 | 10 | contract AccountStateV1 { 11 | uint256 public lastInitializedVersion; 12 | mapping(address => bool) public authKeys; 13 | uint256 public nonce; 14 | uint256 public numAuthKeys; 15 | } -------------------------------------------------------------------------------- /contracts/admin/Timelock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../base/Owned.sol"; 4 | 5 | /** 6 | * @title Timelock 7 | * @author Authereum Labs, Inc. 8 | * @dev Used to make changes to contracts with a known time lock. 9 | * @dev The goal of this contract is to make the public aware of contract changes made 10 | * @dev by the contract owners. This will keep the owners honest and will allow the 11 | * @dev users of the contracts to remove any funds if they suspect any foul play. 12 | */ 13 | 14 | contract Timelock is Owned { 15 | 16 | uint256 public timelock; 17 | uint256 public timelockExpire; 18 | enum ChangeState {Uninitialized, Pending, Changeable, Expired} 19 | 20 | struct ContractChangeTime{ 21 | uint256 unlockTime; 22 | uint256 unlockExpireTime; 23 | } 24 | 25 | event TimelockUpdated(uint256 indexed newTimelock); 26 | event TimelockExpireUpdated(uint256 indexed newTimelockExpire); 27 | event ChangeInitiated(bytes data, address indexed changeAddress, uint256 changeTime); 28 | event ChangeExecuted(bytes data, address indexed changeAddress, uint256 changeTime); 29 | event ChangeCancelled(bytes data, address indexed changeAddress, uint256 changeTime); 30 | 31 | mapping(bytes => mapping(address => ContractChangeTime)) public changes; 32 | 33 | modifier onlyThisContract { 34 | require(msg.sender == address(this), "T: Only this contract can call this function"); 35 | _; 36 | } 37 | 38 | /// @param _timelock Amount of time that a pending change is locked for 39 | /// @param _timelockExpire Amount of time AFTER timelock that data is changeable before expiring 40 | constructor(uint256 _timelock, uint256 _timelockExpire) public { 41 | timelock = _timelock; 42 | timelockExpire = _timelockExpire; 43 | emit TimelockUpdated(timelock); 44 | emit TimelockExpireUpdated(timelockExpire); 45 | } 46 | 47 | /** 48 | * Getters 49 | */ 50 | 51 | /// @dev Get unlock time of change 52 | /// @param _data Data that will be the change 53 | /// @param _changeAddress Address that will receive the data 54 | /// @return The unlock time of the transaction 55 | function getUnlockTime(bytes memory _data, address _changeAddress) public view returns (uint256) { 56 | return changes[_data][_changeAddress].unlockTime; 57 | } 58 | 59 | /// @dev Get the expiration time of change 60 | /// @param _data Data that will be the change 61 | /// @param _changeAddress Address that will receive the data 62 | /// @return The unlock expire time of the transaction 63 | function getUnlockExpireTime(bytes memory _data, address _changeAddress) public view returns (uint256) { 64 | return changes[_data][_changeAddress].unlockExpireTime; 65 | } 66 | 67 | /// @dev Get remaining time until change can be made 68 | /// @param _data Data that will be the change 69 | /// @param _changeAddress Address that will receive the data 70 | /// @return The remaining unlock time of the transaction 71 | function getRemainingUnlockTime(bytes memory _data, address _changeAddress) public view returns (uint256) { 72 | uint256 unlockTime = changes[_data][_changeAddress].unlockTime; 73 | if (unlockTime <= block.timestamp) { 74 | return 0; 75 | } 76 | 77 | return unlockTime - block.timestamp; 78 | } 79 | 80 | /// @dev Get remaining time until change will expire 81 | /// @param _data Data that will be the change 82 | /// @param _changeAddress Address that will receive the data 83 | /// @return The remaining unlock expire time of the transaction 84 | function getRemainingUnlockExpireTime(bytes memory _data, address _changeAddress) public view returns (uint256) { 85 | uint256 unlockTime = changes[_data][_changeAddress].unlockTime; 86 | if (unlockTime <= block.timestamp) { 87 | return 0; 88 | } 89 | 90 | return unlockTime - block.timestamp; 91 | } 92 | 93 | /// @dev Get the current state of some data 94 | /// @param _data Data that will be the change 95 | /// @param _changeAddress Address that will receive the data 96 | /// @return The change state of the transaction 97 | function getCurrentChangeState(bytes memory _data, address _changeAddress) public view returns (ChangeState) { 98 | uint256 unlockTime = changes[_data][_changeAddress].unlockTime; 99 | uint256 unlockExpireTime = changes[_data][_changeAddress].unlockExpireTime; 100 | if (unlockTime == 0) { 101 | return ChangeState.Uninitialized; 102 | } else if (block.timestamp < unlockTime) { 103 | return ChangeState.Pending; 104 | } else if (unlockTime <= block.timestamp && block.timestamp < unlockExpireTime) { 105 | return ChangeState.Changeable; 106 | } else if (unlockExpireTime <= block.timestamp) { 107 | return ChangeState.Expired; 108 | } 109 | } 110 | 111 | /** 112 | * Setters 113 | */ 114 | 115 | /// @dev Sets a new timelock 116 | /// @notice Can only be called by self 117 | /// @param _timelock New timelock time 118 | function setTimelock(uint256 _timelock) public onlyThisContract { 119 | timelock = _timelock; 120 | emit TimelockUpdated(timelock); 121 | } 122 | 123 | /// @dev Sets a new timelock expiration 124 | /// @notice Can only be called by self 125 | /// @param _timelockExpire New timelock time 126 | function setTimelockExpire(uint256 _timelockExpire) public onlyThisContract { 127 | timelockExpire = _timelockExpire; 128 | emit TimelockExpireUpdated(timelockExpire); 129 | } 130 | 131 | /** 132 | * Public functions 133 | */ 134 | 135 | /// @dev Initiate change 136 | /// @param _data Data that will be the change 137 | /// @param _changeAddress Address that will receive the data 138 | function initiateChange(bytes memory _data, address _changeAddress) public onlyOwner { 139 | require(getCurrentChangeState(_data, _changeAddress) == ChangeState.Uninitialized, "T: Change not able to be initiated"); 140 | changes[_data][_changeAddress].unlockTime = timelock + block.timestamp; 141 | changes[_data][_changeAddress].unlockExpireTime = changes[_data][_changeAddress].unlockTime + timelockExpire; 142 | 143 | emit ChangeInitiated(_data, _changeAddress, block.timestamp); 144 | } 145 | 146 | /// @dev Execute change 147 | /// @param _data Data that will be the change 148 | /// @param _changeAddress Address that will receive the data 149 | function executeChange(bytes memory _data, address _changeAddress) public payable onlyOwner { 150 | require(getCurrentChangeState(_data, _changeAddress) == ChangeState.Changeable, "T: Change not able to be made"); 151 | delete changes[_data][_changeAddress]; 152 | _changeAddress.call.value(msg.value)(_data); 153 | emit ChangeExecuted(_data, _changeAddress, block.timestamp); 154 | } 155 | 156 | /// @dev Cancel change 157 | /// @param _data Data that will be cancelled 158 | /// @param _changeAddress Address that will receive the data 159 | function cancelChange(bytes memory _data, address _changeAddress) public onlyOwner { 160 | delete changes[_data][_changeAddress]; 161 | emit ChangeCancelled(_data, _changeAddress, block.timestamp); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /contracts/base/Managed.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./Owned.sol"; 4 | 5 | /** 6 | * @title Managed 7 | * @author Authereum Labs, Inc. 8 | * @dev Basic contract that defines a set of managers. Only the owner can add/remove managers. 9 | */ 10 | 11 | contract Managed is Owned { 12 | 13 | // The managers 14 | mapping(address => bool) public managers; 15 | 16 | /// @dev Throws if the sender is not a manager 17 | modifier onlyManager { 18 | require(managers[msg.sender] == true, "M: Must be manager"); 19 | _; 20 | } 21 | 22 | event ManagerAdded(address indexed _manager); 23 | event ManagerRevoked(address indexed _manager); 24 | 25 | /// @dev Adds a manager 26 | /// @param _manager The address of the manager 27 | function addManager(address _manager) external onlyOwner { 28 | require(_manager != address(0), "M: Address must not be null"); 29 | if(managers[_manager] == false) { 30 | managers[_manager] = true; 31 | emit ManagerAdded(_manager); 32 | } 33 | } 34 | 35 | /// @dev Revokes a manager 36 | /// @param _manager The address of the manager 37 | function revokeManager(address _manager) external onlyOwner { 38 | require(managers[_manager] == true, "M: Target must be an existing manager"); 39 | delete managers[_manager]; 40 | emit ManagerRevoked(_manager); 41 | } 42 | } -------------------------------------------------------------------------------- /contracts/base/Owned.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @title Owned 5 | * @author Authereum Labs, Inc. 6 | * @dev Basic contract to define an owner. 7 | */ 8 | 9 | contract Owned { 10 | 11 | // The owner 12 | address public owner; 13 | 14 | event OwnerChanged(address indexed _newOwner); 15 | 16 | /// @dev Throws if the sender is not the owner 17 | modifier onlyOwner { 18 | require(msg.sender == owner, "O: Must be owner"); 19 | _; 20 | } 21 | 22 | constructor() public { 23 | owner = msg.sender; 24 | } 25 | 26 | /// @dev Return the ownership status of an address 27 | /// @param _potentialOwner Address being checked 28 | /// @return True if the _potentialOwner is the owner 29 | function isOwner(address _potentialOwner) external view returns (bool) { 30 | return owner == _potentialOwner; 31 | } 32 | 33 | /// @dev Lets the owner transfer ownership of the contract to a new owner 34 | /// @param _newOwner The new owner 35 | function changeOwner(address _newOwner) external onlyOwner { 36 | require(_newOwner != address(0), "O: Address must not be null"); 37 | owner = _newOwner; 38 | emit OwnerChanged(_newOwner); 39 | } 40 | } -------------------------------------------------------------------------------- /contracts/ens/AuthereumENSManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./AuthereumEnsResolver.sol"; 4 | import "../base/Owned.sol"; 5 | import "../libs/strings.sol"; 6 | 7 | /** 8 | * @title AuthereumEnsManager 9 | * @author Authereum Labs, Inc. 10 | * @dev Used to manage all subdomains. 11 | * @dev This is also known as the Authereum registrar. 12 | * @dev The public ENS registry is used. The resolver is custom. 13 | */ 14 | 15 | contract AuthereumEnsManager is Owned { 16 | using strings for *; 17 | 18 | string constant public name = "Authereum ENS Manager"; 19 | string constant public version = "2020070100"; 20 | 21 | // namehash('addr.reverse') 22 | bytes32 constant public ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; 23 | address ensRegistry; 24 | 25 | // The managed root name 26 | string public rootName; 27 | // The managed root node 28 | bytes32 public rootNode; 29 | // The address of the authereumEnsResolver 30 | address public authereumEnsResolver; 31 | // The address of the Authereum factory 32 | address public authereumFactoryAddress; 33 | // A mapping of the runtimeCodeHash to creationCodeHash 34 | mapping(bytes32 => bytes32) public authereumProxyBytecodeHashMapping; 35 | 36 | event RootnodeOwnerChanged(bytes32 indexed rootnode, address indexed newOwner); 37 | event RootnodeResolverChanged(bytes32 indexed rootnode, address indexed newResolver); 38 | event RootnodeTTLChanged(bytes32 indexed rootnode, uint64 indexed newTtl); 39 | event RootnodeTextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value); 40 | event RootnodeContenthashChanged(bytes32 indexed node, bytes hash); 41 | event AuthereumEnsResolverChanged(address indexed authereumEnsResolver); 42 | event AuthereumFactoryAddressChanged(address indexed authereumFactoryAddress); 43 | event AuthereumProxyBytecodeHashChanged(bytes32 indexed authereumProxyRuntimeCodeHash, bytes32 indexed authereumProxyCreationCodeHash); 44 | event Registered(address indexed owner, string ens); 45 | 46 | /// @dev Throws if the sender is not the Authereum factory 47 | modifier onlyAuthereumFactory() { 48 | require(msg.sender == authereumFactoryAddress, "AEM: Must be sent from the authereumFactoryAddress"); 49 | _; 50 | } 51 | 52 | /// @dev Constructor that sets the ENS root name and root node to manage 53 | /// @param _rootName The root name (e.g. auth.eth) 54 | /// @param _rootNode The node of the root name (e.g. namehash(auth.eth)) 55 | /// @param _ensRegistry Public ENS Registry address 56 | /// @param _authereumEnsResolver Custom Authereum ENS Resolver address 57 | constructor( 58 | string memory _rootName, 59 | bytes32 _rootNode, 60 | address _ensRegistry, 61 | address _authereumEnsResolver 62 | ) 63 | public 64 | { 65 | rootName = _rootName; 66 | rootNode = _rootNode; 67 | ensRegistry = _ensRegistry; 68 | authereumEnsResolver = _authereumEnsResolver; 69 | } 70 | 71 | /** 72 | * Canonical ENS 73 | */ 74 | 75 | /// @dev Gets the official ENS registry 76 | /// @return The official ENS registry address 77 | function getEnsRegistry() public view returns (EnsRegistry) { 78 | return EnsRegistry(ensRegistry); 79 | } 80 | 81 | /// @dev Gets the official ENS reverse registrar 82 | /// @return The official ENS reverse registrar address 83 | function getEnsReverseRegistrar() public view returns (EnsReverseRegistrar) { 84 | return EnsReverseRegistrar(getEnsRegistry().owner(ADDR_REVERSE_NODE)); 85 | } 86 | 87 | /** 88 | * Rootnode - Registry 89 | */ 90 | 91 | /// @dev This function is used when the rootnode owner is updated 92 | /// @param _newOwner The address of the new ENS manager that will manage the root node 93 | function changeRootnodeOwner(address _newOwner) external onlyOwner { 94 | require(_newOwner != address(0), "AEM: Address must not be null"); 95 | getEnsRegistry().setOwner(rootNode, _newOwner); 96 | emit RootnodeOwnerChanged(rootNode, _newOwner); 97 | } 98 | 99 | /// @dev This function is used when the rootnode resolver is updated 100 | /// @param _newResolver The address of the new ENS Resolver that will manage the root node 101 | function changeRootnodeResolver(address _newResolver) external onlyOwner { 102 | require(_newResolver != address(0), "AEM: Address must not be null"); 103 | getEnsRegistry().setResolver(rootNode, _newResolver); 104 | emit RootnodeResolverChanged(rootNode, _newResolver); 105 | } 106 | 107 | /// @dev This function is used when the rootnode TTL is updated 108 | /// @param _newTtl The address of the new TTL that will manage the root node 109 | function changeRootnodeTTL(uint64 _newTtl) external onlyOwner { 110 | getEnsRegistry().setTTL(rootNode, _newTtl); 111 | emit RootnodeTTLChanged(rootNode, _newTtl); 112 | } 113 | 114 | /** 115 | * Rootnode - Resolver 116 | */ 117 | 118 | /// @dev This function is used when the rootnode text record is updated 119 | /// @param _newKey The key of the new text record for the root node 120 | /// @param _newValue The value of the new text record for the root node 121 | function changeRootnodeText(string calldata _newKey, string calldata _newValue) external onlyOwner { 122 | AuthereumEnsResolver(authereumEnsResolver).setText(rootNode, _newKey, _newValue); 123 | emit RootnodeTextChanged(rootNode, _newKey, _newKey, _newValue); 124 | } 125 | 126 | /// @dev This function is used when the rootnode contenthash is updated 127 | /// @param _newHash The new contenthash of the root node 128 | function changeRootnodeContenthash(bytes calldata _newHash) external onlyOwner { 129 | AuthereumEnsResolver(authereumEnsResolver).setContenthash(rootNode, _newHash); 130 | emit RootnodeContenthashChanged(rootNode, _newHash); 131 | } 132 | 133 | /** 134 | * State 135 | */ 136 | 137 | /// @dev Lets the owner change the address of the Authereum ENS resolver contract 138 | /// @param _authereumEnsResolver The address of the Authereum ENS resolver contract 139 | function changeAuthereumEnsResolver(address _authereumEnsResolver) external onlyOwner { 140 | require(_authereumEnsResolver != address(0), "AEM: Address must not be null"); 141 | authereumEnsResolver = _authereumEnsResolver; 142 | emit AuthereumEnsResolverChanged(_authereumEnsResolver); 143 | } 144 | 145 | /// @dev Lets the owner change the address of the Authereum factory 146 | /// @param _authereumFactoryAddress The address of the Authereum factory 147 | function changeAuthereumFactoryAddress(address _authereumFactoryAddress) external onlyOwner { 148 | require(_authereumFactoryAddress != address(0), "AEM: Address must not be null"); 149 | authereumFactoryAddress = _authereumFactoryAddress; 150 | emit AuthereumFactoryAddressChanged(authereumFactoryAddress); 151 | } 152 | 153 | /** 154 | * Register 155 | */ 156 | 157 | /// @dev Lets the manager assign an ENS subdomain of the root node to a target address 158 | /// @notice Registers both the forward and reverse ENS 159 | /// @param _label The subdomain label 160 | /// @param _owner The owner of the subdomain 161 | function register( 162 | string calldata _label, 163 | address _owner 164 | ) 165 | external 166 | onlyAuthereumFactory 167 | { 168 | bytes32 labelNode = keccak256(abi.encodePacked(_label)); 169 | bytes32 node = keccak256(abi.encodePacked(rootNode, labelNode)); 170 | address currentOwner = getEnsRegistry().owner(node); 171 | require(currentOwner == address(0), "AEM: Label is already owned"); 172 | 173 | // Forward ENS 174 | getEnsRegistry().setSubnodeOwner(rootNode, labelNode, address(this)); 175 | getEnsRegistry().setResolver(node, authereumEnsResolver); 176 | getEnsRegistry().setOwner(node, _owner); 177 | AuthereumEnsResolver(authereumEnsResolver).setAddr(node, _owner); 178 | 179 | // Reverse ENS 180 | strings.slice[] memory parts = new strings.slice[](2); 181 | parts[0] = _label.toSlice(); 182 | parts[1] = rootName.toSlice(); 183 | string memory _name = ".".toSlice().join(parts); 184 | bytes32 reverseNode = EnsReverseRegistrar(getEnsReverseRegistrar()).node(_owner); 185 | AuthereumEnsResolver(authereumEnsResolver).setName(reverseNode, _name); 186 | 187 | emit Registered(_owner, _name); 188 | } 189 | 190 | /** 191 | * Public functions 192 | */ 193 | 194 | /// @dev Returns true is a given subnode is available 195 | /// @param _subnode The target subnode 196 | /// @return True if the subnode is available 197 | function isAvailable(bytes32 _subnode) public view returns (bool) { 198 | bytes32 node = keccak256(abi.encodePacked(rootNode, _subnode)); 199 | address currentOwner = getEnsRegistry().owner(node); 200 | if(currentOwner == address(0)) { 201 | return true; 202 | } 203 | return false; 204 | } 205 | } -------------------------------------------------------------------------------- /contracts/ens/AuthereumENSResolver.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../base/Managed.sol"; 4 | import "./state/AuthereumEnsResolverState.sol"; 5 | 6 | /** 7 | * @title AuthereumEnsResolver 8 | * @author Authereum Labs, Inc. 9 | * @dev Authereum implementation of a Resolver. 10 | */ 11 | 12 | contract AuthereumEnsResolver is Managed, AuthereumEnsResolverState { 13 | 14 | // This contract does not have a `name` variable because that namespace is already 15 | // used for the function below. 16 | string constant public version = "2020070100"; 17 | 18 | bytes4 constant private INTERFACE_META_ID = 0x01ffc9a7; 19 | bytes4 constant private ADDR_INTERFACE_ID = 0x3b3b57de; 20 | bytes4 constant private NAME_INTERFACE_ID = 0x691f3431; 21 | bytes4 constant private TEXT_INTERFACE_ID = 0x59d1d43c; 22 | bytes4 constant private CONTENT_HASH_INTERFACE_ID = 0xbc1c58d1; 23 | 24 | event AddrChanged(bytes32 indexed node, address a); 25 | event NameChanged(bytes32 indexed node, string name); 26 | event TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value); 27 | event ContenthashChanged(bytes32 indexed node, bytes hash); 28 | 29 | /// @dev Constructor 30 | /// @param _ensAddr The ENS registrar contract 31 | constructor(EnsRegistry _ensAddr) public { 32 | ens = _ensAddr; 33 | } 34 | 35 | /** 36 | * Setters 37 | */ 38 | 39 | /// @dev Sets the address associated with an ENS node 40 | /// @notice May only be called by the owner of that node in the ENS registry 41 | /// @param _node The node to update 42 | /// @param _addr The address to set 43 | function setAddr(bytes32 _node, address _addr) public onlyManager { 44 | addrs[_node]= _addr; 45 | emit AddrChanged(_node, _addr); 46 | } 47 | 48 | /// @dev Sets the name associated with an ENS node, for reverse records 49 | /// @notice May only be called by the owner of that node in the ENS registry 50 | /// @param _node The node to update 51 | /// @param _name The name to set 52 | function setName(bytes32 _node, string memory _name) public onlyManager { 53 | names[_node] = _name; 54 | emit NameChanged(_node, _name); 55 | } 56 | 57 | /// @dev Sets the text data associated with an ENS node and key 58 | /// @notice May only be called by the owner of that node in the ENS registry 59 | /// @param node The node to update 60 | /// @param key The key to set 61 | /// @param value The text data value to set 62 | function setText(bytes32 node, string memory key, string memory value) public onlyManager { 63 | texts[node][key] = value; 64 | emit TextChanged(node, key, key, value); 65 | } 66 | 67 | /// @dev Sets the contenthash associated with an ENS node 68 | /// @notice May only be called by the owner of that node in the ENS registry 69 | /// @param node The node to update 70 | /// @param hash The contenthash to set 71 | function setContenthash(bytes32 node, bytes memory hash) public onlyManager { 72 | hashes[node] = hash; 73 | emit ContenthashChanged(node, hash); 74 | } 75 | 76 | /** 77 | * Getters 78 | */ 79 | 80 | /// @dev Returns the address associated with an ENS node 81 | /// @param _node The ENS node to query 82 | /// @return The associated address 83 | function addr(bytes32 _node) public view returns (address) { 84 | return addrs[_node]; 85 | } 86 | 87 | /// @dev Returns the name associated with an ENS node, for reverse records 88 | /// @notice Defined in EIP181 89 | /// @param _node The ENS node to query 90 | /// @return The associated name 91 | function name(bytes32 _node) public view returns (string memory) { 92 | return names[_node]; 93 | } 94 | 95 | /// @dev Returns the text data associated with an ENS node and key 96 | /// @param node The ENS node to query 97 | /// @param key The text data key to query 98 | ///@return The associated text data 99 | function text(bytes32 node, string memory key) public view returns (string memory) { 100 | return texts[node][key]; 101 | } 102 | 103 | /// @dev Returns the contenthash associated with an ENS node 104 | /// @param node The ENS node to query 105 | /// @return The associated contenthash 106 | function contenthash(bytes32 node) public view returns (bytes memory) { 107 | return hashes[node]; 108 | } 109 | 110 | /// @dev Returns true if the resolver implements the interface specified by the provided hash 111 | /// @param _interfaceID The ID of the interface to check for 112 | /// @return True if the contract implements the requested interface 113 | function supportsInterface(bytes4 _interfaceID) public pure returns (bool) { 114 | return _interfaceID == INTERFACE_META_ID || 115 | _interfaceID == ADDR_INTERFACE_ID || 116 | _interfaceID == NAME_INTERFACE_ID || 117 | _interfaceID == TEXT_INTERFACE_ID || 118 | _interfaceID == CONTENT_HASH_INTERFACE_ID; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /contracts/ens/state/AuthereumEnsResolverState.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./AuthereumEnsResolverStateV1.sol"; 4 | 5 | /** 6 | * @title AuthereumEnsResolverState 7 | * @author Authereum Labs, Inc. 8 | * @dev This contract holds the state variables used by the Authereum ENS Resolver. 9 | * @dev This exists as the main contract to hold state. This contract is inherited 10 | * @dev by AuthereumEnsResolver.sol, which will not care about state as long as it inherits 11 | * @dev AuthereumEnsResolverState.sol. Any state variable additions will be made to the various 12 | * @dev versions of AuthereumEnsResolverStateVX that this contract will inherit. 13 | */ 14 | 15 | contract AuthereumEnsResolverState is AuthereumEnsResolverStateV1 {} -------------------------------------------------------------------------------- /contracts/ens/state/AuthereumEnsResolverStateV1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../../test/EnsReverseRegistrar.sol"; 4 | 5 | /** 6 | * @title AuthereumEnsResolverStateV1 7 | * @author Authereum Labs, Inc. 8 | * @dev This contract holds the state variables used by the Authereum ENS Resolver. 9 | * @dev This abstraction exists in order to retain the order of the state variables. 10 | */ 11 | 12 | contract AuthereumEnsResolverStateV1 { 13 | 14 | EnsRegistry ens; 15 | 16 | mapping(bytes32 => address) public addrs; 17 | mapping(bytes32 => string) public names; 18 | mapping(bytes32 => mapping(string => string)) public texts; 19 | mapping(bytes32 => bytes) public hashes; 20 | } -------------------------------------------------------------------------------- /contracts/firewall/TransactionLimit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../account/BaseAccount.sol"; 5 | 6 | /** 7 | * @title TransactionLimit 8 | * @author Authereum Labs, Inc. 9 | * @dev Used to limit login key's transaction limits. This contract handles all 10 | * @dev functionality related to daily limits for login keys. 11 | */ 12 | 13 | contract TransactionLimit is BaseAccount { 14 | 15 | uint256 constant defaultDailyLimit = 10 ether; 16 | 17 | 18 | // NOTE: This state and events will be included in the state and event 19 | // NOTE: account files if they are ever included in an upgrade 20 | uint256 public dailyLimit; 21 | mapping(uint256 => uint256) public dailyLimitTracker; 22 | 23 | event DailySpendIncreased(uint256 indexed day, uint256 indexed spendIncrease); 24 | event DailyLimitChanged(address indexed authKey, uint256 indexed newDailyLimit); 25 | 26 | 27 | /** 28 | * Getters 29 | */ 30 | 31 | /// @dev Gets the current day for the contract 32 | /// @return Current day 33 | function getCurrentDay() public view returns (uint256) { 34 | return block.timestamp / 86400; 35 | } 36 | 37 | /// @dev Check if a user is within their daily limit 38 | /// @return True if transaction will be within the daily limit 39 | function getIsWithinEthDailyTransactionLimit() public view returns (bool) { 40 | return getWillBeWithinEthDailyTransactionLimit(0); 41 | } 42 | 43 | /// @dev Check if a user will be within their daily limit after a transaction 44 | /// @param _value Value being sent with the current transaction 45 | /// @return True if transaction will be within the daily limit 46 | function getWillBeWithinEthDailyTransactionLimit(uint256 _value) public view returns (bool) { 47 | uint256 currentDay = getCurrentDay(); 48 | uint256 dailySpend = dailyLimitTracker[currentDay] + _value; 49 | if (dailySpend <= dailyLimit) { 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | /** 56 | * Setters 57 | */ 58 | 59 | /// @dev Change the daily limit for a user 60 | /// @param _newDailyLimit New daily limit to set 61 | function changeDailyLimit(uint256 _newDailyLimit) public onlyAuthKeyOrSelf { 62 | dailyLimit = _newDailyLimit; 63 | emit DailyLimitChanged(msg.sender, dailyLimit); 64 | } 65 | 66 | /** 67 | * Internal functions 68 | */ 69 | 70 | /// @dev Update the tracked balance for daily limit for a user 71 | /// @param _value Value being sent with the current transaction 72 | function updateEthDailyTransactionLimit(uint256 _value) internal { 73 | // Do not update anything if there is no value associated with the transaction 74 | if (_value == 0) return; 75 | 76 | dailyLimitTracker[getCurrentDay()] += _value; 77 | emit DailySpendIncreased(getCurrentDay(), _value); 78 | } 79 | } -------------------------------------------------------------------------------- /contracts/interfaces/IAuthereumAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import './IERC1271.sol'; 5 | import './IERC721Receiver.sol'; 6 | import './IERC1155TokenReceiver.sol'; 7 | import './IERC1820ImplementerInterface.sol'; 8 | import './IERC777TokensRecipient.sol'; 9 | 10 | contract IAuthereumAccount is IERC1271, IERC721Receiver, IERC1155TokenReceiver, IERC1820ImplementerInterface, IERC777TokensRecipient { 11 | function () external payable; 12 | function name() external view returns (string memory); 13 | function version() external view returns (string memory); 14 | function implementation() external view returns (address); 15 | function lastInitializedVersion() external returns (uint256); 16 | function authKeys(address _authKey) external returns (bool); 17 | function nonce() external returns (uint256); 18 | function numAuthKeys() external returns (uint256); 19 | function getChainId() external pure returns (uint256); 20 | function addAuthKey(address _authKey) external; 21 | function upgradeToAndCall(address _newImplementation, bytes calldata _data) external; 22 | function removeAuthKey(address _authKey) external; 23 | function isValidAuthKeySignature(bytes32 _messageHash, bytes calldata _signature) external view returns (bool); 24 | function isValidLoginKeySignature(bytes32 _messageHash, bytes calldata _signature) external view returns (bool); 25 | function executeMultipleTransactions(bytes[] calldata _transactions) external returns (bytes[] memory); 26 | function executeMultipleMetaTransactions(bytes[] calldata _transactions) external returns (bytes[] memory); 27 | 28 | function executeMultipleAuthKeyMetaTransactions( 29 | bytes[] calldata _transactions, 30 | uint256 _gasPrice, 31 | uint256 _gasOverhead, 32 | address _feeTokenAddress, 33 | uint256 _feeTokenRate, 34 | bytes calldata _transactionMessageHashSignature 35 | ) external returns (bytes[] memory); 36 | 37 | function executeMultipleLoginKeyMetaTransactions( 38 | bytes[] calldata _transactions, 39 | uint256 _gasPrice, 40 | uint256 _gasOverhead, 41 | bytes calldata _loginKeyRestrictionsData, 42 | address _feeTokenAddress, 43 | uint256 _feeTokenRate, 44 | bytes calldata _transactionMessageHashSignature, 45 | bytes calldata _loginKeyAttestationSignature 46 | ) external returns (bytes[] memory); 47 | } 48 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC1155TokenReceiver.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | Note: The ERC-165 identifier for this interface is 0x4e2312e0. 5 | */ 6 | interface IERC1155TokenReceiver { 7 | /** 8 | @notice Handle the receipt of a single ERC1155 token type. 9 | @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. 10 | This function MUST return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` (i.e. 0xf23a6e61) if it accepts the transfer. 11 | This function MUST revert if it rejects the transfer. 12 | Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. 13 | @param _operator The address which initiated the transfer (i.e. msg.sender) 14 | @param _from The address which previously owned the token 15 | @param _id The ID of the token being transferred 16 | @param _value The amount of tokens being transferred 17 | @param _data Additional data with no specified format 18 | @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` 19 | */ 20 | function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4); 21 | 22 | /** 23 | @notice Handle the receipt of multiple ERC1155 token types. 24 | @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. 25 | This function MUST return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` (i.e. 0xbc197c81) if it accepts the transfer(s). 26 | This function MUST revert if it rejects the transfer(s). 27 | Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. 28 | @param _operator The address which initiated the batch transfer (i.e. msg.sender) 29 | @param _from The address which previously owned the token 30 | @param _ids An array containing ids of each token being transferred (order and length must match _values array) 31 | @param _values An array containing amounts of each token being transferred (order and length must match _ids array) 32 | @param _data Additional data with no specified format 33 | @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` 34 | */ 35 | function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4); 36 | } 37 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC1271.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract IERC1271 { 4 | function isValidSignature( 5 | bytes32 _messageHash, 6 | bytes memory _signature 7 | ) public view returns (bytes4 magicValue); 8 | 9 | function isValidSignature( 10 | bytes memory _data, 11 | bytes memory _signature 12 | ) public view returns (bytes4 magicValue); 13 | } -------------------------------------------------------------------------------- /contracts/interfaces/IERC1820ImplementerInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | interface IERC1820ImplementerInterface { 4 | /// @notice Indicates whether the contract implements the interface `interfaceHash` for the address `addr` or not. 5 | /// @param interfaceHash keccak256 hash of the name of the interface 6 | /// @param addr Address for which the contract will implement the interface 7 | /// @return ERC1820_ACCEPT_MAGIC only if the contract implements `interfaceHash` for the address `addr`. 8 | function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC1820Registry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @dev Interface of the global ERC1820 Registry, as defined in the 5 | * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register 6 | * implementers for interfaces in this registry, as well as query support. 7 | * 8 | * Implementers may be shared by multiple accounts, and can also implement more 9 | * than a single interface for each account. Contracts can implement interfaces 10 | * for themselves, but externally-owned accounts (EOA) must delegate this to a 11 | * contract. 12 | * 13 | * {IERC165} interfaces can also be queried via the registry. 14 | * 15 | * For an in-depth explanation and source code analysis, see the EIP text. 16 | */ 17 | interface IERC1820Registry { 18 | /** 19 | * @dev Sets `newManager` as the manager for `account`. A manager of an 20 | * account is able to set interface implementers for it. 21 | * 22 | * By default, each account is its own manager. Passing a value of `0x0` in 23 | * `newManager` will reset the manager to this initial state. 24 | * 25 | * Emits a {ManagerChanged} event. 26 | * 27 | * Requirements: 28 | * 29 | * - the caller must be the current manager for `account`. 30 | */ 31 | function setManager(address account, address newManager) external; 32 | 33 | /** 34 | * @dev Returns the manager for `account`. 35 | * 36 | * See {setManager}. 37 | */ 38 | function getManager(address account) external view returns (address); 39 | 40 | /** 41 | * @dev Sets the `implementer` contract as ``account``'s implementer for 42 | * `interfaceHash`. 43 | * 44 | * `account` being the zero address is an alias for the caller's address. 45 | * The zero address can also be used in `implementer` to remove an old one. 46 | * 47 | * See {interfaceHash} to learn how these are created. 48 | * 49 | * Emits an {InterfaceImplementerSet} event. 50 | * 51 | * Requirements: 52 | * 53 | * - the caller must be the current manager for `account`. 54 | * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not 55 | * end in 28 zeroes). 56 | * - `implementer` must implement {IERC1820Implementer} and return true when 57 | * queried for support, unless `implementer` is the caller. See 58 | * {IERC1820Implementer-canImplementInterfaceForAddress}. 59 | */ 60 | function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external; 61 | 62 | /** 63 | * @dev Returns the implementer of `interfaceHash` for `account`. If no such 64 | * implementer is registered, returns the zero address. 65 | * 66 | * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28 67 | * zeroes), `account` will be queried for support of it. 68 | * 69 | * `account` being the zero address is an alias for the caller's address. 70 | */ 71 | function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address); 72 | 73 | /** 74 | * @dev Returns the interface hash for an `interfaceName`, as defined in the 75 | * corresponding 76 | * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP]. 77 | */ 78 | function interfaceHash(string calldata interfaceName) external pure returns (bytes32); 79 | 80 | /** 81 | * @notice Updates the cache with whether the contract implements an ERC165 interface or not. 82 | * @param account Address of the contract for which to update the cache. 83 | * @param interfaceId ERC165 interface for which to update the cache. 84 | */ 85 | function updateERC165Cache(address account, bytes4 interfaceId) external; 86 | 87 | /** 88 | * @notice Checks whether a contract implements an ERC165 interface or not. 89 | * If the result is not cached a direct lookup on the contract address is performed. 90 | * If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling 91 | * {updateERC165Cache} with the contract address. 92 | * @param account Address of the contract to check. 93 | * @param interfaceId ERC165 interface to check. 94 | * @return True if `account` implements `interfaceId`, false otherwise. 95 | */ 96 | function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool); 97 | 98 | /** 99 | * @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache. 100 | * @param account Address of the contract to check. 101 | * @param interfaceId ERC165 interface to check. 102 | * @return True if `account` implements `interfaceId`, false otherwise. 103 | */ 104 | function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool); 105 | 106 | event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer); 107 | 108 | event ManagerChanged(address indexed account, address indexed newManager); 109 | } -------------------------------------------------------------------------------- /contracts/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract IERC20 { 4 | function balanceOf(address account) external returns (uint256); 5 | function transfer(address recipient, uint256 amount) external returns (bool); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC721Receiver.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @title ERC721 token receiver interface 5 | * @dev Interface for any contract that wants to support safeTransfers 6 | * from ERC721 asset contracts. 7 | * 8 | * This contract is from openzeppelin-solidity 2.4.0 9 | */ 10 | contract IERC721Receiver { 11 | /** 12 | * @notice Handle the receipt of an NFT 13 | * @dev The ERC721 smart contract calls this function on the recipient 14 | * after a {IERC721-safeTransferFrom}. This function MUST return the function selector, 15 | * otherwise the caller will revert the transaction. The selector to be 16 | * returned can be obtained as `this.onERC721Received.selector`. This 17 | * function MAY throw to revert and reject the transfer. 18 | * Note: the ERC721 contract address is always the message sender. 19 | * @param operator The address which called `safeTransferFrom` function 20 | * @param from The address which previously owned the token 21 | * @param tokenId The NFT identifier which is being transferred 22 | * @param data Additional data with no specified format 23 | * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` 24 | */ 25 | function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4); 26 | } 27 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC777TokensRecipient.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract IERC777TokensRecipient { 4 | /// @dev Notify a send or mint (if from is 0x0) of amount tokens from the from address to the 5 | /// to address by the operator address. 6 | /// @param operator Address which triggered the balance increase (through sending or minting). 7 | /// @param from Holder whose tokens were sent (or 0x0 for a mint). 8 | /// @param to Recipient of the tokens. 9 | /// @param amount Number of tokens the recipient balance is increased by. 10 | /// @param data Information provided by the holder. 11 | /// @param operatorData Information provided by the operator. 12 | function tokensReceived( 13 | address operator, 14 | address from, 15 | address to, 16 | uint256 amount, 17 | bytes calldata data, 18 | bytes calldata operatorData 19 | ) external; 20 | } 21 | -------------------------------------------------------------------------------- /contracts/interfaces/ILoginKeyTransactionValidator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | interface ILoginKeyTransactionValidator { 5 | /// @dev Reverts if the transaction is invalid 6 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 7 | /// @param _validationData Data used by the LoginKeyTransactionValidator and is signed in the 8 | /// login key attestation 9 | /// @param _relayerAddress Address that called the account contract 10 | function validateTransactions( 11 | bytes[] calldata _transactions, 12 | bytes calldata _validationData, 13 | address _relayerAddress 14 | ) external; 15 | 16 | /// @dev Called after a transaction is executed to record information about the transaction 17 | /// and perform any post-execution validation 18 | /// @param _transactions Arrays of transaction data ([to, value, gasLimit, data],[...],...) 19 | /// @param _validationData Data used by the LoginKeyTransactionValidator and is signed in the 20 | /// login key attestation 21 | /// @param _relayerAddress Address that called the account contract 22 | function transactionsDidExecute( 23 | bytes[] calldata _transactions, 24 | bytes calldata _validationData, 25 | address _relayerAddress 26 | ) external; 27 | } -------------------------------------------------------------------------------- /contracts/libs/Address.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * Utility library of inline functions on addresses 5 | * 6 | * Source https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/v2.1.3/contracts/utils/Address.sol 7 | * This contract is copied here and renamed from the original to avoid clashes in the compiled artifacts 8 | * when the user imports a zos-lib contract (that transitively causes this contract to be compiled and added to the 9 | * build/artifacts folder) as well as the vanilla Address implementation from an openzeppelin version. 10 | */ 11 | library OpenZeppelinUpgradesAddress { 12 | /** 13 | * Returns whether the target address is a contract 14 | * @dev This function will return false if invoked during the constructor of a contract, 15 | * as the code is not actually created until after the constructor finishes. 16 | * @param account address of the account to check 17 | * @return whether the target address is a contract 18 | */ 19 | function isContract(address account) internal view returns (bool) { 20 | uint256 size; 21 | // XXX Currently there is no better way to check if there is a contract in an address 22 | // than to check the size of the code at that address. 23 | // See https://ethereum.stackexchange.com/a/14016/36603 24 | // for more details about how this works. 25 | // TODO Check this again before the Serenity release, because all addresses will be 26 | // contracts then. 27 | // solhint-disable-next-line no-inline-assembly 28 | assembly { size := extcodesize(account) } 29 | return size > 0; 30 | } 31 | } -------------------------------------------------------------------------------- /contracts/libs/ECDSA.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. 5 | * 6 | * These functions can be used to verify that a message was signed by the holder 7 | * of the private keys of a given address. 8 | * 9 | * This contract is from openzeppelin-solidity 2.4.0 10 | */ 11 | library ECDSA { 12 | /** 13 | * @dev Returns the address that signed a hashed message (`hash`) with 14 | * `signature`. This address can then be used for verification purposes. 15 | * 16 | * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: 17 | * this function rejects them by requiring the `s` value to be in the lower 18 | * half order, and the `v` value to be either 27 or 28. 19 | * 20 | * NOTE: This call _does not revert_ if the signature is invalid, or 21 | * if the signer is otherwise unable to be retrieved. In those scenarios, 22 | * the zero address is returned. 23 | * 24 | * IMPORTANT: `hash` _must_ be the result of a hash operation for the 25 | * verification to be secure: it is possible to craft signatures that 26 | * recover to arbitrary addresses for non-hashed data. A safe way to ensure 27 | * this is by receiving a hash of the original message (which may otherwise 28 | * be too long), and then calling {toEthSignedMessageHash} on it. 29 | */ 30 | function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { 31 | // Check the signature length 32 | if (signature.length != 65) { 33 | return (address(0)); 34 | } 35 | 36 | // Divide the signature in r, s and v variables 37 | bytes32 r; 38 | bytes32 s; 39 | uint8 v; 40 | 41 | // ecrecover takes the signature parameters, and the only way to get them 42 | // currently is to use assembly. 43 | // solhint-disable-next-line no-inline-assembly 44 | assembly { 45 | r := mload(add(signature, 0x20)) 46 | s := mload(add(signature, 0x40)) 47 | v := byte(0, mload(add(signature, 0x60))) 48 | } 49 | 50 | // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature 51 | // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines 52 | // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most 53 | // signatures from current libraries generate a unique signature with an s-value in the lower half order. 54 | // 55 | // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value 56 | // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or 57 | // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept 58 | // these malleable signatures as well. 59 | if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { 60 | return address(0); 61 | } 62 | 63 | if (v != 27 && v != 28) { 64 | return address(0); 65 | } 66 | 67 | // If the signature is valid (and not malleable), return the signer address 68 | return ecrecover(hash, v, r, s); 69 | } 70 | 71 | /** 72 | * @dev Returns an Ethereum Signed Message, created from a `hash`. This 73 | * replicates the behavior of the 74 | * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`] 75 | * JSON-RPC method. 76 | * 77 | * See {recover}. 78 | */ 79 | function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { 80 | // 32 is the length in bytes of hash, 81 | // enforced by the type signature above 82 | return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /contracts/libs/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 5 | * checks. 6 | * 7 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 8 | * in bugs, because programmers usually assume that an overflow raises an 9 | * error, which is the standard behavior in high level programming languages. 10 | * `SafeMath` restores this intuition by reverting the transaction when an 11 | * operation overflows. 12 | * 13 | * Using this library instead of the unchecked operations eliminates an entire 14 | * class of bugs, so it's recommended to use it always. 15 | * 16 | * This contract is from openzeppelin-solidity 2.4.0 17 | */ 18 | library SafeMath { 19 | /** 20 | * @dev Returns the addition of two unsigned integers, reverting on 21 | * overflow. 22 | * 23 | * Counterpart to Solidity's `+` operator. 24 | * 25 | * Requirements: 26 | * - Addition cannot overflow. 27 | */ 28 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 29 | uint256 c = a + b; 30 | require(c >= a, "SafeMath: addition overflow"); 31 | 32 | return c; 33 | } 34 | 35 | /** 36 | * @dev Returns the subtraction of two unsigned integers, reverting on 37 | * overflow (when the result is negative). 38 | * 39 | * Counterpart to Solidity's `-` operator. 40 | * 41 | * Requirements: 42 | * - Subtraction cannot overflow. 43 | */ 44 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 45 | return sub(a, b, "SafeMath: subtraction overflow"); 46 | } 47 | 48 | /** 49 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 50 | * overflow (when the result is negative). 51 | * 52 | * Counterpart to Solidity's `-` operator. 53 | * 54 | * Requirements: 55 | * - Subtraction cannot overflow. 56 | * 57 | * _Available since v2.4.0._ 58 | */ 59 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 60 | require(b <= a, errorMessage); 61 | uint256 c = a - b; 62 | 63 | return c; 64 | } 65 | 66 | /** 67 | * @dev Returns the multiplication of two unsigned integers, reverting on 68 | * overflow. 69 | * 70 | * Counterpart to Solidity's `*` operator. 71 | * 72 | * Requirements: 73 | * - Multiplication cannot overflow. 74 | */ 75 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 76 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 77 | // benefit is lost if 'b' is also tested. 78 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 79 | if (a == 0) { 80 | return 0; 81 | } 82 | 83 | uint256 c = a * b; 84 | require(c / a == b, "SafeMath: multiplication overflow"); 85 | 86 | return c; 87 | } 88 | 89 | /** 90 | * @dev Returns the integer division of two unsigned integers. Reverts on 91 | * division by zero. The result is rounded towards zero. 92 | * 93 | * Counterpart to Solidity's `/` operator. Note: this function uses a 94 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 95 | * uses an invalid opcode to revert (consuming all remaining gas). 96 | * 97 | * Requirements: 98 | * - The divisor cannot be zero. 99 | */ 100 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 101 | return div(a, b, "SafeMath: division by zero"); 102 | } 103 | 104 | /** 105 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on 106 | * division by zero. The result is rounded towards zero. 107 | * 108 | * Counterpart to Solidity's `/` operator. Note: this function uses a 109 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 110 | * uses an invalid opcode to revert (consuming all remaining gas). 111 | * 112 | * Requirements: 113 | * - The divisor cannot be zero. 114 | * 115 | * _Available since v2.4.0._ 116 | */ 117 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 118 | // Solidity only automatically asserts when dividing by 0 119 | require(b > 0, errorMessage); 120 | uint256 c = a / b; 121 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 122 | 123 | return c; 124 | } 125 | 126 | /** 127 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 128 | * Reverts when dividing by zero. 129 | * 130 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 131 | * opcode (which leaves remaining gas untouched) while Solidity uses an 132 | * invalid opcode to revert (consuming all remaining gas). 133 | * 134 | * Requirements: 135 | * - The divisor cannot be zero. 136 | */ 137 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 138 | return mod(a, b, "SafeMath: modulo by zero"); 139 | } 140 | 141 | /** 142 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 143 | * Reverts with custom message when dividing by zero. 144 | * 145 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 146 | * opcode (which leaves remaining gas untouched) while Solidity uses an 147 | * invalid opcode to revert (consuming all remaining gas). 148 | * 149 | * Requirements: 150 | * - The divisor cannot be zero. 151 | * 152 | * _Available since v2.4.0._ 153 | */ 154 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 155 | require(b != 0, errorMessage); 156 | return a % b; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /contracts/modules/AuthereumDelegateKeyModule.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../interfaces/IAuthereumAccount.sol"; 5 | import "../libs/BytesLib.sol"; 6 | import "../libs/SafeMath.sol"; 7 | 8 | /** 9 | * @title AuthereumDelegateKeyModule 10 | * @author Authereum Labs, Inc. 11 | * @dev This contract allows specific transactions to be delegated to a third party key 12 | * @dev This contract may be added as a module for an Authereum account. The Authereum account can 13 | * register a third party key to call a specific function signature on a specific address with a 14 | * maximum amount of ETH included with the transaction. Once added, the Delegate Key can call 15 | * its specified function an unlimited amount of times until it is removed. 16 | * @notice The AuthereumDelegateKeyModule does not enforce that the length of _lockedParameters and 17 | * _lockedParameterValues are equal to the actual number of parameters taken by the 18 | * function being registered. In addition, dynamically-sized parameters cannot be locked 19 | * but this is not enforced on-chain. When registering a Delegate Key, care should be taken 20 | * to ensure that _lockedParameters and _lockedParameterValues equal the number of 21 | * parameters in the function being registered and that none of the parameters being locked 22 | * are dynamically-sized. 23 | */ 24 | 25 | contract AuthereumDelegateKeyModule { 26 | using SafeMath for uint256; 27 | using BytesLib for bytes; 28 | 29 | string constant public name = "Authereum Delegate Key Module"; 30 | string constant public version = "2020070100"; 31 | 32 | /** 33 | * Events 34 | */ 35 | 36 | event DelegateKeyAdded( 37 | address indexed authereumAccount, 38 | address indexed delegateKeyAddress, 39 | bytes4 approvedFunctionSelector, 40 | address indexed approvedDestination, 41 | uint256 maxValue 42 | ); 43 | 44 | event DelegateKeyRemoved( 45 | address indexed authereumAccount, 46 | address indexed delegateKeyAddress 47 | ); 48 | 49 | event TransactionExecuted( 50 | address indexed authereumAccount, 51 | address indexed delegateKey, 52 | uint256 indexed value, 53 | bytes data 54 | ); 55 | 56 | /** 57 | * State 58 | */ 59 | 60 | struct DelegateKey { 61 | bool active; 62 | bytes4 approvedFunctionSelector; 63 | address approvedDestination; 64 | uint256 maxValue; 65 | bool[] lockedParameters; 66 | bytes32[] lockedParameterValues; 67 | } 68 | 69 | mapping(address => mapping(address => DelegateKey)) public delegateKeys; 70 | 71 | /** 72 | * Modifiers 73 | */ 74 | 75 | modifier onlyActiveDelegateKey(address _authereumAccount) { 76 | DelegateKey memory delegateKey = delegateKeys[_authereumAccount][msg.sender]; 77 | require(delegateKey.active == true, "ADKM: Delegate Key is not active"); 78 | _; 79 | } 80 | 81 | modifier onlyWhenRegisteredModule { 82 | require( 83 | IAuthereumAccount(msg.sender).authKeys(address(this)), 84 | "ADKM: Delegate Key module not registered to account" 85 | ); 86 | _; 87 | } 88 | 89 | /** 90 | * Public functions 91 | */ 92 | 93 | /// @dev Adds a Delegate Key 94 | /// @dev Called by the Authereum account 95 | /// @dev The length of _lockedParameters and _lockedParameterValues should equal the number of 96 | /// parameters in the approved function signature. 97 | /// @dev Dynamic parameters cannot be locked 98 | /// @param _delegateKeyAddress Address of the Delegate Key 99 | /// @param _approvedFunctionSelector The function selector that the Delegate Key can call on the 100 | /// approved contract 101 | /// @param _approvedDestination The address that the Delegate Key can call 102 | /// @param _maxValue The maximum value that can be transferred in each transaction 103 | /// @param _lockedParameters An array of booleans specifying which parameters should be locked 104 | /// @param _lockedParameterValues An array of values that locked parameters should be locked to 105 | function addDelegateKey( 106 | address _delegateKeyAddress, 107 | bytes4 _approvedFunctionSelector, 108 | address _approvedDestination, 109 | uint256 _maxValue, 110 | bool[] calldata _lockedParameters, 111 | bytes32[] calldata _lockedParameterValues 112 | ) 113 | external 114 | onlyWhenRegisteredModule 115 | { 116 | require(_delegateKeyAddress != address(0), "ADKM: Delegate Key cannot be address(0)"); 117 | require(delegateKeys[msg.sender][_delegateKeyAddress].active != true, "ADKM: Delegate Key is already registered"); 118 | require( 119 | _lockedParameters.length == _lockedParameterValues.length, 120 | "ADKM: lockedParameters must be the same length as lockedParameterValues" 121 | ); 122 | 123 | delegateKeys[msg.sender][_delegateKeyAddress] = DelegateKey( 124 | true, 125 | _approvedFunctionSelector, 126 | _approvedDestination, 127 | _maxValue, 128 | _lockedParameters, 129 | _lockedParameterValues 130 | ); 131 | 132 | emit DelegateKeyAdded( 133 | msg.sender, 134 | _delegateKeyAddress, 135 | _approvedFunctionSelector, 136 | _approvedDestination, 137 | _maxValue 138 | ); 139 | } 140 | 141 | /// @dev Removes the Delegate Key 142 | /// @dev Called by the Authereum account 143 | /// @param _delegateKeyAddress Address of the Delegate Key 144 | function removeDelegateKey(address _delegateKeyAddress) external { 145 | DelegateKey memory delegateKey = delegateKeys[msg.sender][_delegateKeyAddress]; 146 | require(delegateKey.active == true, "ADKM: Delegate Key is not active"); 147 | 148 | delete delegateKeys[msg.sender][_delegateKeyAddress]; 149 | 150 | emit DelegateKeyRemoved(msg.sender, _delegateKeyAddress); 151 | } 152 | 153 | /// @dev Validates and then executes a transaction with the Authereum account 154 | /// @dev Called by the Delegate Key 155 | /// @param _authereumAccount Address of the Authereum account that the Delegate Key is making 156 | /// a transaction for 157 | /// @param _value Value of the transaction 158 | /// @param _data The calldata of the transaction made by the Authereum account 159 | /// @return Return values of the executed transaction 160 | function executeTransaction( 161 | address payable _authereumAccount, 162 | uint256 _value, 163 | bytes calldata _data 164 | ) 165 | external 166 | onlyActiveDelegateKey(_authereumAccount) 167 | returns (bytes[] memory) 168 | { 169 | DelegateKey memory delegateKey = delegateKeys[_authereumAccount][msg.sender]; 170 | 171 | // Validate value 172 | require(_value <= delegateKey.maxValue, "ADKM: Value is higher than maximum allowed value"); 173 | 174 | _validateCalldata(delegateKey, _data); 175 | 176 | return _executeTransaction( 177 | _authereumAccount, 178 | delegateKey.approvedDestination, 179 | _value, 180 | gasleft(), 181 | _data 182 | ); 183 | } 184 | 185 | /** 186 | * Private functions 187 | */ 188 | 189 | function _validateCalldata(DelegateKey memory _delegateKey, bytes memory _data) private pure { 190 | // If approvedFunctionSelector is 0x, no data can be included 191 | if (_delegateKey.approvedFunctionSelector == bytes4(0)) { 192 | require(_data.length == 0); 193 | return; 194 | } 195 | 196 | bool[] memory lockedParameters = _delegateKey.lockedParameters; 197 | bytes32[] memory lockedParameterValues = _delegateKey.lockedParameterValues; 198 | (bytes4 functionSelector, bytes32[] memory parameters) = _parseCalldata(_data, lockedParameters.length); 199 | 200 | // Validate functionSelector 201 | require( 202 | functionSelector == _delegateKey.approvedFunctionSelector, 203 | "ADKM: Invalid function selector" 204 | ); 205 | 206 | // Validate locked values 207 | for (uint256 i = 0; i < lockedParameters.length; i++) { 208 | if (lockedParameters[i]) { 209 | require(lockedParameterValues[i] == parameters[i], "ADKM: Invalid parameter"); 210 | } 211 | } 212 | } 213 | 214 | function _parseCalldata( 215 | bytes memory _data, 216 | uint256 _parameterCount 217 | ) 218 | internal 219 | pure 220 | returns (bytes4, bytes32[] memory) 221 | { 222 | // NOTE: This function does not handle fallbacks, as those are handled one level above 223 | 224 | // Minimum data length is 4 bytes for the function selector + 32 bytes per parameter 225 | uint256 minDataLength = _parameterCount.mul(32).add(4); 226 | require(_data.length >= minDataLength, "ADKM: Transaction data is too short"); 227 | 228 | bytes4 functionSelector = _data.toBytes4(0); 229 | bytes32[] memory parameters = new bytes32[](_parameterCount); 230 | for (uint256 i = 0; i < _parameterCount; i++) { 231 | // Parameters are every 32 bytes after the 4 byte function selector 232 | parameters[i] = _data.toBytes32(i.mul(32).add(4)); 233 | } 234 | 235 | return (functionSelector, parameters); 236 | } 237 | 238 | function _executeTransaction( 239 | address payable _authereumAccount, 240 | address _to, 241 | uint256 _value, 242 | uint256 _gasLimit, 243 | bytes memory _data 244 | ) 245 | private 246 | returns (bytes[] memory) 247 | { 248 | // Prepare transactions 249 | bytes memory transactionData = abi.encode(_to, _value, _gasLimit, _data); 250 | bytes[] memory transactions = new bytes[](1); 251 | transactions[0] = transactionData; 252 | 253 | // Make the transaction 254 | bytes[] memory returnValues = IAuthereumAccount(_authereumAccount).executeMultipleTransactions(transactions); 255 | 256 | emit TransactionExecuted(_authereumAccount, msg.sender, _value, _data); 257 | return returnValues; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /contracts/modules/AuthereumRecoveryModule.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../libs/SafeMath.sol"; 5 | import "../interfaces/IAuthereumAccount.sol"; 6 | 7 | /** 8 | * @title AuthereumRecoveryModule 9 | * @author Authereum Labs, Inc. 10 | * @dev This contract facilitates the recovery of Authereum accounts. 11 | * @dev This contract may be added as a module for an Authereum account. The Authereum 12 | * @dev account may set one or more recovery addresses and specify a recovery delay period. 13 | * @dev A recovery address may start the recovery process at any time and specify a new auth 14 | * @dev key to be added to the Authereum account. During the recovery delay period, the 15 | * @dev recovery process can be cancelled by the Authereum account. After the recovery delay 16 | * @dev period, any address can complete the recovery process which will add the new auth key 17 | * @dev to the Authereum account. 18 | */ 19 | 20 | contract AuthereumRecoveryModule { 21 | using SafeMath for uint256; 22 | 23 | string constant public name = "Authereum Recovery Module"; 24 | string constant public version = "2020070100"; 25 | 26 | /** 27 | * Events 28 | */ 29 | 30 | event RecoveryAddressAdded ( 31 | address indexed accountContract, 32 | address indexed recoveryAddress, 33 | uint256 indexed recoveryDelay 34 | ); 35 | 36 | event RecoveryAddressRemoved ( 37 | address indexed accountContract, 38 | address indexed recoveryAddress 39 | ); 40 | 41 | event RecoveryStarted ( 42 | address indexed accountContract, 43 | address indexed recoveryAddress, 44 | address indexed newAuthKey, 45 | uint256 startTime, 46 | uint256 recoveryDelay 47 | ); 48 | 49 | event RecoveryCancelled ( 50 | address indexed accountContract, 51 | address indexed recoveryAddress, 52 | address indexed newAuthKey 53 | ); 54 | 55 | event RecoveryCompleted ( 56 | address indexed accountContract, 57 | address indexed recoveryAddress, 58 | address indexed newAuthKey 59 | ); 60 | 61 | /** 62 | * State 63 | */ 64 | 65 | struct RecoveryAccount { 66 | bool active; 67 | uint256 delay; 68 | } 69 | 70 | struct RecoveryAttempt { 71 | uint256 startTime; 72 | address newAuthKey; 73 | } 74 | 75 | mapping(address => mapping(address => RecoveryAccount)) public recoveryAccounts; 76 | mapping(address => mapping(address => RecoveryAttempt)) public recoveryAttempts; 77 | 78 | /** 79 | * Modifiers 80 | */ 81 | 82 | modifier isRecoveryAddress(address _accountContract, address _recoveryAddress) { 83 | require(recoveryAccounts[_accountContract][_recoveryAddress].active, "ARM: Inactive recovery account"); 84 | _; 85 | } 86 | 87 | modifier onlyWhenRegisteredModule { 88 | require(IAuthereumAccount(msg.sender).authKeys(address(this)), "ARM: Recovery module not registered to account"); 89 | _; 90 | } 91 | 92 | /** 93 | * Public functions 94 | */ 95 | 96 | /// @dev Add a recovery address 97 | /// @dev Called by the Authereum account 98 | /// @param _recoveryAddress The address that can recover the account 99 | /// @param _recoveryDelay The delay required between starting and completing recovery 100 | function addRecoveryAccount(address _recoveryAddress, uint256 _recoveryDelay) external onlyWhenRegisteredModule { 101 | require(_recoveryAddress != address(0), "ARM: Recovery address cannot be address(0)"); 102 | require(_recoveryAddress != msg.sender, "ARM: Cannot add self as recovery account"); 103 | require(recoveryAccounts[msg.sender][_recoveryAddress].active == false, "ARM: Recovery address has already been added"); 104 | recoveryAccounts[msg.sender][_recoveryAddress] = RecoveryAccount(true, _recoveryDelay); 105 | 106 | emit RecoveryAddressAdded(msg.sender, _recoveryAddress, _recoveryDelay); 107 | } 108 | 109 | /// @dev Remove a recovery address 110 | /// @dev Called by the Authereum account 111 | /// @param _recoveryAddress The address that can recover the account 112 | function removeRecoveryAccount(address _recoveryAddress) external { 113 | require(recoveryAccounts[msg.sender][_recoveryAddress].active == true, "ARM: Recovery address is already inactive"); 114 | delete recoveryAccounts[msg.sender][_recoveryAddress]; 115 | 116 | RecoveryAttempt storage recoveryAttempt = recoveryAttempts[msg.sender][_recoveryAddress]; 117 | if (recoveryAttempt.startTime != 0) { 118 | emit RecoveryCancelled(msg.sender, _recoveryAddress, recoveryAttempt.newAuthKey); 119 | } 120 | delete recoveryAttempts[msg.sender][_recoveryAddress]; 121 | 122 | emit RecoveryAddressRemoved(msg.sender, _recoveryAddress); 123 | } 124 | 125 | /// @dev Start the recovery process 126 | /// @dev Called by the recovery address 127 | /// @param _accountContract Address of the Authereum account being recovered 128 | /// @param _newAuthKey The address of the Auth Key being added to the Authereum account 129 | function startRecovery(address _accountContract, address _newAuthKey) external isRecoveryAddress(_accountContract, msg.sender) { 130 | require(recoveryAttempts[_accountContract][msg.sender].startTime == 0, "ARM: Recovery is already in process"); 131 | require(_newAuthKey != address(0), "ARM: Auth Key cannot be address(0)"); 132 | 133 | recoveryAttempts[_accountContract][msg.sender] = RecoveryAttempt(now, _newAuthKey); 134 | 135 | uint256 recoveryDelay = recoveryAccounts[_accountContract][msg.sender].delay; 136 | emit RecoveryStarted(_accountContract, msg.sender, _newAuthKey, now, recoveryDelay); 137 | } 138 | 139 | /// @dev Cancel the recovery process 140 | /// @dev Called by the recovery address 141 | /// @param _accountContract Address of the Authereum account being recovered 142 | function cancelRecovery(address _accountContract) external isRecoveryAddress(_accountContract, msg.sender) { 143 | RecoveryAttempt memory recoveryAttempt = recoveryAttempts[_accountContract][msg.sender]; 144 | 145 | require(recoveryAttempt.startTime != 0, "ARM: Recovery attempt does not exist"); 146 | 147 | delete recoveryAttempts[_accountContract][msg.sender]; 148 | 149 | emit RecoveryCancelled(_accountContract, msg.sender, recoveryAttempt.newAuthKey); 150 | } 151 | 152 | /// @dev Complete the recovery process 153 | /// @dev Called by any address 154 | /// @param _accountContract Address of the Authereum account being recovered 155 | /// @param _recoveryAddress The address that can recover the account 156 | function completeRecovery(address payable _accountContract, address _recoveryAddress) external isRecoveryAddress(_accountContract, _recoveryAddress) { 157 | RecoveryAccount memory recoveryAccount = recoveryAccounts[_accountContract][_recoveryAddress]; 158 | RecoveryAttempt memory recoveryAttempt = recoveryAttempts[_accountContract][_recoveryAddress]; 159 | 160 | require(recoveryAttempt.startTime != 0, "ARM: Recovery attempt does not exist"); 161 | require(recoveryAttempt.startTime.add(recoveryAccount.delay) <= now, "ARM: Recovery attempt delay period has not completed"); 162 | 163 | delete recoveryAttempts[_accountContract][_recoveryAddress]; 164 | IAuthereumAccount(_accountContract).addAuthKey(recoveryAttempt.newAuthKey); 165 | 166 | emit RecoveryCompleted(_accountContract, _recoveryAddress, recoveryAttempt.newAuthKey); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /contracts/test/BadTransaction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @title Authereum Bad Transaction 5 | * @author Authereum Labs, Inc. 6 | * @dev A contract that has a transaction that will throw. 7 | */ 8 | 9 | contract BadTransaction { 10 | function () external payable { 11 | require(1 == 2, "BT: Will fail"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/test/ERC777Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts/token/ERC777/ERC777.sol"; 4 | 5 | contract ERC777Token is ERC777 { 6 | // contract ERC777Token { 7 | constructor() public ERC777("Test", "TST", new address[](0)) { 8 | // constructor() public { 9 | 10 | } 11 | 12 | function mint(address _to, uint256 _amount) external { 13 | // address operator, 14 | // address account, 15 | // uint256 amount, 16 | // bytes memory userData, 17 | // bytes memory operatorData 18 | bytes memory zeroByres = new bytes(0); 19 | _mint(msg.sender, _to, _amount, zeroByres, zeroByres); 20 | } 21 | } -------------------------------------------------------------------------------- /contracts/test/EnsRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * ENS registry test contract. 5 | */ 6 | contract EnsRegistry { 7 | 8 | struct Record { 9 | address owner; 10 | address resolver; 11 | uint64 ttl; 12 | } 13 | 14 | mapping(bytes32=>Record) records; 15 | 16 | // Logged when the owner of a node assigns a new owner to a subnode. 17 | event NewOwner(bytes32 indexed _node, bytes32 indexed _label, address _owner); 18 | 19 | // Logged when the owner of a node transfers ownership to a new account. 20 | event Transfer(bytes32 indexed _node, address _owner); 21 | 22 | // Logged when the resolver for a node changes. 23 | event NewResolver(bytes32 indexed _node, address _resolver); 24 | 25 | // Logged when the TTL of a node changes 26 | event NewTTL(bytes32 indexed _node, uint64 _ttl); 27 | 28 | // Permits modifications only by the owner of the specified node. 29 | modifier only_owner(bytes32 _node) { 30 | require(records[_node].owner == msg.sender, "ENSTest: this method needs to be called by the owner of the node"); 31 | _; 32 | } 33 | 34 | /** 35 | * Constructs a new ENS registrar. 36 | */ 37 | constructor() public { 38 | records[bytes32(0)].owner = msg.sender; 39 | } 40 | 41 | /** 42 | * Returns the address that owns the specified node. 43 | */ 44 | function owner(bytes32 _node) public view returns (address) { 45 | return records[_node].owner; 46 | } 47 | 48 | /** 49 | * Returns the address of the resolver for the specified node. 50 | */ 51 | function resolver(bytes32 _node) public view returns (address) { 52 | return records[_node].resolver; 53 | } 54 | 55 | /** 56 | * Returns the TTL of a node, and any records associated with it. 57 | */ 58 | function ttl(bytes32 _node) public view returns (uint64) { 59 | return records[_node].ttl; 60 | } 61 | 62 | /** 63 | * Transfers ownership of a node to a new address. May only be called by the current 64 | * owner of the node. 65 | * @param _node The node to transfer ownership of. 66 | * @param _owner The address of the new owner. 67 | */ 68 | function setOwner(bytes32 _node, address _owner) public only_owner(_node) { 69 | emit Transfer(_node, _owner); 70 | records[_node].owner = _owner; 71 | } 72 | 73 | /** 74 | * Transfers ownership of a subnode sha3(node, label) to a new address. May only be 75 | * called by the owner of the parent node. 76 | * @param _node The parent node. 77 | * @param _label The hash of the label specifying the subnode. 78 | * @param _owner The address of the new owner. 79 | */ 80 | function setSubnodeOwner(bytes32 _node, bytes32 _label, address _owner) public only_owner(_node) { 81 | bytes32 subnode = keccak256(abi.encodePacked(_node, _label)); 82 | emit NewOwner(_node, _label, _owner); 83 | records[subnode].owner = _owner; 84 | } 85 | 86 | /** 87 | * Sets the resolver address for the specified node. 88 | * @param _node The node to update. 89 | * @param _resolver The address of the resolver. 90 | */ 91 | function setResolver(bytes32 _node, address _resolver) public only_owner(_node) { 92 | emit NewResolver(_node, _resolver); 93 | records[_node].resolver = _resolver; 94 | } 95 | 96 | /** 97 | * Sets the TTL for the specified node. 98 | * @param _node The node to update. 99 | * @param _ttl The TTL in seconds. 100 | */ 101 | function setTTL(bytes32 _node, uint64 _ttl) public only_owner(_node) { 102 | emit NewTTL(_node, _ttl); 103 | records[_node].ttl = _ttl; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /contracts/test/EnsResolver.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2018-11-15 3 | */ 4 | 5 | pragma solidity 0.5.17; 6 | 7 | interface ENS { 8 | 9 | // Logged when the owner of a node assigns a new owner to a subnode. 10 | event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); 11 | 12 | // Logged when the owner of a node transfers ownership to a new account. 13 | event Transfer(bytes32 indexed node, address owner); 14 | 15 | // Logged when the resolver for a node changes. 16 | event NewResolver(bytes32 indexed node, address resolver); 17 | 18 | // Logged when the TTL of a node changes 19 | event NewTTL(bytes32 indexed node, uint64 ttl); 20 | 21 | 22 | function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external; 23 | function setResolver(bytes32 node, address resolver) external; 24 | function setOwner(bytes32 node, address owner) external; 25 | function setTTL(bytes32 node, uint64 ttl) external; 26 | function owner(bytes32 node) external view returns (address); 27 | function resolver(bytes32 node) external view returns (address); 28 | function ttl(bytes32 node) external view returns (uint64); 29 | 30 | } 31 | 32 | /** 33 | * A simple resolver anyone can use; only allows the owner of a node to set its 34 | * address. 35 | */ 36 | contract EnsResolver { 37 | 38 | bytes4 constant INTERFACE_META_ID = 0x01ffc9a7; 39 | bytes4 constant ADDR_INTERFACE_ID = 0x3b3b57de; 40 | bytes4 constant NAME_INTERFACE_ID = 0x691f3431; 41 | bytes4 constant ABI_INTERFACE_ID = 0x2203ab56; 42 | bytes4 constant PUBKEY_INTERFACE_ID = 0xc8690233; 43 | bytes4 constant TEXT_INTERFACE_ID = 0x59d1d43c; 44 | bytes4 constant CONTENTHASH_INTERFACE_ID = 0xbc1c58d1; 45 | 46 | event AddrChanged(bytes32 indexed node, address a); 47 | event NameChanged(bytes32 indexed node, string name); 48 | event ABIChanged(bytes32 indexed node, uint256 indexed contentType); 49 | event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y); 50 | event TextChanged(bytes32 indexed node, string indexedKey, string key); 51 | event ContenthashChanged(bytes32 indexed node, bytes hash); 52 | 53 | struct PublicKey { 54 | bytes32 x; 55 | bytes32 y; 56 | } 57 | 58 | struct Record { 59 | address addr; 60 | string name; 61 | PublicKey pubkey; 62 | mapping(string=>string) text; 63 | mapping(uint256=>bytes) abis; 64 | bytes contenthash; 65 | } 66 | 67 | ENS ens; 68 | 69 | mapping (bytes32 => Record) records; 70 | 71 | modifier onlyOwner(bytes32 node) { 72 | require(ens.owner(node) == msg.sender); 73 | _; 74 | } 75 | 76 | /** 77 | * Constructor. 78 | * @param ensAddr The ENS registrar contract. 79 | */ 80 | constructor(ENS ensAddr) public { 81 | ens = ensAddr; 82 | } 83 | 84 | /** 85 | * Sets the address associated with an ENS node. 86 | * May only be called by the owner of that node in the ENS registry. 87 | * @param node The node to update. 88 | * @param addr The address to set. 89 | */ 90 | function setAddr(bytes32 node, address addr) public onlyOwner(node) { 91 | records[node].addr = addr; 92 | emit AddrChanged(node, addr); 93 | } 94 | 95 | /** 96 | * Sets the contenthash associated with an ENS node. 97 | * May only be called by the owner of that node in the ENS registry. 98 | * @param node The node to update. 99 | * @param hash The contenthash to set 100 | */ 101 | function setContenthash(bytes32 node, bytes memory hash) public onlyOwner(node) { 102 | records[node].contenthash = hash; 103 | emit ContenthashChanged(node, hash); 104 | } 105 | 106 | /** 107 | * Sets the name associated with an ENS node, for reverse records. 108 | * May only be called by the owner of that node in the ENS registry. 109 | * @param node The node to update. 110 | * @param name The name to set. 111 | */ 112 | function setName(bytes32 node, string memory name) public onlyOwner(node) { 113 | records[node].name = name; 114 | emit NameChanged(node, name); 115 | } 116 | 117 | /** 118 | * Sets the ABI associated with an ENS node. 119 | * Nodes may have one ABI of each content type. To remove an ABI, set it to 120 | * the empty string. 121 | * @param node The node to update. 122 | * @param contentType The content type of the ABI 123 | * @param data The ABI data. 124 | */ 125 | function setABI(bytes32 node, uint256 contentType, bytes memory data) public onlyOwner(node) { 126 | // Content types must be powers of 2 127 | require(((contentType - 1) & contentType) == 0); 128 | 129 | records[node].abis[contentType] = data; 130 | emit ABIChanged(node, contentType); 131 | } 132 | 133 | /** 134 | * Sets the SECP256k1 public key associated with an ENS node. 135 | * @param node The ENS node to query 136 | * @param x the X coordinate of the curve point for the public key. 137 | * @param y the Y coordinate of the curve point for the public key. 138 | */ 139 | function setPubkey(bytes32 node, bytes32 x, bytes32 y) public onlyOwner(node) { 140 | records[node].pubkey = PublicKey(x, y); 141 | emit PubkeyChanged(node, x, y); 142 | } 143 | 144 | /** 145 | * Sets the text data associated with an ENS node and key. 146 | * May only be called by the owner of that node in the ENS registry. 147 | * @param node The node to update. 148 | * @param key The key to set. 149 | * @param value The text data value to set. 150 | */ 151 | function setText(bytes32 node, string memory key, string memory value) public onlyOwner(node) { 152 | records[node].text[key] = value; 153 | emit TextChanged(node, key, key); 154 | } 155 | 156 | /** 157 | * Returns the text data associated with an ENS node and key. 158 | * @param node The ENS node to query. 159 | * @param key The text data key to query. 160 | * @return The associated text data. 161 | */ 162 | function text(bytes32 node, string memory key) public view returns (string memory) { 163 | return records[node].text[key]; 164 | } 165 | 166 | /** 167 | * Returns the SECP256k1 public key associated with an ENS node. 168 | * Defined in EIP 619. 169 | * @param node The ENS node to query 170 | * @return x, y the X and Y coordinates of the curve point for the public key. 171 | */ 172 | function pubkey(bytes32 node) public view returns (bytes32 x, bytes32 y) { 173 | return (records[node].pubkey.x, records[node].pubkey.y); 174 | } 175 | 176 | /** 177 | * Returns the ABI associated with an ENS node. 178 | * Defined in EIP205. 179 | * @param node The ENS node to query 180 | * @param contentTypes A bitwise OR of the ABI formats accepted by the caller. 181 | * @return contentType The content type of the return value 182 | * @return data The ABI data 183 | */ 184 | function ABI(bytes32 node, uint256 contentTypes) public view returns (uint256 contentType, bytes memory data) { 185 | Record storage record = records[node]; 186 | for (contentType = 1; contentType <= contentTypes; contentType <<= 1) { 187 | if ((contentType & contentTypes) != 0 && record.abis[contentType].length > 0) { 188 | data = record.abis[contentType]; 189 | return (contentType, data); 190 | } 191 | } 192 | contentType = 0; 193 | } 194 | 195 | /** 196 | * Returns the name associated with an ENS node, for reverse records. 197 | * Defined in EIP181. 198 | * @param node The ENS node to query. 199 | * @return The associated name. 200 | */ 201 | function name(bytes32 node) public view returns (string memory) { 202 | return records[node].name; 203 | } 204 | 205 | /** 206 | * Returns the address associated with an ENS node. 207 | * @param node The ENS node to query. 208 | * @return The associated address. 209 | */ 210 | function addr(bytes32 node) public view returns (address) { 211 | return records[node].addr; 212 | } 213 | 214 | /** 215 | * Returns the contenthash associated with an ENS node. 216 | * @param node The ENS node to query. 217 | * @return The associated contenthash. 218 | */ 219 | function contenthash(bytes32 node) public view returns (bytes memory) { 220 | return records[node].contenthash; 221 | } 222 | 223 | /** 224 | * Returns true if the resolver implements the interface specified by the provided hash. 225 | * @param interfaceID The ID of the interface to check for. 226 | * @return True if the contract implements the requested interface. 227 | */ 228 | function supportsInterface(bytes4 interfaceID) public pure returns (bool) { 229 | return interfaceID == ADDR_INTERFACE_ID || 230 | interfaceID == NAME_INTERFACE_ID || 231 | interfaceID == ABI_INTERFACE_ID || 232 | interfaceID == PUBKEY_INTERFACE_ID || 233 | interfaceID == TEXT_INTERFACE_ID || 234 | interfaceID == CONTENTHASH_INTERFACE_ID || 235 | interfaceID == INTERFACE_META_ID; 236 | } 237 | } -------------------------------------------------------------------------------- /contracts/test/EnsReverseRegistrar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./EnsRegistry.sol"; 4 | import "./EnsResolver.sol"; 5 | 6 | /** 7 | * ENS Reverse registrar test contract. 8 | */ 9 | contract EnsReverseRegistrar { 10 | 11 | // namehash('addr.reverse') 12 | bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; 13 | 14 | EnsRegistry public ens; 15 | EnsResolver public defaultResolver; 16 | 17 | /** 18 | * @dev Constructor 19 | * @param ensAddr The address of the ENS registry. 20 | * @param resolverAddr The address of the default reverse resolver. 21 | */ 22 | constructor(address ensAddr, address resolverAddr) public { 23 | ens = EnsRegistry(ensAddr); 24 | defaultResolver = EnsResolver(resolverAddr); 25 | } 26 | 27 | /** 28 | * @dev Transfers ownership of the reverse ENS record associated with the 29 | * calling account. 30 | * @param owner The address to set as the owner of the reverse record in ENS. 31 | * @return The ENS node hash of the reverse record. 32 | */ 33 | function claim(address owner) public returns (bytes32) { 34 | return claimWithResolver(owner, address(0)); 35 | } 36 | 37 | /** 38 | * @dev Transfers ownership of the reverse ENS record associated with the 39 | * calling account. 40 | * @param owner The address to set as the owner of the reverse record in ENS. 41 | * @param resolver The address of the resolver to set; 0 to leave unchanged. 42 | * @return The ENS node hash of the reverse record. 43 | */ 44 | function claimWithResolver(address owner, address resolver) public returns (bytes32) { 45 | bytes32 label = sha3HexAddress(msg.sender); 46 | bytes32 node = keccak256(abi.encodePacked(ADDR_REVERSE_NODE, label)); 47 | address currentOwner = ens.owner(node); 48 | 49 | // Update the resolver if required 50 | if(resolver != address(0) && resolver != address(ens.resolver(node))) { 51 | // Transfer the name to us first if it's not already 52 | if(currentOwner != address(this)) { 53 | ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, address(this)); 54 | currentOwner = address(this); 55 | } 56 | ens.setResolver(node, resolver); 57 | } 58 | 59 | // Update the owner if required 60 | if(currentOwner != owner) { 61 | ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, owner); 62 | } 63 | 64 | return node; 65 | } 66 | 67 | /** 68 | * @dev Sets the `name()` record for the reverse ENS record associated with 69 | * the calling account. First updates the resolver to the default reverse 70 | * resolver if necessary. 71 | * @param name The name to set for this address. 72 | * @return The ENS node hash of the reverse record. 73 | */ 74 | function setName(string memory name) public returns (bytes32 node) { 75 | node = claimWithResolver(address(this), address(defaultResolver)); 76 | defaultResolver.setName(node, name); 77 | return node; 78 | } 79 | 80 | /** 81 | * @dev Returns the node hash for a given account's reverse records. 82 | * @param addr The address to hash 83 | * @return The ENS node hash. 84 | */ 85 | function node(address addr) public returns (bytes32 ret) { 86 | return keccak256(abi.encodePacked(ADDR_REVERSE_NODE, sha3HexAddress(addr))); 87 | } 88 | 89 | /** 90 | * @dev An optimised function to compute the sha3 of the lower-case 91 | * hexadecimal representation of an Ethereum address. 92 | * @param addr The address to hash 93 | * @return The SHA3 hash of the lower-case hexadecimal encoding of the 94 | * input address. 95 | */ 96 | function sha3HexAddress(address addr) private pure returns (bytes32 ret) { 97 | assembly { 98 | let lookup := 0x3031323334353637383961626364656600000000000000000000000000000000 99 | let i := 40 100 | 101 | for { } gt(i, 0) { } { 102 | i := sub(i, 1) 103 | mstore8(i, byte(and(addr, 0xf), lookup)) 104 | addr := div(addr, 0x10) 105 | i := sub(i, 1) 106 | mstore8(i, byte(and(addr, 0xf), lookup)) 107 | addr := div(addr, 0x10) 108 | } 109 | ret := keccak256(0, 40) 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /contracts/test/ParseCallData.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../modules/AuthereumDelegateKeyModule.sol"; 5 | 6 | /** 7 | * @title Parse Call Data 8 | * @author Authereum Labs, Inc. 9 | * @dev Inherits AuthereumDelegateKeyModule in order to test the _parseCalldata() function. 10 | * @dev The purpose of this file is to test the private function from the real contract. 11 | */ 12 | 13 | contract ParseCallData is AuthereumDelegateKeyModule { 14 | 15 | /// @dev Calls the internal _parseCalldata function 16 | /// @dev This is used strictly for testing purposes 17 | /// @param _data The calldata of the transaction made by the Authereum account 18 | /// @param _parameterCount Number parameters in the function call 19 | /// @return The function selector of the parsed data 20 | /// @return The parameters of the parsed data 21 | function callParseCalldata( 22 | bytes memory _data, 23 | uint256 _parameterCount 24 | ) 25 | public 26 | pure 27 | returns (bytes4, bytes32[] memory) 28 | { 29 | return _parseCalldata(_data, _parameterCount); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/test/ResetManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../ens/AuthereumEnsResolver.sol"; 4 | import "../base/Owned.sol"; 5 | 6 | contract ResetManager is Owned { 7 | 8 | address ensRegistry; 9 | // The managed root node 10 | bytes32 public rootNode; 11 | 12 | event RootnodeOwnerChanged(bytes32 indexed rootnode, address indexed newOwner); 13 | 14 | /// @dev Constructor that sets the ENS root name and root node to manage 15 | /// @param _rootNode The node of the root name (e.g. namehash(auth.eth)) 16 | /// @param _ensRegistry Custom ENS Registry address 17 | constructor( 18 | bytes32 _rootNode, 19 | address _ensRegistry 20 | ) 21 | public 22 | { 23 | rootNode = _rootNode; 24 | ensRegistry = _ensRegistry; 25 | } 26 | 27 | /// @dev This function is used when the rootnode owner is updated 28 | /// @param _newOwner The address of the new ENS manager that will manage the root node 29 | function changeRootnodeOwner(address _newOwner) external onlyOwner { 30 | require(_newOwner != address(0), "AEM: Address must not be null"); 31 | getEnsRegistry().setOwner(rootNode, _newOwner); 32 | emit RootnodeOwnerChanged(rootNode, _newOwner); 33 | } 34 | 35 | /// @dev Gets the official ENS registry 36 | /// @return The official ENS registry address 37 | function getEnsRegistry() public view returns (EnsRegistry) { 38 | return EnsRegistry(ensRegistry); 39 | } 40 | 41 | function reset( 42 | string calldata _label 43 | ) 44 | external 45 | onlyOwner 46 | { 47 | bytes32 labelNode = keccak256(abi.encodePacked(_label)); 48 | bytes32 node = keccak256(abi.encodePacked(rootNode, labelNode)); 49 | 50 | // Reset Official ENS Registry 51 | getEnsRegistry().setSubnodeOwner(rootNode, labelNode, address(this)); 52 | getEnsRegistry().setResolver(node, address(0)); // TODO: Technically I think this doesn't matter 53 | getEnsRegistry().setOwner(node, address(0)); 54 | getEnsRegistry().setSubnodeOwner(rootNode, labelNode, address(0)); 55 | } 56 | } -------------------------------------------------------------------------------- /contracts/test/ReturnTransaction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @title Authereum Return Transaction 5 | * @author Authereum Labs, Inc. 6 | * @dev A contract that has a transaction that will return 123. 7 | */ 8 | 9 | contract ReturnTransaction { 10 | event UintEvent(uint256 _data); 11 | event UintEvent2(uint256 num1, uint256 num2); 12 | 13 | function returnTest() external payable returns (uint256) { 14 | emit UintEvent(123); 15 | return 123; 16 | } 17 | 18 | function returnTest2(uint256 num1, uint256 num2) external payable returns (uint256) { 19 | emit UintEvent2(num1, num2); 20 | return num1; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/test/UpgradeAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../account/AuthereumAccount.sol"; 5 | 6 | /** 7 | * @title UpgradeAccount 8 | * @author Authereum Labs, Inc. 9 | * @dev A contract used to test upgrades. This contract does not have an init function. 10 | */ 11 | 12 | contract UpgradeAccount is AuthereumAccount { 13 | 14 | /// @dev Function to call to test an upgrade with a new function 15 | /// @return A constant 16 | function upgradeTest() public pure returns (uint256) { 17 | return 42; 18 | } 19 | 20 | /// @dev Function to call to test an upgrade with a new function reading an old state 21 | /// @return The chain ID 22 | function getChainIdValue() public pure returns (uint256) { 23 | return getChainId(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/UpgradeAccountBadInit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../account/AuthereumAccount.sol"; 5 | 6 | /** 7 | * @title UpgradeAccountBadInit 8 | * @author Authereum Labs, Inc. 9 | * @dev A contract used to test failing initializations. 10 | */ 11 | 12 | contract UpgradeAccountBadInit is AuthereumAccount { 13 | 14 | /// @dev Function to call to test an upgrade with a new function 15 | function upgradeTest() public pure { 16 | require(false, 'Upgrade failed'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/test/UpgradeAccountWithInit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../account/AuthereumAccount.sol"; 5 | 6 | /** 7 | * @title UpgradeAccountWithInit 8 | * @author Authereum Labs, Inc. 9 | * @dev A contract used to test upgrades. This contract does have an init function. 10 | */ 11 | 12 | contract UpgradeAccountWithInit is AuthereumAccount { 13 | 14 | uint256 public upgradeTestVal; 15 | 16 | /// @dev Function to call to test an upgrade with a new function 17 | /// @return A constant 18 | function upgradeTestInit() public { 19 | upgradeTestVal = 42; 20 | } 21 | 22 | /// @dev Function to call to test an upgrade with a new function 23 | /// @return An initialized constant 24 | function upgradeTest() public view returns (uint256) { 25 | return upgradeTestVal; 26 | } 27 | 28 | /// @dev Function to call to test an upgrade with a new function reading an old state 29 | /// @return The chain ID 30 | function getChainIdValue() public pure returns (uint256) { 31 | return getChainId(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/upgradeability/AuthereumEnsResolverProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../base/Owned.sol"; 5 | 6 | /** 7 | * @title AuthereumEnsResolverProxy 8 | * @author Authereum Labs, Inc. 9 | * @dev The Authereum ENS Resolver Proxy. 10 | */ 11 | 12 | contract AuthereumEnsResolverProxy is Owned { 13 | 14 | // We do not include a name or a version for this contract as this 15 | // is a simple proxy. Including them here would overwrite the declaration 16 | // of these variables in the implementation. 17 | 18 | /// @dev Storage slot with the address of the current implementation. 19 | /// @notice This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted 20 | /// @notice by 1, and is validated in the constructor. 21 | bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 22 | 23 | /// @dev Set the implementation address in the constructor 24 | /// @param _logic Address of the logic contract 25 | constructor(address _logic) public payable { 26 | bytes32 slot = IMPLEMENTATION_SLOT; 27 | assembly { 28 | sstore(slot, _logic) 29 | } 30 | } 31 | 32 | /** 33 | * Fallback 34 | */ 35 | 36 | /// @dev Fallback function 37 | /// @notice A payable fallback needs to be implemented in the implementation contract 38 | /// @notice This is a low level function that doesn't return to its internal call site. 39 | /// @notice It will return to the external caller whatever the implementation returns. 40 | function () external payable { 41 | if (msg.data.length == 0) return; 42 | address _implementation = implementation(); 43 | 44 | assembly { 45 | // Copy msg.data. We take full control of memory in this inline assembly 46 | // block because it will not return to Solidity code. We overwrite the 47 | // Solidity scratch pad at memory position 0. 48 | calldatacopy(0, 0, calldatasize) 49 | 50 | // Call the implementation. 51 | // out and outsize are 0 because we don't know the size yet. 52 | let result := delegatecall(gas, _implementation, 0, calldatasize, 0, 0) 53 | 54 | // Copy the returned data. 55 | returndatacopy(0, 0, returndatasize) 56 | 57 | switch result 58 | // delegatecall returns 0 on error. 59 | case 0 { revert(0, returndatasize) } 60 | default { return(0, returndatasize) } 61 | } 62 | } 63 | 64 | /** 65 | * Setters 66 | */ 67 | 68 | /// @dev Set the implementation address 69 | /// @param _logic Address of the logic contract 70 | function setImplementation (address _logic) public onlyOwner { 71 | bytes32 slot = IMPLEMENTATION_SLOT; 72 | assembly { 73 | sstore(slot, _logic) 74 | } 75 | } 76 | 77 | /** 78 | * Getters 79 | */ 80 | 81 | /// @dev Returns the current implementation. 82 | /// @return Address of the current implementation 83 | function implementation() public view returns (address impl) { 84 | bytes32 slot = IMPLEMENTATION_SLOT; 85 | assembly { 86 | impl := sload(slot) 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /contracts/upgradeability/AuthereumProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @title AuthereumProxy 5 | * @author Authereum Labs, Inc. 6 | * @dev The Authereum Proxy. 7 | */ 8 | 9 | contract AuthereumProxy { 10 | 11 | // We do not include a name or a version for this contract as this 12 | // is a simple proxy. Including them here would overwrite the declaration 13 | // of these variables in the implementation. 14 | 15 | /// @dev Storage slot with the address of the current implementation. 16 | /// @notice This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1 17 | bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 18 | 19 | /// @dev Set the implementation in the constructor 20 | /// @param _logic Address of the logic contract 21 | constructor(address _logic) public payable { 22 | bytes32 slot = IMPLEMENTATION_SLOT; 23 | assembly { 24 | sstore(slot, _logic) 25 | } 26 | } 27 | 28 | /// @dev Fallback function 29 | /// @notice A payable fallback needs to be implemented in the implementation contract 30 | /// @notice This is a low level function that doesn't return to its internal call site. 31 | /// @notice It will return to the external caller whatever the implementation returns. 32 | function () external payable { 33 | if (msg.data.length == 0) return; 34 | 35 | assembly { 36 | // Load the implementation address from the IMPLEMENTATION_SLOT 37 | let impl := sload(IMPLEMENTATION_SLOT) 38 | 39 | // Copy msg.data. We take full control of memory in this inline assembly 40 | // block because it will not return to Solidity code. We overwrite the 41 | // Solidity scratch pad at memory position 0. 42 | calldatacopy(0, 0, calldatasize) 43 | 44 | // Call the implementation. 45 | // out and outsize are 0 because we don't know the size yet. 46 | let result := delegatecall(gas, impl, 0, calldatasize, 0, 0) 47 | 48 | // Copy the returned data. 49 | returndatacopy(0, 0, returndatasize) 50 | 51 | switch result 52 | // delegatecall returns 0 on error. 53 | case 0 { revert(0, returndatasize) } 54 | default { return(0, returndatasize) } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /contracts/upgradeability/AuthereumProxyFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../base/Owned.sol"; 5 | import "./AuthereumProxy.sol"; 6 | 7 | contract AuthereumEnsManager { 8 | function register(string calldata _label, address _owner) external {} 9 | } 10 | /** 11 | * @title AuthereumProxyFactory 12 | * @author Authereum Labs, Inc. 13 | * @dev A factory that creates Authereum Proxies. 14 | */ 15 | 16 | contract AuthereumProxyFactory is Owned { 17 | 18 | string constant public name = "Authereum Proxy Factory"; 19 | string constant public version = "2020070100"; 20 | 21 | bytes private initCode; 22 | address private authereumEnsManagerAddress; 23 | 24 | AuthereumEnsManager authereumEnsManager; 25 | 26 | event InitCodeChanged(bytes initCode); 27 | event AuthereumEnsManagerChanged(address indexed authereumEnsManager); 28 | 29 | /// @dev Constructor 30 | /// @param _initCode Init code of the AuthereumProxy without the constructor arguments 31 | /// @param _authereumEnsManagerAddress Address for the Authereum ENS Manager contract 32 | constructor(bytes memory _initCode, address _authereumEnsManagerAddress) public { 33 | initCode = _initCode; 34 | authereumEnsManagerAddress = _authereumEnsManagerAddress; 35 | authereumEnsManager = AuthereumEnsManager(authereumEnsManagerAddress); 36 | emit InitCodeChanged(initCode); 37 | emit AuthereumEnsManagerChanged(authereumEnsManagerAddress); 38 | } 39 | 40 | /** 41 | * Setters 42 | */ 43 | 44 | /// @dev Setter for the proxy initCode without the constructor arguments 45 | /// @param _initCode Init code of the AuthereumProxy without the constructor arguments 46 | function setInitCode(bytes memory _initCode) public onlyOwner { 47 | initCode = _initCode; 48 | emit InitCodeChanged(initCode); 49 | } 50 | 51 | /// @dev Setter for the Authereum ENS Manager address 52 | /// @param _authereumEnsManagerAddress Address of the new Authereum ENS Manager 53 | function setAuthereumEnsManager(address _authereumEnsManagerAddress) public onlyOwner { 54 | authereumEnsManagerAddress = _authereumEnsManagerAddress; 55 | authereumEnsManager = AuthereumEnsManager(authereumEnsManagerAddress); 56 | emit AuthereumEnsManagerChanged(authereumEnsManagerAddress); 57 | } 58 | 59 | /** 60 | * Getters 61 | */ 62 | 63 | /// @dev Getter for the proxy initCode without the constructor arguments 64 | /// @return Init code 65 | function getInitCode() public view returns (bytes memory) { 66 | return initCode; 67 | } 68 | 69 | /// @dev Getter for the private authereumEnsManager variable 70 | /// @return Authereum Ens Manager 71 | function getAuthereumEnsManager() public view returns (address) { 72 | return authereumEnsManagerAddress; 73 | } 74 | 75 | /// @dev Create an Authereum Proxy and iterate through initialize data 76 | /// @notice The bytes[] _initData is an array of initialize functions. 77 | /// @notice This is used when a user creates an account e.g. on V5, but V1,2,3, 78 | /// @notice etc. have state vars that need to be included. 79 | /// @param _salt A uint256 value to add randomness to the account creation 80 | /// @param _label Label for the user's Authereum ENS subdomain 81 | /// @param _initData Array of initialize data 82 | /// @param _implementation Address of the logic contract that the proxy will point to 83 | function createProxy( 84 | uint256 _salt, 85 | string memory _label, 86 | bytes[] memory _initData, 87 | address _implementation 88 | ) 89 | public 90 | onlyOwner 91 | returns (AuthereumProxy) 92 | { 93 | address payable addr; 94 | bytes32 create2Salt = _getCreate2Salt(_salt, _initData, _implementation); 95 | bytes memory initCodeWithConstructor = abi.encodePacked(initCode, uint256(_implementation)); 96 | 97 | // Create proxy 98 | assembly { 99 | addr := create2(0, add(initCodeWithConstructor, 0x20), mload(initCodeWithConstructor), create2Salt) 100 | if iszero(extcodesize(addr)) { 101 | revert(0, 0) 102 | } 103 | } 104 | 105 | // Loop through initializations of each version of the logic contract 106 | bool success; 107 | for (uint256 i = 0; i < _initData.length; i++) { 108 | require(_initData[i].length != 0, "APF: Empty initialization data"); 109 | (success,) = addr.call(_initData[i]); 110 | require(success, "APF: Unsuccessful account initialization"); 111 | } 112 | 113 | // Set ENS name 114 | authereumEnsManager.register(_label, addr); 115 | 116 | return AuthereumProxy(addr); 117 | } 118 | 119 | /// @dev Generate a salt out of a uint256 value and the init data 120 | /// @param _salt A uint256 value to add randomness to the account creation 121 | /// @param _initData Array of initialize data 122 | /// @param _implementation Address of the logic contract that the proxy will point to 123 | function _getCreate2Salt( 124 | uint256 _salt, 125 | bytes[] memory _initData, 126 | address _implementation 127 | ) 128 | internal 129 | pure 130 | returns (bytes32) 131 | { 132 | bytes32 _initDataHash = keccak256(abi.encode(_initData)); 133 | return keccak256(abi.encodePacked(_salt, _initDataHash, _implementation)); 134 | } 135 | } -------------------------------------------------------------------------------- /contracts/validation/AuthereumLoginKeyValidator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../base/Owned.sol"; 5 | import "../interfaces/ILoginKeyTransactionValidator.sol"; 6 | 7 | /** 8 | * @title AuthereumLoginKeyValidator 9 | * @author Authereum Labs, Inc. 10 | * @dev This contract used to validate Login Key transactions. Its address is included in the 11 | * loginKeyRestrictionsData that is a part of the data signed for a loginKeyAttestationSignature. 12 | */ 13 | 14 | contract AuthereumLoginKeyValidator is Owned, ILoginKeyTransactionValidator { 15 | 16 | string constant public name = "Authereum Login Key Validator"; 17 | string constant public version = "2020070100"; 18 | 19 | /** 20 | * Events 21 | */ 22 | 23 | event RelayerAdded(address indexed relayer); 24 | event RelayerRemoved(address indexed relayer); 25 | 26 | /** 27 | * State 28 | */ 29 | 30 | mapping(address => bool) public relayerIsAllowed; 31 | 32 | /// @dev Returns true and an empty string if transactions are valid and false and an error 33 | /// message if it's invalid. 34 | /// @dev validateTransaction MUST return an error message if `success` is `false` 35 | // @param _transactions The encoded transactions being executed 36 | /// @param _validationData The encoded data containing the expiration time 37 | /// @param _relayerAddress The address calling the account contract 38 | function validateTransactions( 39 | bytes[] calldata, 40 | bytes calldata _validationData, 41 | address _relayerAddress 42 | ) 43 | external 44 | { 45 | uint256 loginKeyExpirationTime = abi.decode(_validationData, (uint256)); 46 | 47 | // Check that loginKey is not expired 48 | require(loginKeyExpirationTime > now, "LKV: Login key is expired"); 49 | 50 | // Check that _relayerAddress is an Authereum relayer 51 | require(relayerIsAllowed[_relayerAddress], "LKV: Invalid relayer"); 52 | } 53 | 54 | /// @dev Called after a transaction is executed to record information about the transaction 55 | /// for validation such as value transferred 56 | // @param _transactions The encoded transactions being executed 57 | // @param _validationData The encoded data containing the expiration time 58 | // @param _relayerAddress The address calling the account contract 59 | function transactionsDidExecute( 60 | bytes[] calldata, 61 | bytes calldata, 62 | address 63 | ) 64 | external 65 | { } 66 | 67 | /// @dev Allow an array of relayers 68 | /// @param _newRelayers The list of relayers to be allowed 69 | function addRelayers(address[] calldata _newRelayers) external onlyOwner { 70 | for (uint256 i = 0; i < _newRelayers.length; i++) { 71 | address relayer = _newRelayers[i]; 72 | require(relayerIsAllowed[relayer] == false, "LKV: Relayer has already been added"); 73 | relayerIsAllowed[relayer] = true; 74 | emit RelayerAdded(relayer); 75 | } 76 | } 77 | 78 | /// @dev Remove a relayer from the allowlist 79 | /// @param _relayersToRemove The list of relayers to remove from the allowlist 80 | function removeRelayers(address[] calldata _relayersToRemove) external onlyOwner { 81 | for (uint256 i = 0; i < _relayersToRemove.length; i++) { 82 | address relayer = _relayersToRemove[i]; 83 | require(relayerIsAllowed[relayer] == true, "LKV: Address is not a relayer"); 84 | relayerIsAllowed[relayer] = false; 85 | emit RelayerRemoved(relayer); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "roots": [ 3 | "src" 4 | ], 5 | "transform": { 6 | "^.+\\.tsx?$": "ts-jest" 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@authereum/contracts", 3 | "version": "1.0.0", 4 | "description": "Ethereum smart contract for Authereum", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "dev": "tsc --watch", 12 | "build": "tsc", 13 | "clean": "rimraf dist", 14 | "ganache": "ganache-cli -h 0.0.0.0 -p 8545 -d -a 11 -e 1000 -i 1234 -k muirGlacier", 15 | "lint": "tslint --fix -c tslint.json src/*.ts src/**/*.ts src/**/**/*.ts", 16 | "publish-module": "npm publish --tag beta --access public", 17 | "dist-tag": "npm dist-tag add \"@authereum/contracts@$(jq -r .version { 20 | managedInstance = await ArtifactManaged.new() 21 | }) 22 | 23 | // Take snapshot before each test and revert after each test 24 | beforeEach(async() => { 25 | snapshotId = await timeUtils.takeSnapshot() 26 | }) 27 | 28 | afterEach(async() => { 29 | await timeUtils.revertSnapshot(snapshotId.result) 30 | }) 31 | 32 | //**********// 33 | // Tests // 34 | //********// 35 | 36 | describe('addManager', () => { 37 | context('Happy Path', async () => { 38 | it('Should add a new manager', async () => { 39 | var { logs } = await managedInstance.addManager(AUTHEREUM_OWNER) 40 | expectEvent.inLogs(logs, 'ManagerAdded', { _manager: AUTHEREUM_OWNER }) 41 | 42 | const isManager = await managedInstance.managers.call(AUTHEREUM_OWNER) 43 | assert.equal(isManager, true) 44 | }) 45 | it('Should do nothing if the same address is set as an owner', async () => { 46 | var { logs } = await managedInstance.addManager(AUTHEREUM_OWNER) 47 | expectEvent.inLogs(logs, 'ManagerAdded', { _manager: AUTHEREUM_OWNER }) 48 | 49 | const emptyLog = await managedInstance.addManager(AUTHEREUM_OWNER) 50 | assert.equal('', emptyLog.logs) 51 | }) 52 | }) 53 | context('Non-Happy Path', async () => { 54 | it('Should not allow 0 to be a manager', async () => { 55 | await expectRevert(managedInstance.addManager(constants.ZERO_ADDRESS), constants.REVERT_MSG.M_NOT_NULL_ADDRESS) 56 | }) 57 | it('Should exclusively allow the owner to set a manager', async () => { 58 | await expectRevert(managedInstance.addManager(AUTHEREUM_OWNER, { from: RELAYER }), constants.REVERT_MSG.O_MUST_BE_OWNER) 59 | }) 60 | }) 61 | }) 62 | describe('revokeManager', () => { 63 | context('Happy Path', async () => { 64 | it('Should remove a manager', async () => { 65 | await managedInstance.addManager(AUTHEREUM_OWNER) 66 | var { logs } = await managedInstance.revokeManager(AUTHEREUM_OWNER) 67 | expectEvent.inLogs(logs, 'ManagerRevoked', { _manager: AUTHEREUM_OWNER }) 68 | 69 | 70 | const isManager = await managedInstance.managers.call(AUTHEREUM_OWNER) 71 | assert.equal(isManager, false) 72 | }) 73 | }) 74 | context('Non-Happy Path', async () => { 75 | it('Should not remove a manager if said manager is not already set', async () => { 76 | await expectRevert(managedInstance.revokeManager(AUTHEREUM_OWNER), constants.REVERT_MSG.M_MUST_BE_EXISTING_MANAGER) 77 | }) 78 | it('Should not remove a manager if the function is not called by the owner', async () => { 79 | await expectRevert(managedInstance.revokeManager(AUTHEREUM_OWNER, { from: RELAYER }), constants.REVERT_MSG.O_MUST_BE_OWNER) 80 | }) 81 | }) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /test/base/Owned.js: -------------------------------------------------------------------------------- 1 | const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers') 2 | 3 | const constants = require('../utils/constants.js') 4 | const timeUtils = require('../utils/time.js') 5 | const ArtifactOwned = artifacts.require('Owned') 6 | 7 | contract('Owned', function (accounts) { 8 | const OWNER = accounts[0] 9 | const RELAYER = accounts[9] 10 | const AUTH_KEYS = [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5], accounts[6]] 11 | const RECEIVERS = [accounts[7]] 12 | const ENS_OWNER = accounts[8] 13 | const AUTHEREUM_OWNER = accounts[9] 14 | const LOGIN_KEY = accounts[10] 15 | 16 | // Test Params 17 | let snapshotId 18 | 19 | before(async () => { 20 | ownedInstance = await ArtifactOwned.new() 21 | }) 22 | 23 | // Take snapshot before each test and revert after each test 24 | beforeEach(async() => { 25 | snapshotId = await timeUtils.takeSnapshot() 26 | }) 27 | 28 | afterEach(async() => { 29 | await timeUtils.revertSnapshot(snapshotId.result) 30 | }) 31 | 32 | //**********// 33 | // Tests // 34 | //********// 35 | 36 | describe('isOwner', () => { 37 | context('Happy Path', async () => { 38 | it('Should return true if the owner is passed in', async () => { 39 | const isOwner = await ownedInstance.isOwner.call(OWNER) 40 | assert.equal(isOwner, true) 41 | }) 42 | it('Should return false if the owner is not passed in', async () => { 43 | const isOwner = await ownedInstance.isOwner.call(AUTHEREUM_OWNER) 44 | assert.equal(isOwner, false) 45 | }) 46 | }) 47 | }) 48 | describe('changeOwner', () => { 49 | context('Happy Path', async () => { 50 | it('Should allow the owner to change the owner', async () => { 51 | var { logs } = await ownedInstance.changeOwner(AUTHEREUM_OWNER) 52 | 53 | const isOwner = await ownedInstance.isOwner.call(AUTHEREUM_OWNER) 54 | assert.equal(isOwner, true) 55 | 56 | expectEvent.inLogs(logs, 'OwnerChanged', { _newOwner: AUTHEREUM_OWNER }) 57 | }) 58 | }) 59 | context('Non-Happy Path', async () => { 60 | it('Should not allow a non-owner to change the owner', async () => { 61 | await expectRevert(ownedInstance.changeOwner(AUTHEREUM_OWNER, { from: AUTHEREUM_OWNER }), constants.REVERT_MSG.O_MUST_BE_OWNER) 62 | }) 63 | it('Should not allow the owner to be set to 0', async () => { 64 | await expectRevert(ownedInstance.changeOwner(constants.ZERO_ADDRESS), constants.REVERT_MSG.O_NOT_NULL_ADDRESS) 65 | }) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/firewall/TransactionLimit.js: -------------------------------------------------------------------------------- 1 | // const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers') 2 | 3 | // const utils = require('../utils/utils') 4 | // const constants = require('../utils/constants.js') 5 | // const timeUtils = require('../utils/time.js') 6 | 7 | // const ArtifactBadTransaction = artifacts.require('BadTransaction') 8 | // const ArtifactAuthereumAccount = artifacts.require('AuthereumAccount') 9 | // const ArtifactAuthereumProxy = artifacts.require('AuthereumProxy') 10 | // const ArtifactAuthereumProxyFactory = artifacts.require('AuthereumProxyFactory') 11 | // const ArtifactAuthereumProxyAccountUpgrade = artifacts.require('UpgradeAccount') 12 | // const ArtifactAuthereumProxyAccountUpgradeWithInit = artifacts.require('UpgradeAccountWithInit') 13 | // const AuthereumEnsResolver = artifacts.require('AuthereumEnsResolver') 14 | // const AuthereumEnsManager = artifacts.require('AuthereumEnsManager') 15 | 16 | // contract('TransactionLimit', function (accounts) { 17 | // const OWNER = accounts[0] 18 | // const RELAYER = accounts[9] 19 | // const AUTH_KEYS = [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5], accounts[6]] 20 | // const RECEIVERS = [accounts[7]] 21 | // const ENS_OWNER = accounts[8] 22 | // const AUTHEREUM_OWNER = accounts[9] 23 | // const LOGIN_KEY = accounts[10] 24 | 25 | // // Test Params 26 | // let snapshotId 27 | 28 | // // Parameters 29 | // let label 30 | // let expectedSalt 31 | // let expectedCreationCodeHash 32 | // let nonce 33 | // let to 34 | // let value 35 | // let data 36 | // let gasPrice 37 | // let gasLimit 38 | // let transactionMessageHashSignature 39 | 40 | // // Addresses 41 | // let expectedAddress 42 | // let expectedAddressWithUpgrade 43 | // let expectedAddressWithUpgradeWithInit 44 | 45 | // // Logic Addresses 46 | // let authereumProxyFactoryLogicContract 47 | // let authereumAccountLogicContract 48 | // let authereumProxyAccountUpgradeLogicContract 49 | // let authereumProxyAccountUpgradeWithInitLogicContract 50 | 51 | // // Contract Instances 52 | // let authereumProxy 53 | // let authereumProxyAccount 54 | // let authereumProxyAccountUpgrade 55 | // let authereumProxyAccountUpgradeWithInit 56 | 57 | // before(async () => { 58 | // // Set up ENS defaults 59 | // const { authereumEnsManager } = await utils.setENSDefaults(AUTHEREUM_OWNER) 60 | 61 | // // Create Logic Contracts 62 | // authereumAccountLogicContract = await ArtifactAuthereumAccount.new() 63 | // authereumProxyFactoryLogicContract = await ArtifactAuthereumProxyFactory.new(_proxyInitCode, authereumEnsManager.address) 64 | // authereumProxyAccountUpgradeLogicContract = await ArtifactAuthereumProxyAccountUpgrade.new() 65 | // authereumProxyAccountUpgradeWithInitLogicContract = await ArtifactAuthereumProxyAccountUpgradeWithInit.new() 66 | 67 | // // Set up Authereum ENS Manager defaults 68 | // await utils.setAuthereumENSManagerDefaults(authereumEnsManager, AUTHEREUM_OWNER, authereumProxyFactoryLogicContract.address, constants.AUTHEREUM_PROXY_RUNTIME_CODE_HASH) 69 | 70 | // // Create default proxies 71 | // label = constants.DEFAULT_LABEL 72 | // expectedSalt = constants.SALT 73 | // expectedCreationCodeHash = constants.AUTHEREUM_PROXY_CREATION_CODE_HASH 74 | 75 | // expectedAddress = await utils.createDefaultProxy( 76 | // expectedSalt, accounts[0], authereumProxyFactoryLogicContract, 77 | // AUTH_KEYS[0], label, authereumAccountLogicContract.address 78 | // ) 79 | 80 | // // Wrap in truffle-contract 81 | // badContract = await ArtifactBadTransaction.new() 82 | // authereumProxy = await ArtifactAuthereumProxy.at(expectedAddress) 83 | // authereumProxyAccount = await ArtifactAuthereumAccount.at(expectedAddress) 84 | 85 | // // Send relayer ETH to use as a transaction fee 86 | // await authereumProxyAccount.sendTransaction({ value:constants.TWO_ETHER, from: AUTH_KEYS[0] }) 87 | 88 | // nonce = await authereumProxyAccount.nonce() 89 | // nonce = nonce.toNumber() 90 | 91 | // // Transaction parameters 92 | // nonce = 0 93 | // to = RECEIVERS[0] 94 | // value = constants.ONE_ETHER 95 | // data = '0x00' 96 | // }) 97 | 98 | // // Take snapshot before each test and revert after each test 99 | // beforeEach(async() => { 100 | // snapshotId = await timeUtils.takeSnapshot(); 101 | // }); 102 | 103 | // afterEach(async() => { 104 | // await timeUtils.revertSnapshot(snapshotId.result); 105 | // }); 106 | 107 | // //**********// 108 | // // Tests // 109 | // //********// 110 | 111 | // describe('changeDailyLimit', () => { 112 | // context('Happy Path', async () => { 113 | // it('Should allow an authkey to change the daily limit', async () => { 114 | // var { logs } = await authereumProxyAccount.changeDailyLimit(10, { from: AUTH_KEYS[0] }) 115 | // expectEvent.inLogs(logs, 'DailyLimitChanged', { authKey: AUTH_KEYS[0], newDailyLimit: '10' }) 116 | // const dailyLimit = await authereumProxyAccount.dailyLimit() 117 | // assert.equal(dailyLimit, '10') 118 | // }) 119 | // }) 120 | // context('Non-Happy Path', async () => { 121 | // it('Should not allow a non-authkey to change the daily limit', async () => { 122 | // await expectRevert(authereumProxyAccount.changeDailyLimit(10, { from: AUTH_KEYS[1] }), constants.REVERT_MSG.AUTH_KEY_INVALID) 123 | // }) 124 | // }) 125 | // }) 126 | // describe('getCurrentDay', () => { 127 | // context('Happy Path', async () => { 128 | // it('Should get the current day', async () => { 129 | // const day = await authereumProxyAccount.getCurrentDay({ from: AUTH_KEYS[0] }) 130 | 131 | // // Calculate current day 132 | // const currentBlock = await web3.eth.getBlockNumber() 133 | // const blockData = await web3.eth.getBlock(currentBlock) 134 | // const currentDay = Math.floor(blockData.timestamp / 86400) 135 | 136 | // assert.equal(Number(day), currentDay) 137 | // }) 138 | // it('Should get the current day, advance a few seconds, and return the same day', async () => { 139 | // const day = await authereumProxyAccount.getCurrentDay({ from: AUTH_KEYS[0] }) 140 | 141 | // // Calculate current day 142 | // const currentBlock = await web3.eth.getBlockNumber() 143 | // const blockData = await web3.eth.getBlock(currentBlock) 144 | // const currentDay = Math.floor(blockData.timestamp / 86400) 145 | 146 | // await timeUtils.increaseTime(2) 147 | // const sameDay = await authereumProxyAccount.getCurrentDay({ from: AUTH_KEYS[0] }) 148 | // assert.equal(Number(day), currentDay) 149 | // assert.equal(Number(sameDay), currentDay) 150 | // }) 151 | // it.skip('Should get the current day, advance a few days, and return the new day', async () => { 152 | // const day = await authereumProxyAccount.getCurrentDay({ from: AUTH_KEYS[0] }) 153 | 154 | // // Calculate current day 155 | // const currentBlock = await web3.eth.getBlockNumber() 156 | // const blockData = await web3.eth.getBlock(currentBlock) 157 | // const currentDay = Math.floor(blockData.timestamp / 86400) 158 | 159 | // const numDaysToPass = 3 160 | // const newDayTime = numDaysToPass * 86400 161 | // await timeUtils.increaseTime(newDayTime) 162 | // const newDay = await authereumProxyAccount.getCurrentDay({ from: AUTH_KEYS[0] }) 163 | // assert.equal(Number(day), currentDay) 164 | // assert.equal(Number(newDay), currentDay + numDaysToPass) 165 | // }) 166 | // }) 167 | // }) 168 | // describe('getIsWithinEthDailyTransactionLimit', () => { 169 | // context('Happy Path', async () => { 170 | // it('Should return true if a user is within their daily limits for the day', async () => { 171 | // const isWithinEthDailyTransactionLimit = await authereumProxyAccount.getIsWithinEthDailyTransactionLimit.call() 172 | // assert.equal(isWithinEthDailyTransactionLimit, true) 173 | // }) 174 | // }) 175 | // }) 176 | // describe('getWillBeWithinEthDailyTransactionLimit', () => { 177 | // context('Happy Path', async () => { 178 | // it('Should return true if a user is within their daily limits for the day', async () => { 179 | // const isWithinEthDailyTransactionLimit = await authereumProxyAccount.getWillBeWithinEthDailyTransactionLimit.call(constants.ONE_ETHER) 180 | // assert.equal(isWithinEthDailyTransactionLimit, true) 181 | // }) 182 | // }) 183 | // context('Non-Happy Path', async () => { 184 | // it('Should return false if a certain value will put a user out of their daily limit', async () => { 185 | // const isWithinEthDailyTransactionLimit = await authereumProxyAccount.getWillBeWithinEthDailyTransactionLimit.call(constants.TWENTY_ETHER) 186 | // assert.equal(isWithinEthDailyTransactionLimit, false) 187 | // }) 188 | // }) 189 | // }) 190 | // }) 191 | -------------------------------------------------------------------------------- /test/test/GanacheEnvironment.js: -------------------------------------------------------------------------------- 1 | const utils = require('../utils/utils') 2 | const constants = require('../utils/constants.js') 3 | const timeUtils = require('../utils/time.js') 4 | 5 | const ArtifactAuthereumAccount = artifacts.require('AuthereumAccount') 6 | const ArtifactAuthereumProxy = artifacts.require('AuthereumProxy') 7 | const ArtifactAuthereumProxyFactory = artifacts.require('AuthereumProxyFactory') 8 | const ArtifactTestERC20 = artifacts.require('TestERC20') 9 | const ArtifactAuthereumRecoveryModule = artifacts.require('AuthereumRecoveryModule') 10 | 11 | contract('GanacheEnvironment', function (accounts) { 12 | const AUTHEREUM_OWNER = accounts[0] 13 | const AUTH_KEYS = [accounts[1], accounts[2], accounts[3], accounts[4]] 14 | 15 | // Token Params 16 | const DEFAULT_TOKEN_SUPPLY = constants.DEFAULT_TOKEN_SUPPLY 17 | const DEFAULT_TOKEN_DECIMALS = constants.DEFAULT_TOKEN_DECIMALS 18 | 19 | // Testing params 20 | let beforeAllSnapshotId 21 | 22 | // Parameters 23 | let _ensRegistry 24 | let _ensReverseRegistrar 25 | let _authereumEnsResolver 26 | let _authereumEnsManager 27 | let label 28 | let daiToken 29 | let saiToken 30 | let batToken 31 | let gntToken 32 | 33 | // Addresses 34 | let expectedAddress 35 | 36 | // Logic Addresses 37 | let authereumProxyFactoryLogicContract 38 | let authereumAccountLogicContract 39 | 40 | // Contract Instances 41 | let authereumRecoveryModule 42 | let authereumProxyAccount 43 | 44 | before(async () => { 45 | // Take snapshot to reset to a known state 46 | // This is required due to the deployment of the 1820 contract 47 | beforeAllSnapshotId = await timeUtils.takeSnapshot() 48 | 49 | // Deploy the recovery module 50 | authereumRecoveryModule = await ArtifactAuthereumRecoveryModule.new() 51 | 52 | // Deploy the 1820 contract 53 | await utils.deploy1820Contract(AUTHEREUM_OWNER) 54 | 55 | // Set up ENS defaults 56 | const { ensRegistry, ensReverseRegistrar, authereumEnsResolver, authereumEnsManager }= await utils.setENSDefaults(AUTHEREUM_OWNER) 57 | _ensRegistry = ensRegistry 58 | _ensReverseRegistrar = ensReverseRegistrar 59 | _authereumEnsResolver = authereumEnsResolver 60 | _authereumEnsManager = authereumEnsManager 61 | 62 | // Create Logic Contracts 63 | authereumAccountLogicContract = await ArtifactAuthereumAccount.new() 64 | const _proxyInitCode = await utils.getProxyBytecode() 65 | authereumProxyFactoryLogicContract = await ArtifactAuthereumProxyFactory.new(_proxyInitCode, authereumEnsManager.address) 66 | 67 | // Set up Authereum ENS Manager defaults 68 | await utils.setAuthereumENSManagerDefaults(authereumEnsManager, AUTHEREUM_OWNER, authereumProxyFactoryLogicContract.address, constants.AUTHEREUM_PROXY_RUNTIME_CODE_HASH) 69 | 70 | // Create default proxies 71 | label = constants.DEFAULT_LABEL 72 | expectedSalt = constants.SALT 73 | expectedCreationCodeHash = constants.AUTHEREUM_PROXY_CREATION_CODE_HASH 74 | 75 | expectedAddress = await utils.createDefaultProxy( 76 | expectedSalt, accounts[0], authereumProxyFactoryLogicContract, 77 | AUTH_KEYS[0], label, authereumAccountLogicContract.address 78 | ) 79 | 80 | // Wrap in truffle-contract 81 | authereumProxyAccount = await ArtifactAuthereumAccount.at(expectedAddress) 82 | 83 | // Create 4 tokens 84 | daiToken = await ArtifactTestERC20.new([AUTHEREUM_OWNER], DEFAULT_TOKEN_SUPPLY, 'AUTH0', 'AuthereumToken0', DEFAULT_TOKEN_DECIMALS) 85 | saiToken = await ArtifactTestERC20.new([AUTHEREUM_OWNER], DEFAULT_TOKEN_SUPPLY, 'AUTH1', 'AuthereumToken1', DEFAULT_TOKEN_DECIMALS) 86 | batToken = await ArtifactTestERC20.new([AUTHEREUM_OWNER], DEFAULT_TOKEN_SUPPLY, 'AUTH2', 'AuthereumToken2', DEFAULT_TOKEN_DECIMALS) 87 | gntToken = await ArtifactTestERC20.new([AUTHEREUM_OWNER], DEFAULT_TOKEN_SUPPLY, 'AUTH3', 'AuthereumToken3', DEFAULT_TOKEN_DECIMALS) 88 | }) 89 | 90 | after(async() => { 91 | await timeUtils.revertSnapshot(beforeAllSnapshotId.result) 92 | }) 93 | 94 | //**********// 95 | // Tests // 96 | //********// 97 | 98 | describe('log', () => { 99 | it('Should log all values', async () => { 100 | const logs = ` 101 | EOAs 102 | ==== 103 | Authereum Owner (owns everything): ${AUTHEREUM_OWNER} 104 | Authereum Account 0 Auth Key: ${AUTH_KEYS[0]} 105 | 106 | Authereum Accounts 107 | ================== 108 | Authereum Account 0 (a user): ${authereumProxyAccount.address} 109 | Authereum Account 0 Name: ${constants.DEFAULT_LABEL} 110 | 111 | Logic Contracts 112 | =============== 113 | Authereum Account Logic Contract: ${authereumAccountLogicContract.address} 114 | Proxy Factory Logic Contract: ${authereumProxyFactoryLogicContract.address} 115 | 116 | Tokens 117 | ====== 118 | DAI: ${daiToken.address} 119 | SAI: ${saiToken.address} 120 | BAT: ${batToken.address} 121 | GNT: ${gntToken.address} 122 | 123 | ENS 124 | === 125 | ENS Registry: ${_ensRegistry.address} 126 | ENS Reverse Registrar: ${_ensReverseRegistrar.address} 127 | Authereum ENS Resolver: ${_authereumEnsResolver.address} 128 | Authereum ENS Manager: ${_authereumEnsManager.address} 129 | ` 130 | console.log(logs) 131 | 132 | console.log(' Ganache Accounts') 133 | console.log(' ================') 134 | for (const [index, account] of accounts.entries()) { 135 | console.log(` ${index}: ${account}`); 136 | } 137 | 138 | }) 139 | }) 140 | }) 141 | -------------------------------------------------------------------------------- /test/test/ParseCallData.js: -------------------------------------------------------------------------------- 1 | const { expectRevert } = require('@openzeppelin/test-helpers') 2 | 3 | const utils = require('../utils/utils') 4 | const constants = require('../utils/constants.js') 5 | const timeUtils = require('../utils/time.js') 6 | 7 | const ArtifactParseCallData = artifacts.require('ParseCallData') 8 | 9 | contract('ParseCallData', function (accounts) { 10 | const OWNER = accounts[0] 11 | const ENS_OWNER = accounts[8] 12 | 13 | // Test Params 14 | let snapshotId 15 | 16 | // Instances 17 | let parseCallDataInstance 18 | 19 | before(async () => { 20 | parseCallDataInstance = await ArtifactParseCallData.new() 21 | }) 22 | 23 | // Take snapshot before each test and revert after each test 24 | beforeEach(async() => { 25 | snapshotId = await timeUtils.takeSnapshot() 26 | }) 27 | 28 | afterEach(async() => { 29 | await timeUtils.revertSnapshot(snapshotId.result) 30 | }) 31 | 32 | //**********// 33 | // Tests // 34 | //********// 35 | 36 | describe('Happy Path', () => { 37 | it('Should correctly parse arbitrary data', async () => { 38 | const _parameterCount = 2 39 | const _addr = OWNER 40 | const _num = 123 41 | const _data = web3.eth.abi.encodeFunctionCall({ 42 | name: 'testFunction', 43 | type: 'function', 44 | inputs: [{ 45 | type: 'address', 46 | name: '_addr' 47 | },{ 48 | type: 'uint256', 49 | name: '_num' 50 | }] 51 | }, [_addr, _num]) 52 | 53 | const _parsedData = await parseCallDataInstance.callParseCalldata(_data, _parameterCount) 54 | 55 | const _expectedFunctionSig = '0x5d0da2cf' 56 | const _expectedFunctionParams = [ 57 | await web3.eth.abi.encodeParameter('address', _addr), 58 | await web3.eth.abi.encodeParameter('uint256', _num) 59 | ] 60 | 61 | expectData(_parsedData, _expectedFunctionSig, _expectedFunctionParams) 62 | }) 63 | it('Should correctly parse arbitrary data with both static and dynamic data', async () => { 64 | const _parameterCount = 6 65 | const _addrOne = OWNER 66 | const _numOne = 123 67 | const _nameOne = 'shane' 68 | const _numTwo = 456 69 | const _nameTwo = utils.stringToBytes('abc') 70 | const _addrTwo = ENS_OWNER 71 | 72 | const _data = web3.eth.abi.encodeFunctionCall({ 73 | name: 'testFunction', 74 | type: 'function', 75 | inputs: [{ 76 | type: 'address', 77 | name: '_addrOne' 78 | },{ 79 | type: 'uint256', 80 | name: '_numOne' 81 | },{ 82 | type: 'string', 83 | name: '_nameOne' 84 | },{ 85 | type: 'uint256', 86 | name: '_numTwo' 87 | },{ 88 | type: 'bytes', 89 | name: '_nameTwo' 90 | },{ 91 | type: 'address', 92 | name: '_addrTwo' 93 | }] 94 | }, [_addrOne, _numOne, _nameOne, _numTwo, _nameTwo, _addrTwo]) 95 | 96 | const _parsedData = await parseCallDataInstance.callParseCalldata(_data, _parameterCount) 97 | 98 | const _expectedFunctionSig = '0x11f716c9' 99 | const _expectedFunctionParams = [ 100 | await web3.eth.abi.encodeParameter('address', _addrOne), 101 | await web3.eth.abi.encodeParameter('uint256', _numOne), 102 | await web3.eth.abi.encodeParameter('string', _nameOne), 103 | await web3.eth.abi.encodeParameter('uint256', _numTwo), 104 | await web3.eth.abi.encodeParameter('bytes', _nameTwo), 105 | await web3.eth.abi.encodeParameter('address', _addrTwo) 106 | ] 107 | 108 | expectData(_parsedData, _expectedFunctionSig, _expectedFunctionParams) 109 | }) 110 | it('Should correctly parse data for a token transfer', async () => { 111 | const _parameterCount = 2 112 | const _to = OWNER 113 | const _value = 123 114 | const _data = web3.eth.abi.encodeFunctionCall({ 115 | name: 'transfer', 116 | type: 'function', 117 | inputs: [{ 118 | type: 'address', 119 | name: '_to' 120 | },{ 121 | type: 'uint256', 122 | name: '_value' 123 | }] 124 | }, [_to, _value]) 125 | 126 | const _parsedData = await parseCallDataInstance.callParseCalldata(_data, _parameterCount) 127 | 128 | const _expectedFunctionSig = '0xa9059cbb' 129 | const _expectedFunctionParams = [ 130 | await web3.eth.abi.encodeParameter('address', _to), 131 | await web3.eth.abi.encodeParameter('uint256', _value) 132 | ] 133 | 134 | expectData(_parsedData, _expectedFunctionSig, _expectedFunctionParams) 135 | }) 136 | it('Should correctly parse data for a uniswap transfer (tokenToEthSwapInput)', async () => { 137 | const _parameterCount = 2 138 | const _tokensSold = 1000000000000 139 | const _minEth = 1000000 140 | const _deadline = 123 141 | const _data = web3.eth.abi.encodeFunctionCall({ 142 | name: 'tokenToEthSwapInput', 143 | type: 'function', 144 | inputs: [{ 145 | type: 'uint256', 146 | name: '_tokensSold' 147 | },{ 148 | type: 'uint256', 149 | name: '_minEth' 150 | },{ 151 | type: 'uint256', 152 | name: '_deadline' 153 | 154 | }] 155 | }, [_tokensSold, _minEth, _deadline]) 156 | 157 | const _parsedData = await parseCallDataInstance.callParseCalldata(_data, _parameterCount) 158 | 159 | const _expectedFunctionSig = '0x95e3c50b' 160 | const _expectedFunctionParams = [ 161 | await web3.eth.abi.encodeParameter('uint256', _tokensSold), 162 | await web3.eth.abi.encodeParameter('uint256', _minEth), 163 | await web3.eth.abi.encodeParameter('uint256', _deadline) 164 | ] 165 | 166 | expectData(_parsedData, _expectedFunctionSig, _expectedFunctionParams) 167 | }) 168 | it('Should correctly parse data for an empty function selector', async () => { 169 | // NOTE: This case is handled in our contracts by a different function. 170 | // The _parseCallData function cannot ever receive a fallback, so this 171 | // test is invalid 172 | }) 173 | }) 174 | describe('Non-happy Path', () => { 175 | it('Should not allow data to be too short', async () => { 176 | const _parameterCount = 2 177 | const _addr = OWNER 178 | const _num = 123 179 | let _data = web3.eth.abi.encodeFunctionCall({ 180 | name: 'testFunction', 181 | type: 'function', 182 | inputs: [{ 183 | type: 'address', 184 | name: '_addr' 185 | },{ 186 | type: 'uint256', 187 | name: '_num' 188 | }] 189 | }, [_addr, _num]) 190 | 191 | _data = _data.substring(0, _data.length - 2) 192 | await expectRevert(parseCallDataInstance.callParseCalldata(_data, _parameterCount), constants.REVERT_MSG.ADKM_TRANSACTION_DATA_TOO_SHORT) 193 | }) 194 | it('Should allow data to be too long (1 byte)', async () => { 195 | const _parameterCount = 2 196 | const _addr = OWNER 197 | const _num = 123 198 | let _data = web3.eth.abi.encodeFunctionCall({ 199 | name: 'testFunction', 200 | type: 'function', 201 | inputs: [{ 202 | type: 'address', 203 | name: '_addr' 204 | },{ 205 | type: 'uint256', 206 | name: '_num' 207 | }] 208 | }, [_addr, _num]) 209 | 210 | _data = _data + 'a' 211 | const _parsedData = await parseCallDataInstance.callParseCalldata(_data, _parameterCount) 212 | 213 | const _expectedFunctionSig = '0x05d0da2c' 214 | 215 | // NOTE: Because of the odd-length data, everything gets shifted right 216 | // Because of this, we will insert custom data in the expected data 217 | const _expectedFunctionParams = [ 218 | '0xf00000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c', 219 | '0x1000000000000000000000000000000000000000000000000000000000000007' 220 | ] 221 | 222 | expectData(_parsedData, _expectedFunctionSig, _expectedFunctionParams) 223 | }) 224 | it('Should allow data to be too long (2 bytes)', async () => { 225 | const _parameterCount = 2 226 | const _addr = OWNER 227 | const _num = 123 228 | let _data = web3.eth.abi.encodeFunctionCall({ 229 | name: 'testFunction', 230 | type: 'function', 231 | inputs: [{ 232 | type: 'address', 233 | name: '_addr' 234 | },{ 235 | type: 'uint256', 236 | name: '_num' 237 | }] 238 | }, [_addr, _num]) 239 | 240 | _data = _data + 'ab' 241 | const _parsedData = await parseCallDataInstance.callParseCalldata(_data, _parameterCount) 242 | 243 | const _expectedFunctionSig = '0x5d0da2cf' 244 | const _expectedFunctionParams = [ 245 | await web3.eth.abi.encodeParameter('address', _addr), 246 | await web3.eth.abi.encodeParameter('uint256', _num) 247 | ] 248 | 249 | expectData(_parsedData, _expectedFunctionSig, _expectedFunctionParams) 250 | }) 251 | }) 252 | 253 | function expectData(parsedData, functionSig, functionParams) { 254 | const parsedFunctionSig = parsedData[0] 255 | const parsedParams = parsedData[1] 256 | 257 | assert.equal(parsedFunctionSig, functionSig) 258 | 259 | for (let i = 0; i < parsedParams.length; i++) { 260 | if (functionParams[i].length > 66) continue 261 | assert.equal(parsedParams[i], functionParams[i]) 262 | } 263 | } 264 | }) 265 | -------------------------------------------------------------------------------- /test/upgradeability/AuthereumEnsResolverProxy.js: -------------------------------------------------------------------------------- 1 | const { expectRevert } = require('@openzeppelin/test-helpers') 2 | 3 | const utils = require('../utils/utils') 4 | const constants = require('../utils/constants.js') 5 | const timeUtils = require('../utils/time.js') 6 | 7 | const ArtifactBadTransaction = artifacts.require('BadTransaction') 8 | const ArtifactAuthereumAccount = artifacts.require('AuthereumAccount') 9 | const ArtifactAuthereumProxy = artifacts.require('AuthereumProxy') 10 | const ArtifactAuthereumProxyFactory = artifacts.require('AuthereumProxyFactory') 11 | const ArtifactAuthereumProxyAccountUpgrade = artifacts.require('UpgradeAccount') 12 | const ArtifactAuthereumProxyAccountUpgradeWithInit = artifacts.require('UpgradeAccountWithInit') 13 | const ArtifactAuthereumEnsResolver = artifacts.require('AuthereumEnsResolver') 14 | const ArtifactAuthereumEnsResolverProxy = artifacts.require('AuthereumEnsResolverProxy') 15 | const ArtifactAuthereumRecoveryModule = artifacts.require('AuthereumRecoveryModule') 16 | const ArtifactERC1820Registry = artifacts.require('ERC1820Registry') 17 | 18 | contract('AuthereumEnsResolverProxy', function (accounts) { 19 | const AUTHEREUM_OWNER = accounts[0] 20 | const ENS_OWNER = accounts[8] 21 | const RELAYER = accounts[9] 22 | const AUTH_KEYS = [accounts[1], accounts[2], accounts[3], accounts[4]] 23 | const RECEIVERS = [accounts[5], accounts[6], accounts[7]] 24 | 25 | // Test Params 26 | let beforeAllSnapshotId 27 | let snapshotId 28 | 29 | // Parameters 30 | let authereumProxyFactory 31 | let authereumAccount 32 | let authereumProxy 33 | 34 | // Proxy Creation Params 35 | let expectedSalt 36 | let logicAddress 37 | let expectedAuthKey 38 | let expectedAuthereumEnsManager 39 | let expectedLabel 40 | let data 41 | let erc1820Registry 42 | 43 | before(async () => { 44 | // Take snapshot to reset to a known state 45 | // This is required due to the deployment of the 1820 contract 46 | beforeAllSnapshotId = await timeUtils.takeSnapshot() 47 | 48 | // Deploy the recovery module 49 | authereumRecoveryModule = await ArtifactAuthereumRecoveryModule.new() 50 | 51 | // Deploy the 1820 contract 52 | await utils.deploy1820Contract(AUTHEREUM_OWNER) 53 | 54 | // Set up ENS defaults 55 | const { ensRegistry, authereumEnsManager } = await utils.setENSDefaults(AUTHEREUM_OWNER) 56 | 57 | // Create Logic Contracts 58 | authereumEnsResolverLogicContract = await ArtifactAuthereumEnsResolver.new(ensRegistry.address) 59 | authereumAccountLogicContract = await ArtifactAuthereumAccount.new() 60 | const _proxyInitCode = await utils.getProxyBytecode() 61 | authereumProxyFactoryLogicContract = await ArtifactAuthereumProxyFactory.new(_proxyInitCode, authereumEnsManager.address) 62 | authereumProxyAccountUpgradeLogicContract = await ArtifactAuthereumProxyAccountUpgrade.new() 63 | authereumProxyAccountUpgradeWithInitLogicContract = await ArtifactAuthereumProxyAccountUpgradeWithInit.new() 64 | 65 | // Set up Authereum ENS Manager defaults 66 | await utils.setAuthereumENSManagerDefaults(authereumEnsManager, AUTHEREUM_OWNER, authereumProxyFactoryLogicContract.address, constants.AUTHEREUM_PROXY_RUNTIME_CODE_HASH) 67 | 68 | // Create default proxies 69 | label = constants.DEFAULT_LABEL 70 | expectedSalt = constants.SALT 71 | expectedCreationCodeHash = constants.AUTHEREUM_PROXY_CREATION_CODE_HASH 72 | 73 | expectedAddress = await utils.createDefaultProxy( 74 | expectedSalt, accounts[0], authereumProxyFactoryLogicContract, 75 | AUTH_KEYS[0], label, authereumAccountLogicContract.address 76 | ) 77 | 78 | // Set up IERC1820 contract 79 | erc1820Registry = await ArtifactERC1820Registry.at(constants.ERC1820_REGISTRY_ADDRESS) 80 | 81 | // Wrap in truffle-contract 82 | badContract = await ArtifactBadTransaction.new() 83 | authereumProxy = await ArtifactAuthereumProxy.at(expectedAddress) 84 | authereumProxyAccount = await ArtifactAuthereumAccount.at(expectedAddress) 85 | 86 | // Create Authereum ENS Resolver Proxy 87 | authereumEnsResolverProxy = await ArtifactAuthereumEnsResolverProxy.new(authereumEnsResolverLogicContract.address) 88 | 89 | // Handle post-proxy deployment 90 | await authereumProxyAccount.sendTransaction({ value:constants.TWO_ETHER, from: AUTH_KEYS[0] }) 91 | await utils.setAuthereumRecoveryModule(authereumProxyAccount, authereumRecoveryModule.address, AUTH_KEYS[0]) 92 | }) 93 | 94 | after(async() => { 95 | await timeUtils.revertSnapshot(beforeAllSnapshotId.result) 96 | }) 97 | 98 | // Take snapshot before each test and revert after each test 99 | beforeEach(async() => { 100 | snapshotId = await timeUtils.takeSnapshot() 101 | }) 102 | 103 | afterEach(async() => { 104 | await timeUtils.revertSnapshot(snapshotId.result) 105 | }) 106 | 107 | //**********// 108 | // Tests // 109 | //********// 110 | 111 | describe('fallback', () => { 112 | context('Non-Happy Path', async () => { 113 | it.skip('Should allow an arbitrary person to call the fallback but not change any state on behalf of the proxy owner', async () => { 114 | }) 115 | }) 116 | }) 117 | describe('implementation', () => { 118 | context('Happy Path', async () => { 119 | it('Should confirm the implementation address after the creation of a proxy', async () => { 120 | const implementationAddress = await authereumEnsResolverProxy.implementation() 121 | assert.equal(authereumEnsResolverLogicContract.address, implementationAddress) 122 | }) 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /test/upgradeability/AuthereumProxy.js: -------------------------------------------------------------------------------- 1 | const utils = require('../utils/utils') 2 | const constants = require('../utils/constants.js') 3 | const timeUtils = require('../utils/time.js') 4 | 5 | const ArtifactBadTransaction = artifacts.require('BadTransaction') 6 | const ArtifactAuthereumAccount = artifacts.require('AuthereumAccount') 7 | const ArtifactAuthereumProxy = artifacts.require('AuthereumProxy') 8 | const ArtifactAuthereumProxyFactory = artifacts.require('AuthereumProxyFactory') 9 | const ArtifactAuthereumProxyAccountUpgrade = artifacts.require('UpgradeAccount') 10 | const ArtifactAuthereumProxyAccountUpgradeWithInit = artifacts.require('UpgradeAccountWithInit') 11 | const ArtifactAuthereumRecoveryModule = artifacts.require('AuthereumRecoveryModule') 12 | const ArtifactERC1820Registry = artifacts.require('ERC1820Registry') 13 | 14 | contract('AuthereumProxy', function (accounts) { 15 | const AUTHEREUM_OWNER = accounts[0] 16 | const ENS_OWNER = accounts[8] 17 | const RELAYER = accounts[9] 18 | const AUTH_KEYS = [accounts[1], accounts[2], accounts[3], accounts[4]] 19 | const RECEIVERS = [accounts[5], accounts[6], accounts[7]] 20 | 21 | // Test Params 22 | let beforeAllSnapshotId 23 | let snapshotId 24 | 25 | // Parameters 26 | let authereumProxyFactory 27 | let authereumAccount 28 | let authereumProxy 29 | 30 | // Proxy Creation Params 31 | let expectedSalt 32 | let logicAddress 33 | let expectedAuthKey 34 | let expectedAuthereumEnsManager 35 | let expectedLabel 36 | let data 37 | let erc1820Registry 38 | 39 | before(async () => { 40 | // Take snapshot to reset to a known state 41 | // This is required due to the deployment of the 1820 contract 42 | beforeAllSnapshotId = await timeUtils.takeSnapshot() 43 | 44 | // Deploy the recovery module 45 | authereumRecoveryModule = await ArtifactAuthereumRecoveryModule.new() 46 | 47 | // Deploy the 1820 contract 48 | await utils.deploy1820Contract(AUTHEREUM_OWNER) 49 | 50 | // Set up ENS defaults 51 | const { authereumEnsManager } = await utils.setENSDefaults(AUTHEREUM_OWNER) 52 | 53 | // Create Logic Contracts 54 | authereumAccountLogicContract = await ArtifactAuthereumAccount.new() 55 | const _proxyInitCode = await utils.getProxyBytecode() 56 | authereumProxyFactoryLogicContract = await ArtifactAuthereumProxyFactory.new(_proxyInitCode, authereumEnsManager.address) 57 | authereumProxyAccountUpgradeLogicContract = await ArtifactAuthereumProxyAccountUpgrade.new() 58 | authereumProxyAccountUpgradeWithInitLogicContract = await ArtifactAuthereumProxyAccountUpgradeWithInit.new() 59 | 60 | // Set up Authereum ENS Manager defaults 61 | await utils.setAuthereumENSManagerDefaults(authereumEnsManager, AUTHEREUM_OWNER, authereumProxyFactoryLogicContract.address, constants.AUTHEREUM_PROXY_RUNTIME_CODE_HASH) 62 | 63 | // Create default proxies 64 | label = constants.DEFAULT_LABEL 65 | expectedSalt = constants.SALT 66 | expectedCreationCodeHash = constants.AUTHEREUM_PROXY_CREATION_CODE_HASH 67 | 68 | expectedAddress = await utils.createDefaultProxy( 69 | expectedSalt, accounts[0], authereumProxyFactoryLogicContract, 70 | AUTH_KEYS[0], label, authereumAccountLogicContract.address 71 | ) 72 | 73 | // Set up IERC1820 contract 74 | erc1820Registry = await ArtifactERC1820Registry.at(constants.ERC1820_REGISTRY_ADDRESS) 75 | 76 | // Wrap in truffle-contract 77 | badContract = await ArtifactBadTransaction.new() 78 | authereumProxy = await ArtifactAuthereumProxy.at(expectedAddress) 79 | authereumProxyAccount = await ArtifactAuthereumAccount.at(expectedAddress) 80 | 81 | // Handle post-proxy deployment 82 | await authereumProxyAccount.sendTransaction({ value:constants.TWO_ETHER, from: AUTH_KEYS[0] }) 83 | await utils.setAuthereumRecoveryModule(authereumProxyAccount, authereumRecoveryModule.address, AUTH_KEYS[0]) 84 | }) 85 | 86 | after(async() => { 87 | await timeUtils.revertSnapshot(beforeAllSnapshotId.result) 88 | }) 89 | 90 | // Take snapshot before each test and revert after each test 91 | beforeEach(async() => { 92 | snapshotId = await timeUtils.takeSnapshot() 93 | }) 94 | 95 | afterEach(async() => { 96 | await timeUtils.revertSnapshot(snapshotId.result) 97 | }) 98 | 99 | //**********// 100 | // Tests // 101 | //********// 102 | 103 | describe('fallback', () => { 104 | context('Non-Happy Path', async () => { 105 | it.skip('Should allow an arbitrary person to call the fallback but not change any state on behalf of the proxy owner', async () => { 106 | }) 107 | }) 108 | }) 109 | describe('implementation', () => { 110 | context('Happy Path', async () => { 111 | it('Should confirm the implementation address via the data at the storage slot after the creation of a proxy', async () => { 112 | const _implementationAddress = await utils.getImplementationAddressFromStorageSlot(authereumProxy.address) 113 | assert.equal(authereumAccountLogicContract.address, _implementationAddress) 114 | }) 115 | }) 116 | context('Non-Happy Path', async () => { 117 | it('Should not confirm the implementation address after the creation of a proxy because the getter lives on the implementation and not the proxy', async () => { 118 | const _expectedError = 'TypeError: authereumProxy.implementation is not a function' 119 | try{ 120 | await authereumProxy.implementation() 121 | } catch (err) { 122 | assert.equal(err, _expectedError) 123 | } 124 | }) 125 | }) 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /test/utils/time.js: -------------------------------------------------------------------------------- 1 | // This module is used strictly for tests 2 | function send(method, params = []) { 3 | return new Promise((resolve, reject) => { 4 | // eslint-disable-next-line no-undef 5 | web3.currentProvider.send({ 6 | jsonrpc: '2.0', 7 | id: Date.now(), 8 | method, 9 | params 10 | }, (err, res) => { 11 | return err ? reject(err) : resolve(res) 12 | }) 13 | }) 14 | } 15 | 16 | const takeSnapshot = async () => { 17 | return await send('evm_snapshot') 18 | } 19 | 20 | const revertSnapshot = async (id) => { 21 | await send('evm_revert', [id]) 22 | } 23 | 24 | const mineBlock = async (timestamp) => { 25 | await send('evm_mine', [timestamp]) 26 | } 27 | 28 | const increaseTime = async (seconds) => { 29 | await send('evm_increaseTime', [seconds]) 30 | await mineBlock() 31 | } 32 | 33 | const minerStop = async () => { 34 | await send('miner_stop', []) 35 | } 36 | 37 | const minerStart = async () => { 38 | await send('miner_start', []) 39 | } 40 | 41 | module.exports = { 42 | takeSnapshot, 43 | revertSnapshot, 44 | mineBlock, 45 | minerStop, 46 | minerStart, 47 | increaseTime, 48 | } 49 | -------------------------------------------------------------------------------- /test/validation/AuthereumLoginKeyValidator.js: -------------------------------------------------------------------------------- 1 | const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers') 2 | const timeUtils = require('../utils/time.js') 3 | 4 | const utils = require('../utils/utils') 5 | const constants = require('../utils/constants.js') 6 | 7 | const ArtifactAuthereumLoginKeyValidator = artifacts.require('AuthereumLoginKeyValidator') 8 | 9 | contract('AuthereumLoginKeyValidator', function (accounts) { 10 | let snapshotId 11 | const relayers = [accounts[0], accounts[1]] 12 | const maliciousAccount = accounts[1] 13 | 14 | let AuthereumLoginKeyValidator 15 | before(async () => { 16 | AuthereumLoginKeyValidator = await ArtifactAuthereumLoginKeyValidator.new() 17 | }) 18 | 19 | // Take snapshot before each test and revert after each test 20 | beforeEach(async() => { 21 | snapshotId = await timeUtils.takeSnapshot() 22 | }) 23 | 24 | afterEach(async() => { 25 | await timeUtils.revertSnapshot(snapshotId.result) 26 | }) 27 | 28 | describe('name', () => { 29 | context('Happy path', () => { 30 | it('Should return the name of the contract', async () => { 31 | const _name = await AuthereumLoginKeyValidator.name.call() 32 | assert.equal(_name, constants.CONTRACTS.AUTHEREUM_LOGIN_KEY_VALIDATOR.NAME) 33 | }) 34 | }) 35 | }) 36 | describe('version', () => { 37 | context('Happy path', () => { 38 | it('Should return the version of the contract', async () => { 39 | const _version = await AuthereumLoginKeyValidator.version.call() 40 | const _contractVersions = constants.CONTRACTS.AUTHEREUM_LOGIN_KEY_VALIDATOR.VERSIONS 41 | const _latestVersionIndex = _contractVersions.length - 1 42 | assert.equal(_version, _contractVersions[_latestVersionIndex]) 43 | }) 44 | }) 45 | }) 46 | describe('validateTransactions', () => { 47 | context('Happy path', () => { 48 | it('should return true for a valid transaction', async () => { 49 | await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 50 | 51 | let error 52 | try { 53 | await AuthereumLoginKeyValidator.validateTransactions([], constants.DEFAULT_LOGIN_KEY_EXPIRATION_TIME_DATA, relayers[0]) 54 | } catch (err) { 55 | error = err 56 | } 57 | expect(error).to.eq(undefined) 58 | }) 59 | }) 60 | context('Non-Happy path', () => { 61 | it('should revert if login key is expired', async () => { 62 | await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 63 | 64 | const loginKeyExpirationTimeData = web3.eth.abi.encodeParameter('uint256', 1) 65 | 66 | await expectRevert( 67 | AuthereumLoginKeyValidator.validateTransactions([], loginKeyExpirationTimeData, relayers[0]), 68 | constants.REVERT_MSG.LKV_LOGIN_KEY_EXPIRED 69 | ) 70 | }) 71 | 72 | it('should revert if relayer is not allowed', async () => { 73 | await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 74 | 75 | await expectRevert( 76 | AuthereumLoginKeyValidator.validateTransactions([], constants.DEFAULT_LOGIN_KEY_EXPIRATION_TIME_DATA, accounts[1]), 77 | constants.REVERT_MSG.LKV_INVALID_RELAYER 78 | ) 79 | }) 80 | 81 | it('should revert with additional data appended to the restrictions data (1 byte)', async () => { 82 | // This reverts because our off-chain provider prepends `0` to the 83 | // _beginning_ of data if there is an odd-length data string 84 | await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 85 | 86 | const _data = constants.DEFAULT_LOGIN_KEY_EXPIRATION_TIME_DATA + 'a' 87 | 88 | await expectRevert( 89 | AuthereumLoginKeyValidator.validateTransactions([], _data, relayers[0]), 90 | constants.REVERT_MSG.LKV_LOGIN_KEY_EXPIRED 91 | ) 92 | }) 93 | 94 | it('should behave as expected with additional data appended to the restrictions data (2 byte)', async () => { 95 | // This works because the additional data is ignored by the contract 96 | await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 97 | 98 | const _data = constants.DEFAULT_LOGIN_KEY_EXPIRATION_TIME_DATA + 'ab' 99 | 100 | let error 101 | try { 102 | await AuthereumLoginKeyValidator.validateTransactions([], _data, relayers[0]) 103 | } catch (err) { 104 | error = err 105 | } 106 | expect(error).to.eq(undefined) 107 | }) 108 | 109 | it('should revert with additional data deleted from the restrictions data (1 byte)', async () => { 110 | // This reverts because our off-chain provider prepends `0` to the 111 | // _beginning_ of data if there is an odd-length data string 112 | await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 113 | 114 | const _data = constants.DEFAULT_LOGIN_KEY_EXPIRATION_TIME_DATA.substring(0, constants.DEFAULT_LOGIN_KEY_EXPIRATION_TIME_DATA.length - 1) 115 | 116 | await expectRevert( 117 | AuthereumLoginKeyValidator.validateTransactions([], _data, relayers[0]), 118 | constants.REVERT_MSG.LKV_LOGIN_KEY_EXPIRED 119 | ) 120 | }) 121 | 122 | it('should behave as expected with additional data appended to to the restrictions data (2 byte)', async () => { 123 | // This does not work because the decode function in the contract 124 | // does not see this as a valid `uint256` 125 | await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 126 | 127 | const _data = constants.DEFAULT_LOGIN_KEY_EXPIRATION_TIME_DATA.substring(0, constants.DEFAULT_LOGIN_KEY_EXPIRATION_TIME_DATA.length - 2) 128 | 129 | await expectRevert( 130 | AuthereumLoginKeyValidator.validateTransactions([], _data, relayers[0]), 131 | constants.REVERT_MSG.GENERAL_REVERT 132 | ) 133 | }) 134 | }) 135 | }) 136 | describe('addRelayers', () => { 137 | context('Happy path', () => { 138 | it('should add a relayer', async () => { 139 | const tx = await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 140 | 141 | expectEvent(tx, 'RelayerAdded', { relayer: relayers[0] }) 142 | }) 143 | it('should add two relayers in a single transaction', async () => { 144 | const tx = await AuthereumLoginKeyValidator.addRelayers(relayers) 145 | 146 | expectEvent(tx, 'RelayerAdded', { relayer: relayers[0] }) 147 | expectEvent(tx, 'RelayerAdded', { relayer: relayers[1] }) 148 | }) 149 | }) 150 | context('Non-Happy path', () => { 151 | it('should revert if relayer is allowed', async () => { 152 | 153 | await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 154 | 155 | await expectRevert( 156 | AuthereumLoginKeyValidator.addRelayers([relayers[0]]), 157 | constants.REVERT_MSG.LKV_RELAYER_ALREADY_ADDED 158 | ) 159 | }) 160 | it('should revert if the transaction is not from the owner', async () => { 161 | await expectRevert( 162 | AuthereumLoginKeyValidator.addRelayers([relayers[0]], { from: maliciousAccount }), 163 | constants.REVERT_MSG.O_MUST_BE_OWNER 164 | ) 165 | }) 166 | }) 167 | }) 168 | describe('removeRelayers', () => { 169 | context('Happy path', () => { 170 | it('should remove a relayer', async () => { 171 | let tx = await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 172 | expectEvent(tx, 'RelayerAdded', { relayer: relayers[0] }) 173 | 174 | tx = await AuthereumLoginKeyValidator.removeRelayers([relayers[0]]) 175 | expectEvent(tx, 'RelayerRemoved', { relayer: relayers[0] }) 176 | }) 177 | it('should remove two relayers in a single transaction', async () => { 178 | let tx = await AuthereumLoginKeyValidator.addRelayers(relayers) 179 | expectEvent(tx, 'RelayerAdded', { relayer: relayers[0] }) 180 | expectEvent(tx, 'RelayerAdded', { relayer: relayers[1] }) 181 | 182 | tx = await AuthereumLoginKeyValidator.removeRelayers(relayers) 183 | expectEvent(tx, 'RelayerRemoved', { relayer: relayers[0] }) 184 | expectEvent(tx, 'RelayerRemoved', { relayer: relayers[1] }) 185 | }) 186 | }) 187 | context('Non-Happy path', () => { 188 | it('should revert if relayer is already removed', async () => { 189 | await expectRevert( 190 | AuthereumLoginKeyValidator.removeRelayers([relayers[0]]), 191 | constants.REVERT_MSG.LKV_NOT_A_RELAYER 192 | ) 193 | }) 194 | }) 195 | it('should revert if the transaction is not from the owner', async () => { 196 | // Add a relayer 197 | let tx = await AuthereumLoginKeyValidator.addRelayers([relayers[0]]) 198 | expectEvent(tx, 'RelayerAdded', { relayer: relayers[0] }) 199 | 200 | await expectRevert( 201 | AuthereumLoginKeyValidator.addRelayers([relayers[0]], { from: maliciousAccount }), 202 | constants.REVERT_MSG.O_MUST_BE_OWNER 203 | ) 204 | }) 205 | }) 206 | }) 207 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | const Web3WsProvider = require('web3-providers-ws') 4 | const HDWalletProvider = require('truffle-hdwallet-provider') 5 | const PrivateKeyProvider = require('truffle-privatekey-provider') 6 | 7 | const privateKey = process.env.RELAYER_PRIVATE_KEY || process.env.SENDER_PRIVATE_KEY || process.env.PRIVATE_KEY 8 | const infuraId = process.env.INFURA_ID 9 | const rpcUri = process.env.TRUFFLE_RPC_URI || process.env.RPC_URI || process.env.ETH_HTTP_PROVIDER_URI 10 | const etherscanApiKey = process.env.ETHERSCAN_API_KEY 11 | 12 | const createPrivKeyProvider = (networkName) => { 13 | const providerUri = `https://${networkName}.rpc.authereum.com` 14 | return () => new PrivateKeyProvider(privateKey, providerUri) 15 | } 16 | 17 | module.exports = { 18 | networks: { 19 | development: { 20 | host: 'localhost', 21 | port: '8545', 22 | network_id: '*', // eslint-disable-line camelcase 23 | gas: 5712383, 24 | gasPrice: 20000000000 25 | }, 26 | local: { 27 | network_id: '*', // eslint-disable-line camelcase 28 | host: 'localhost', 29 | port: '9545', 30 | gas: 5712383, 31 | gasPrice: 20000000000 32 | }, 33 | mainnet: { 34 | provider: createPrivKeyProvider('mainnet'), 35 | network_id: '1', // eslint-disable-line camelcase 36 | gas: 2000000, 37 | gasPrice: 50000000000 38 | }, 39 | ropsten: { 40 | provider: createPrivKeyProvider('ropsten'), 41 | network_id: '3', // eslint-disable-line camelcase 42 | gas: 5712383, 43 | gasPrice: 20000000000 44 | }, 45 | rinkeby: { 46 | provider: createPrivKeyProvider('rinkeby'), 47 | network_id: '4', // eslint-disable-line camelcase 48 | gas: 5712383, 49 | gasPrice: 20000000000 50 | }, 51 | kovan: { 52 | provider: createPrivKeyProvider('kovan'), 53 | network_id: '42', // eslint-disable-line camelcase 54 | gas: 5712383, 55 | gasPrice: 20000000000 56 | }, 57 | goerli: { 58 | provider: createPrivKeyProvider('goerli'), 59 | network_id: '5', // eslint-disable-line camelcase 60 | gas: 5712383, 61 | gasPrice: 20000000000 62 | }, 63 | }, 64 | 65 | mocha: { 66 | timeout: 100000 67 | }, 68 | 69 | compilers: { 70 | solc: { 71 | version: '0.5.17', 72 | docker: false, 73 | optimizer: { 74 | // We are deliberately disabling the optimizer. 75 | // https://github.com/gnosis/safe-contracts/pull/167#discussion_r365153500 76 | enabled: false, 77 | runs: 200 78 | }, 79 | // NOTE: This should technically be muirGlacier, but that fork is 80 | // (a) not supported here and (b) had no changes that would affect 81 | // this compilation. 82 | evmVersion: 'istanbul' 83 | } 84 | }, 85 | 86 | plugins: [ 87 | 'truffle-plugin-verify' 88 | ], 89 | 90 | verify: { 91 | preamble: "Author: Authereum Labs, Inc." 92 | }, 93 | 94 | api_keys: { 95 | etherscan: etherscanApiKey 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ "es2015" , "dom" ], 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "skipLibCheck": true, 7 | "target": "es6", 8 | "declaration": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "sourceMap": false, 13 | "outDir": "dist", 14 | "baseUrl": ".", 15 | "paths": { 16 | "*": [ 17 | "node_modules/*" 18 | ] 19 | }, 20 | "typeRoots": [ 21 | "./node_modules/@types", 22 | "./@types" 23 | ] 24 | }, 25 | "include": [ 26 | "src/**/*", 27 | "src/config/*.json" 28 | ], 29 | "exclude": [ 30 | "node_modules", 31 | "**/*.spec.ts", 32 | "**/*.test.ts", 33 | "dist", 34 | "build" 35 | ] 36 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint-config-standard" 5 | ], 6 | "jsRules": {}, 7 | "rules": {}, 8 | "rulesDirectory": [] 9 | } 10 | --------------------------------------------------------------------------------