Transaction Hash:
Block:
16355205 at Jan-07-2023 01:28:47 PM +UTC
Transaction Fee:
0.000950056852263558 ETH
$2.16
Gas Used:
66,963 Gas / 14.187788066 Gwei
Emitted Events:
176 |
ERC20Proxy.Approval( _owner=[Sender] 0xc222f7d5c2cf5139d60e74ea69eff0e3bebcd471, _spender=0x11111112...90643097d, _value=115792089237316195423570985008687907853269984665640564039457584007913129639935 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x86c03684...3C9Be0B4a | |||||
0xC222f7D5...3bEBcd471 |
0.002856416604352624 Eth
Nonce: 2
|
0.001906359752089066 Eth
Nonce: 3
| 0.000950056852263558 | ||
0xDAFEA492...692c98Bc5
Miner
| (Flashbots: Builder) | 1.235613492651155428 Eth | 1.235680455651155428 Eth | 0.000066963 |
Execution Trace
ERC20Proxy.approve( _spender=0x1111111254fb6c44bAC0beD2854e76F90643097d, _value=115792089237316195423570985008687907853269984665640564039457584007913129639935 ) => ( success=True )

ERC20Impl.approveWithSender( _sender=0xC222f7D5c2cf5139D60E74Ea69eff0E3bEBcd471, _spender=0x1111111254fb6c44bAC0beD2854e76F90643097d, _value=115792089237316195423570985008687907853269984665640564039457584007913129639935 ) => ( success=True )
-
ERC20Store.setAllowance( _owner=0xC222f7D5c2cf5139D60E74Ea69eff0E3bEBcd471, _spender=0x1111111254fb6c44bAC0beD2854e76F90643097d, _value=115792089237316195423570985008687907853269984665640564039457584007913129639935 )
-
ERC20Proxy.emitApproval( _owner=0xC222f7D5c2cf5139D60E74Ea69eff0E3bEBcd471, _spender=0x1111111254fb6c44bAC0beD2854e76F90643097d, _value=115792089237316195423570985008687907853269984665640564039457584007913129639935 )
-
File 1 of 3: ERC20Proxy
File 2 of 3: ERC20Impl
File 3 of 3: ERC20Store
{"callbackSelector.sol":{"content":"pragma solidity ^0.5.10;\n\n/**\n * @title contract for generating HEX pointer\n * for functions.\n *\n * @dev this contract is a tool meant to be used\n * on local JavaScript VM.\n *\n */\ncontract callbackSelector {\n\n /**\n * @notice function which returns function HEX pointer (callbackSelector)\n *\n * @param _function function name with parameter types. Case and whitespace sensitive.\n *\n * @dev example: `function get(string memory _function)`\n * _function: `get(string)`\n * result: `0x693ec85e`\n */\n function get(string memory _function) public pure returns (bytes4) {\n return bytes4(keccak256(abi.encodePacked(_function)));\n }\n}"},"source.sol":{"content":"pragma solidity ^0.5.10;\n\n/** @title A contract for generating unique identifiers\n *\n * @notice A contract that provides an identifier generation scheme,\n * guaranteeing uniqueness across all contracts that inherit from it,\n * as well as the unpredictability of future identifiers.\n *\n * @dev This contract is intended to be inherited by any contract that\n * implements the callback software pattern for cooperative custodianship.\n *\n*/\ncontract LockRequestable {\n\n // MEMBERS\n /// @notice the count of all invocations of `generateLockId`.\n uint256 public lockRequestCount;\n\n // CONSTRUCTOR\n constructor() public {\n lockRequestCount = 0;\n }\n\n // FUNCTIONS\n /** @notice Returns a fresh unique identifier.\n *\n * @dev the generation scheme uses three components.\n * First, the blockhash of the previous block.\n * Second, the deployed address.\n * Third, the next value of the counter.\n * This ensures that identifiers are unique across all contracts\n * following this scheme, and that future identifiers are\n * unpredictable.\n *\n * @return a 32-byte unique identifier.\n */\n function generateLockId() internal returns (bytes32 lockId) {\n return keccak256(abi.encodePacked(blockhash(block.number - 1), address(this), ++lockRequestCount));\n }\n}\n\ncontract ERC20Interface {\n\n // METHODS\n\n // NOTE:\n // public getter functions are not currently recognised as an\n // implementation of the matching abstract function by the compiler.\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#name\n // function name() public view returns (string);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#symbol\n // function symbol() public view returns (string);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#totalsupply\n // function decimals() public view returns (uint8);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#totalsupply\n function totalSupply() public view returns (uint256);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#balanceof\n function balanceOf(address _owner) public view returns (uint256 balance);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer\n function transfer(address _to, uint256 _value) public returns (bool success);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transferfrom\n function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approve\n function approve(address _spender, uint256 _value) public returns (bool success);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#allowance\n function allowance(address _owner, address _spender) public view returns (uint256 remaining);\n\n // EVENTS\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer-1\n event Transfer(address indexed _from, address indexed _to, uint256 _value);\n\n // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approval\n event Approval(address indexed _owner, address indexed _spender, uint256 _value);\n}\n\n/** @title A dual control contract.\n *\n * @notice A general-purpose contract that implements dual control over\n * co-operating contracts through a callback mechanism.\n *\n * @dev This contract implements dual control through a 2-of-N\n * threshold multi-signature scheme. The contract recognizes a set of N signers,\n * and will unlock requests with signatures from any distinct pair of them.\n * This contract signals the unlocking through a co-operative callback\n * scheme.\n * This contract also provides time lock and revocation features.\n * Requests made by a \u0027primary\u0027 account have a default time lock applied.\n * All other requests must pay a 1 ETH stake and have an extended time lock\n * applied.\n * A request that is completed will prevent all previous pending requests\n * that share the same callback from being completed: this is the\n * revocation feature.\n *\n */\ncontract Custodian {\n\n // TYPES\n /** @dev The `Request` struct stores a pending unlocking.\n * `callbackAddress` and `callbackSelector` are the data required to\n * make a callback. The custodian completes the process by\n * calling `callbackAddress.call(callbackSelector, lockId)`, which\n * signals to the contract co-operating with the Custodian that\n * the 2-of-N signatures have been provided and verified.\n */\n struct Request {\n bytes32 lockId;\n bytes4 callbackSelector; // bytes4 and address can be packed into 1 word\n address callbackAddress;\n uint256 idx;\n uint256 timestamp;\n bool extended;\n }\n\n // EVENTS\n /// @dev Emitted by successful `requestUnlock` calls.\n event Requested(\n bytes32 _lockId,\n address _callbackAddress,\n bytes4 _callbackSelector,\n uint256 _nonce,\n address _whitelistedAddress,\n bytes32 _requestMsgHash,\n uint256 _timeLockExpiry\n );\n\n /// @dev Emitted by `completeUnlock` calls on requests in the time-locked state.\n event TimeLocked(\n uint256 _timeLockExpiry,\n bytes32 _requestMsgHash\n );\n\n /// @dev Emitted by successful `completeUnlock` calls.\n event Completed(\n bytes32 _lockId,\n bytes32 _requestMsgHash,\n address _signer1,\n address _signer2\n );\n\n /// @dev Emitted by `completeUnlock` calls where the callback failed.\n event Failed(\n bytes32 _lockId,\n bytes32 _requestMsgHash,\n address _signer1,\n address _signer2\n );\n\n /// @dev Emitted by successful `extendRequestTimeLock` calls.\n event TimeLockExtended(\n uint256 _timeLockExpiry,\n bytes32 _requestMsgHash\n );\n\n // MEMBERS\n /** @dev The count of all requests.\n * This value is used as a nonce, incorporated into the request hash.\n */\n uint256 public requestCount;\n\n /// @dev The set of signers: signatures from two signers unlock a pending request.\n mapping (address =\u003e bool) public signerSet;\n\n /// @dev The map of request hashes to pending requests.\n mapping (bytes32 =\u003e Request) public requestMap;\n\n /// @dev The map of callback addresses to callback selectors to request indexes.\n mapping (address =\u003e mapping (bytes4 =\u003e uint256)) public lastCompletedIdxs;\n\n /** @dev The default period (in seconds) to time-lock requests.\n * All requests will be subject to this default time lock, and the duration\n * is fixed at contract creation.\n */\n uint256 public defaultTimeLock;\n\n /** @dev The extended period (in seconds) to time-lock requests.\n * Requests not from the primary account are subject to this time lock.\n * The primary account may also elect to extend the time lock on requests\n * that originally received the default.\n */\n uint256 public extendedTimeLock;\n\n /// @dev The primary account is the privileged account for making requests.\n address public primary;\n\n // CONSTRUCTOR\n constructor(\n address[] memory _signers,\n uint256 _defaultTimeLock,\n uint256 _extendedTimeLock,\n address _primary\n )\n public\n {\n // check for at least two `_signers`\n require(_signers.length \u003e= 2, \"at least two `_signers`\");\n\n // validate time lock params\n require(_defaultTimeLock \u003c= _extendedTimeLock, \"valid timelock params\");\n defaultTimeLock = _defaultTimeLock;\n extendedTimeLock = _extendedTimeLock;\n\n primary = _primary;\n\n // explicitly initialize `requestCount` to zero\n requestCount = 0;\n // turn the array into a set\n for (uint i = 0; i \u003c _signers.length; i++) {\n // no zero addresses or duplicates\n require(_signers[i] != address(0) \u0026\u0026 !signerSet[_signers[i]], \"no zero addresses or duplicates\");\n signerSet[_signers[i]] = true;\n }\n }\n\n // MODIFIERS\n modifier onlyPrimary {\n require(msg.sender == primary, \"only primary\");\n _;\n }\n\n modifier onlySigner {\n require(signerSet[msg.sender], \"only signer\");\n _;\n }\n\n // METHODS\n /** @notice Requests an unlocking with a lock identifier and a callback.\n *\n * @dev If called by an account other than the primary a 1 ETH stake\n * must be paid. When the request is unlocked stake will be transferred to the message sender.\n * This is an anti-spam measure. As well as the callback\n * and the lock identifier parameters a \u0027whitelisted address\u0027 is required\n * for compatibility with existing signature schemes.\n *\n * @param _lockId The identifier of a pending request in a co-operating contract.\n * @param _callbackAddress The address of a co-operating contract.\n * @param _callbackSelector The function selector of a function within\n * the co-operating contract at address `_callbackAddress`.\n * @param _whitelistedAddress An address whitelisted in existing\n * offline control protocols.\n *\n * @return requestMsgHash The hash of a request message to be signed.\n */\n function requestUnlock(\n bytes32 _lockId,\n address _callbackAddress,\n bytes4 _callbackSelector,\n address _whitelistedAddress\n )\n public\n payable\n returns (bytes32 requestMsgHash)\n {\n require(msg.sender == primary || msg.value \u003e= 1 ether, \"sender is primary or stake is paid\");\n\n // disallow using a zero value for the callback address\n require(_callbackAddress != address(0), \"no zero value for callback address\");\n\n uint256 requestIdx = ++requestCount;\n // compute a nonce value\n // - the blockhash prevents prediction of future nonces\n // - the address of this contract prevents conflicts with co-operating contracts using this scheme\n // - the counter prevents conflicts arising from multiple txs within the same block\n uint256 nonce = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), address(this), requestIdx)));\n\n requestMsgHash = keccak256(\n abi.encodePacked(\n nonce,\n _whitelistedAddress,\n uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)\n )\n );\n requestMap[requestMsgHash] = Request({\n lockId: _lockId,\n callbackSelector: _callbackSelector,\n callbackAddress: _callbackAddress,\n idx: requestIdx,\n timestamp: block.timestamp,\n extended: false\n });\n\n // compute the expiry time\n uint256 timeLockExpiry = block.timestamp;\n if (msg.sender == primary) {\n timeLockExpiry += defaultTimeLock;\n } else {\n timeLockExpiry += extendedTimeLock;\n\n // any sender that is not the creator will get the extended time lock\n requestMap[requestMsgHash].extended = true;\n }\n\n emit Requested(_lockId, _callbackAddress, _callbackSelector, nonce, _whitelistedAddress, requestMsgHash, timeLockExpiry);\n }\n\n /** @notice Completes a pending unlocking with two signatures.\n *\n * @dev Given a request message hash as two signatures of it from\n * two distinct signers in the signer set, this function completes the\n * unlocking of the pending request by executing the callback.\n *\n * @param _requestMsgHash The request message hash of a pending request.\n * @param _recoveryByte1 The public key recovery byte (27 or 28)\n * @param _ecdsaR1 The R component of an ECDSA signature (R, S) pair\n * @param _ecdsaS1 The S component of an ECDSA signature (R, S) pair\n * @param _recoveryByte2 The public key recovery byte (27 or 28)\n * @param _ecdsaR2 The R component of an ECDSA signature (R, S) pair\n * @param _ecdsaS2 The S component of an ECDSA signature (R, S) pair\n *\n * @return success True if the callback successfully executed.\n */\n function completeUnlock(\n bytes32 _requestMsgHash,\n uint8 _recoveryByte1, bytes32 _ecdsaR1, bytes32 _ecdsaS1,\n uint8 _recoveryByte2, bytes32 _ecdsaR2, bytes32 _ecdsaS2\n )\n public\n onlySigner\n returns (bool success)\n {\n Request storage request = requestMap[_requestMsgHash];\n\n // copy storage to locals before `delete`\n bytes32 lockId = request.lockId;\n address callbackAddress = request.callbackAddress;\n bytes4 callbackSelector = request.callbackSelector;\n\n // failing case of the lookup if the callback address is zero\n require(callbackAddress != address(0), \"no zero value for callback address\");\n\n // reject confirms of earlier withdrawals buried under later confirmed withdrawals\n require(request.idx \u003e lastCompletedIdxs[callbackAddress][callbackSelector],\n \"reject confirms of earlier withdrawals buried under later confirmed withdrawals\");\n\n address signer1 = ecrecover(\n keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", _requestMsgHash)),\n _recoveryByte1,\n _ecdsaR1,\n _ecdsaS1\n );\n require(signerSet[signer1], \"signer is set\");\n\n address signer2 = ecrecover(\n keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", _requestMsgHash)),\n _recoveryByte2,\n _ecdsaR2,\n _ecdsaS2\n );\n require(signerSet[signer2], \"signer is set\");\n require(signer1 != signer2, \"signers are different\");\n\n if (request.extended \u0026\u0026 ((block.timestamp - request.timestamp) \u003c extendedTimeLock)) {\n emit TimeLocked(request.timestamp + extendedTimeLock, _requestMsgHash);\n return false;\n } else if ((block.timestamp - request.timestamp) \u003c defaultTimeLock) {\n emit TimeLocked(request.timestamp + defaultTimeLock, _requestMsgHash);\n return false;\n } else {\n if (address(this).balance \u003e 0) {\n // reward sender with anti-spam payments\n msg.sender.transfer(address(this).balance);\n }\n\n // raise the waterline for the last completed unlocking\n lastCompletedIdxs[callbackAddress][callbackSelector] = request.idx;\n // and delete the request\n delete requestMap[_requestMsgHash];\n\n // invoke callback\n (success,) = callbackAddress.call(abi.encodeWithSelector(callbackSelector, lockId));\n\n if (success) {\n emit Completed(lockId, _requestMsgHash, signer1, signer2);\n } else {\n emit Failed(lockId, _requestMsgHash, signer1, signer2);\n }\n }\n }\n\n /** @notice Reclaim the storage of a pending request that is uncompletable.\n *\n * @dev If a pending request shares the callback (address and selector) of\n * a later request has been completed, then the request can no longer\n * be completed. This function will reclaim the contract storage of the\n * pending request.\n *\n * @param _requestMsgHash The request message hash of a pending request.\n */\n function deleteUncompletableRequest(bytes32 _requestMsgHash) public {\n Request storage request = requestMap[_requestMsgHash];\n\n uint256 idx = request.idx;\n\n require(0 \u003c idx \u0026\u0026 idx \u003c lastCompletedIdxs[request.callbackAddress][request.callbackSelector],\n \"there must be a completed latter request with same callback\");\n\n delete requestMap[_requestMsgHash];\n }\n\n /** @notice Extend the time lock of a pending request.\n *\n * @dev Requests made by the primary account receive the default time lock.\n * This function allows the primary account to apply the extended time lock\n * to one its own requests.\n *\n * @param _requestMsgHash The request message hash of a pending request.\n */\n function extendRequestTimeLock(bytes32 _requestMsgHash) public onlyPrimary {\n Request storage request = requestMap[_requestMsgHash];\n\n // reject ‘null’ results from the map lookup\n // this can only be the case if an unknown `_requestMsgHash` is received\n require(request.callbackAddress != address(0), \"reject ‘null’ results from the map lookup\");\n\n // `extendRequestTimeLock` must be idempotent\n require(request.extended != true, \"`extendRequestTimeLock` must be idempotent\");\n\n // set the `extended` flag; note that this is never unset\n request.extended = true;\n\n emit TimeLockExtended(request.timestamp + extendedTimeLock, _requestMsgHash);\n }\n}\n\n/** @title A contract to inherit upgradeable custodianship.\n *\n * @notice A contract that provides re-usable code for upgradeable\n * custodianship. That custodian may be an account or another contract.\n *\n * @dev This contract is intended to be inherited by any contract\n * requiring a custodian to control some aspect of its functionality.\n * This contract provides the mechanism for that custodianship to be\n * passed from one custodian to the next.\n *\n*/\ncontract CustodianUpgradeable is LockRequestable {\n\n // TYPES\n /// @dev The struct type for pending custodian changes.\n struct CustodianChangeRequest {\n address proposedNew;\n }\n\n // MEMBERS\n /// @dev The address of the account or contract that acts as the custodian.\n address public custodian;\n\n /// @dev The map of lock ids to pending custodian changes.\n mapping (bytes32 =\u003e CustodianChangeRequest) public custodianChangeReqs;\n\n // CONSTRUCTOR\n constructor(\n address _custodian\n )\n LockRequestable()\n public\n {\n custodian = _custodian;\n }\n\n // MODIFIERS\n modifier onlyCustodian {\n require(msg.sender == custodian, \"only custodian\");\n _;\n }\n\n // PUBLIC FUNCTIONS\n // (UPGRADE)\n\n /** @notice Requests a change of the custodian associated with this contract.\n *\n * @dev Returns a unique lock id associated with the request.\n * Anyone can call this function, but confirming the request is authorized\n * by the custodian.\n *\n * @param _proposedCustodian The address of the new custodian.\n * @return lockId A unique identifier for this request.\n */\n function requestCustodianChange(address _proposedCustodian) public returns (bytes32 lockId) {\n require(_proposedCustodian != address(0), \"no null value for `_proposedCustodian`\");\n\n lockId = generateLockId();\n\n custodianChangeReqs[lockId] = CustodianChangeRequest({\n proposedNew: _proposedCustodian\n });\n\n emit CustodianChangeRequested(lockId, msg.sender, _proposedCustodian);\n }\n\n /** @notice Confirms a pending change of the custodian associated with this contract.\n *\n * @dev When called by the current custodian with a lock id associated with a\n * pending custodian change, the `address custodian` member will be updated with the\n * requested address.\n *\n * @param _lockId The identifier of a pending change request.\n */\n function confirmCustodianChange(bytes32 _lockId) public onlyCustodian {\n custodian = getCustodianChangeReq(_lockId);\n\n delete custodianChangeReqs[_lockId];\n\n emit CustodianChangeConfirmed(_lockId, custodian);\n }\n\n // PRIVATE FUNCTIONS\n function getCustodianChangeReq(bytes32 _lockId) private view returns (address _proposedNew) {\n CustodianChangeRequest storage changeRequest = custodianChangeReqs[_lockId];\n\n // reject ‘null’ results from the map lookup\n // this can only be the case if an unknown `_lockId` is received\n require(changeRequest.proposedNew != address(0), \"reject ‘null’ results from the map lookup\");\n\n return changeRequest.proposedNew;\n }\n\n //EVENTS\n /// @dev Emitted by successful `requestCustodianChange` calls.\n event CustodianChangeRequested(\n bytes32 _lockId,\n address _msgSender,\n address _proposedCustodian\n );\n\n /// @dev Emitted by successful `confirmCustodianChange` calls.\n event CustodianChangeConfirmed(bytes32 _lockId, address _newCustodian);\n}\n\n/** @title A contract to inherit upgradeable token implementations.\n *\n * @notice A contract that provides re-usable code for upgradeable\n * token implementations. It itself inherits from `CustodianUpgradable`\n * as the upgrade process is controlled by the custodian.\n *\n * @dev This contract is intended to be inherited by any contract\n * requiring a reference to the active token implementation, either\n * to delegate calls to it, or authorize calls from it. This contract\n * provides the mechanism for that implementation to be replaced,\n * which constitutes an implementation upgrade.\n *\n */\ncontract ERC20ImplUpgradeable is CustodianUpgradeable {\n\n // TYPES\n /// @dev The struct type for pending implementation changes.\n struct ImplChangeRequest {\n address proposedNew;\n }\n\n // MEMBERS\n // @dev The reference to the active token implementation.\n ERC20Impl public erc20Impl;\n\n /// @dev The map of lock ids to pending implementation changes.\n mapping (bytes32 =\u003e ImplChangeRequest) public implChangeReqs;\n\n // CONSTRUCTOR\n constructor(address _custodian) CustodianUpgradeable(_custodian) public {\n erc20Impl = ERC20Impl(0x0);\n }\n\n // MODIFIERS\n modifier onlyImpl {\n require(msg.sender == address(erc20Impl), \"only ERC20Impl\");\n _;\n }\n\n // PUBLIC FUNCTIONS\n // (UPGRADE)\n /** @notice Requests a change of the active implementation associated\n * with this contract.\n *\n * @dev Returns a unique lock id associated with the request.\n * Anyone can call this function, but confirming the request is authorized\n * by the custodian.\n *\n * @param _proposedImpl The address of the new active implementation.\n * @return lockId A unique identifier for this request.\n */\n function requestImplChange(address _proposedImpl) public returns (bytes32 lockId) {\n require(_proposedImpl != address(0), \"no null value for `_proposedImpl`\");\n\n lockId = generateLockId();\n\n implChangeReqs[lockId] = ImplChangeRequest({\n proposedNew: _proposedImpl\n });\n\n emit ImplChangeRequested(lockId, msg.sender, _proposedImpl);\n }\n\n /** @notice Confirms a pending change of the active implementation\n * associated with this contract.\n *\n * @dev When called by the custodian with a lock id associated with a\n * pending change, the `ERC20Impl erc20Impl` member will be updated\n * with the requested address.\n *\n * @param _lockId The identifier of a pending change request.\n */\n function confirmImplChange(bytes32 _lockId) public onlyCustodian {\n erc20Impl = getImplChangeReq(_lockId);\n\n delete implChangeReqs[_lockId];\n\n emit ImplChangeConfirmed(_lockId, address(erc20Impl));\n }\n\n // PRIVATE FUNCTIONS\n function getImplChangeReq(bytes32 _lockId) private view returns (ERC20Impl _proposedNew) {\n ImplChangeRequest storage changeRequest = implChangeReqs[_lockId];\n\n // reject ‘null’ results from the map lookup\n // this can only be the case if an unknown `_lockId` is received\n require(changeRequest.proposedNew != address(0), \"reject ‘null’ results from the map lookup\");\n\n return ERC20Impl(changeRequest.proposedNew);\n }\n\n //EVENTS\n /// @dev Emitted by successful `requestImplChange` calls.\n event ImplChangeRequested(\n bytes32 _lockId,\n address _msgSender,\n address _proposedImpl\n );\n\n /// @dev Emitted by successful `confirmImplChange` calls.\n event ImplChangeConfirmed(bytes32 _lockId, address _newImpl);\n}\n\n/** @title Public interface to ERC20 compliant token.\n *\n * @notice This contract is a permanent entry point to an ERC20 compliant\n * system of contracts.\n *\n * @dev This contract contains no business logic and instead\n * delegates to an instance of ERC20Impl. This contract also has no storage\n * that constitutes the operational state of the token. This contract is\n * upgradeable in the sense that the `custodian` can update the\n * `erc20Impl` address, thus redirecting the delegation of business logic.\n * The `custodian` is also authorized to pass custodianship.\n *\n*/\ncontract ERC20Proxy is ERC20Interface, ERC20ImplUpgradeable {\n\n // MEMBERS\n /// @notice Returns the name of the token.\n string public name;\n\n /// @notice Returns the symbol of the token.\n string public symbol;\n\n /// @notice Returns the number of decimals the token uses.\n uint8 public decimals;\n\n // CONSTRUCTOR\n constructor(\n string memory _name,\n string memory _symbol,\n uint8 _decimals,\n address _custodian\n )\n ERC20ImplUpgradeable(_custodian)\n public\n {\n name = _name;\n symbol = _symbol;\n decimals = _decimals;\n }\n\n // PUBLIC FUNCTIONS\n // (ERC20Interface)\n /** @notice Returns the total token supply.\n *\n * @return the total token supply.\n */\n function totalSupply() public view returns (uint256) {\n return erc20Impl.totalSupply();\n }\n\n /** @notice Returns the account balance of another account with an address\n * `_owner`.\n *\n * @return balance the balance of account with address `_owner`.\n */\n function balanceOf(address _owner) public view returns (uint256 balance) {\n return erc20Impl.balanceOf(_owner);\n }\n\n /** @dev Internal use only.\n */\n function emitTransfer(address _from, address _to, uint256 _value) public onlyImpl {\n emit Transfer(_from, _to, _value);\n }\n\n /** @notice Transfers `_value` amount of tokens to address `_to`.\n *\n * @dev Will fire the `Transfer` event. Will revert if the `_from`\n * account balance does not have enough tokens to spend.\n *\n * @return success true if transfer completes.\n */\n function transfer(address _to, uint256 _value) public returns (bool success) {\n return erc20Impl.transferWithSender(msg.sender, _to, _value);\n }\n\n /** @notice Transfers `_value` amount of tokens from address `_from`\n * to address `_to`.\n *\n * @dev Will fire the `Transfer` event. Will revert unless the `_from`\n * account has deliberately authorized the sender of the message\n * via some mechanism.\n *\n * @return success true if transfer completes.\n */\n function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {\n return erc20Impl.transferFromWithSender(msg.sender, _from, _to, _value);\n }\n\n /** @dev Internal use only.\n */\n function emitApproval(address _owner, address _spender, uint256 _value) public onlyImpl {\n emit Approval(_owner, _spender, _value);\n }\n\n /** @notice Allows `_spender` to withdraw from your account multiple times,\n * up to the `_value` amount. If this function is called again it\n * overwrites the current allowance with _value.\n *\n * @dev Will fire the `Approval` event.\n *\n * @return success true if approval completes.\n */\n function approve(address _spender, uint256 _value) public returns (bool success) {\n return erc20Impl.approveWithSender(msg.sender, _spender, _value);\n }\n\n /** @notice Increases the amount `_spender` is allowed to withdraw from\n * your account.\n * This function is implemented to avoid the race condition in standard\n * ERC20 contracts surrounding the `approve` method.\n *\n * @dev Will fire the `Approval` event. This function should be used instead of\n * `approve`.\n *\n * @return success true if approval completes.\n */\n function increaseApproval(address _spender, uint256 _addedValue) public returns (bool success) {\n return erc20Impl.increaseApprovalWithSender(msg.sender, _spender, _addedValue);\n }\n\n /** @notice Decreases the amount `_spender` is allowed to withdraw from\n * your account. This function is implemented to avoid the race\n * condition in standard ERC20 contracts surrounding the `approve` method.\n *\n * @dev Will fire the `Approval` event. This function should be used\n * instead of `approve`.\n *\n * @return success true if approval completes.\n */\n function decreaseApproval(address _spender, uint256 _subtractedValue) public returns (bool success) {\n return erc20Impl.decreaseApprovalWithSender(msg.sender, _spender, _subtractedValue);\n }\n\n /** @notice Returns how much `_spender` is currently allowed to spend from\n * `_owner`\u0027s balance.\n *\n * @return remaining the remaining allowance.\n */\n function allowance(address _owner, address _spender) public view returns (uint256 remaining) {\n return erc20Impl.allowance(_owner, _spender);\n }\n}\n\n/** @title ERC20 compliant token balance store.\n *\n * @notice This contract serves as the store of balances, allowances, and\n * supply for the ERC20 compliant token. No business logic exists here.\n *\n * @dev This contract contains no business logic and instead\n * is the final destination for any change in balances, allowances, or token\n * supply. This contract is upgradeable in the sense that its custodian can\n * update the `erc20Impl` address, thus redirecting the source of logic that\n * determines how the balances will be updated.\n *\n */\ncontract ERC20Store is ERC20ImplUpgradeable {\n\n // MEMBERS\n /// @dev The total token supply.\n uint256 public totalSupply;\n\n /// @dev The mapping of balances.\n mapping (address =\u003e uint256) public balances;\n\n /// @dev The mapping of allowances.\n mapping (address =\u003e mapping (address =\u003e uint256)) public allowed;\n\n // CONSTRUCTOR\n constructor(address _custodian) ERC20ImplUpgradeable(_custodian) public {\n totalSupply = 0;\n }\n\n // PUBLIC FUNCTIONS\n // (ERC20 Ledger)\n\n /** @notice The function to set the total supply of tokens.\n *\n * @dev Intended for use by token implementation functions\n * that update the total supply. The only authorized caller\n * is the active implementation.\n *\n * @param _newTotalSupply the value to set as the new total supply\n */\n function setTotalSupply(\n uint256 _newTotalSupply\n )\n public\n onlyImpl\n {\n totalSupply = _newTotalSupply;\n }\n\n /** @notice Sets how much `_owner` allows `_spender` to transfer on behalf\n * of `_owner`.\n *\n * @dev Intended for use by token implementation functions\n * that update spending allowances. The only authorized caller\n * is the active implementation.\n *\n * @param _owner The account that will allow an on-behalf-of spend.\n * @param _spender The account that will spend on behalf of the owner.\n * @param _value The limit of what can be spent.\n */\n function setAllowance(\n address _owner,\n address _spender,\n uint256 _value\n )\n public\n onlyImpl\n {\n allowed[_owner][_spender] = _value;\n }\n\n /** @notice Sets the balance of `_owner` to `_newBalance`.\n *\n * @dev Intended for use by token implementation functions\n * that update balances. The only authorized caller\n * is the active implementation.\n *\n * @param _owner The account that will hold a new balance.\n * @param _newBalance The balance to set.\n */\n function setBalance(\n address _owner,\n uint256 _newBalance\n )\n public\n onlyImpl\n {\n balances[_owner] = _newBalance;\n }\n\n /** @notice Adds `_balanceIncrease` to `_owner`\u0027s balance.\n *\n * @dev Intended for use by token implementation functions\n * that update balances. The only authorized caller\n * is the active implementation.\n * WARNING: the caller is responsible for preventing overflow.\n *\n * @param _owner The account that will hold a new balance.\n * @param _balanceIncrease The balance to add.\n */\n function addBalance(\n address _owner,\n uint256 _balanceIncrease\n )\n public\n onlyImpl\n {\n balances[_owner] = balances[_owner] + _balanceIncrease;\n }\n}\n\n/** @title ERC20 compliant token intermediary contract holding core logic.\n *\n * @notice This contract serves as an intermediary between the exposed ERC20\n * interface in ERC20Proxy and the store of balances in ERC20Store. This\n * contract contains core logic that the proxy can delegate to\n * and that the store is called by.\n *\n * @dev This contract contains the core logic to implement the\n * ERC20 specification as well as several extensions.\n * 1. Changes to the token supply.\n * 2. Batched transfers.\n * 3. Relative changes to spending approvals.\n * 4. Delegated transfer control (\u0027sweeping\u0027).\n *\n */\ncontract ERC20Impl is CustodianUpgradeable {\n\n // TYPES\n /// @dev The struct type for pending increases to the token supply (print).\n struct PendingPrint {\n address receiver;\n uint256 value;\n }\n\n // MEMBERS\n /// @dev The reference to the proxy.\n ERC20Proxy public erc20Proxy;\n\n /// @dev The reference to the store.\n ERC20Store public erc20Store;\n\n /// @dev The sole authorized caller of delegated transfer control (\u0027sweeping\u0027).\n address public sweeper;\n\n /** @dev The static message to be signed by an external account that\n * signifies their permission to forward their balance to any arbitrary\n * address. This is used to consolidate the control of all accounts\n * backed by a shared keychain into the control of a single key.\n * Initialized as the concatenation of the address of this contract\n * and the word \"sweep\". This concatenation is done to prevent a replay\n * attack in a subsequent contract, where the sweeping message could\n * potentially be replayed to re-enable sweeping ability.\n */\n bytes32 public sweepMsg;\n\n /** @dev The mapping that stores whether the address in question has\n * enabled sweeping its contents to another account or not.\n * If an address maps to \"true\", it has already enabled sweeping,\n * and thus does not need to re-sign the `sweepMsg` to enact the sweep.\n */\n mapping (address =\u003e bool) public sweptSet;\n\n /// @dev The map of lock ids to pending token increases.\n mapping (bytes32 =\u003e PendingPrint) public pendingPrintMap;\n\n /// @dev The map of blocked addresses.\n mapping (address =\u003e bool) public blocked;\n\n // CONSTRUCTOR\n constructor(\n address _erc20Proxy,\n address _erc20Store,\n address _custodian,\n address _sweeper\n )\n CustodianUpgradeable(_custodian)\n public\n {\n require(_sweeper != address(0), \"no null value for `_sweeper`\");\n erc20Proxy = ERC20Proxy(_erc20Proxy);\n erc20Store = ERC20Store(_erc20Store);\n\n sweeper = _sweeper;\n sweepMsg = keccak256(abi.encodePacked(address(this), \"sweep\"));\n }\n\n // MODIFIERS\n modifier onlyProxy {\n require(msg.sender == address(erc20Proxy), \"only ERC20Proxy\");\n _;\n }\n modifier onlySweeper {\n require(msg.sender == sweeper, \"only sweeper\");\n _;\n }\n\n\n /** @notice Core logic of the ERC20 `approve` function.\n *\n * @dev This function can only be called by the referenced proxy,\n * which has an `approve` function.\n * Every argument passed to that function as well as the original\n * `msg.sender` gets passed to this function.\n * NOTE: approvals for the zero address (unspendable) are disallowed.\n *\n * @param _sender The address initiating the approval in a proxy.\n */\n function approveWithSender(\n address _sender,\n address _spender,\n uint256 _value\n )\n public\n onlyProxy\n returns (bool success)\n {\n require(_spender != address(0), \"no null value for `_spender`\");\n require(blocked[_sender] != true, \"_sender must not be blocked\");\n require(blocked[_spender] != true, \"_spender must not be blocked\");\n erc20Store.setAllowance(_sender, _spender, _value);\n erc20Proxy.emitApproval(_sender, _spender, _value);\n return true;\n }\n\n /** @notice Core logic of the `increaseApproval` function.\n *\n * @dev This function can only be called by the referenced proxy,\n * which has an `increaseApproval` function.\n * Every argument passed to that function as well as the original\n * `msg.sender` gets passed to this function.\n * NOTE: approvals for the zero address (unspendable) are disallowed.\n *\n * @param _sender The address initiating the approval.\n */\n function increaseApprovalWithSender(\n address _sender,\n address _spender,\n uint256 _addedValue\n )\n public\n onlyProxy\n returns (bool success)\n {\n require(_spender != address(0),\"no null value for_spender\");\n require(blocked[_sender] != true, \"_sender must not be blocked\");\n require(blocked[_spender] != true, \"_spender must not be blocked\");\n uint256 currentAllowance = erc20Store.allowed(_sender, _spender);\n uint256 newAllowance = currentAllowance + _addedValue;\n\n require(newAllowance \u003e= currentAllowance, \"new allowance must not be smaller than previous\");\n\n erc20Store.setAllowance(_sender, _spender, newAllowance);\n erc20Proxy.emitApproval(_sender, _spender, newAllowance);\n return true;\n }\n\n /** @notice Core logic of the `decreaseApproval` function.\n *\n * @dev This function can only be called by the referenced proxy,\n * which has a `decreaseApproval` function.\n * Every argument passed to that function as well as the original\n * `msg.sender` gets passed to this function.\n * NOTE: approvals for the zero address (unspendable) are disallowed.\n *\n * @param _sender The address initiating the approval.\n */\n function decreaseApprovalWithSender(\n address _sender,\n address _spender,\n uint256 _subtractedValue\n )\n public\n onlyProxy\n returns (bool success)\n {\n require(_spender != address(0), \"no unspendable approvals\"); // disallow unspendable approvals\n require(blocked[_sender] != true, \"_sender must not be blocked\");\n require(blocked[_spender] != true, \"_spender must not be blocked\");\n uint256 currentAllowance = erc20Store.allowed(_sender, _spender);\n uint256 newAllowance = currentAllowance - _subtractedValue;\n\n require(newAllowance \u003c= currentAllowance, \"new allowance must not be smaller than previous\");\n\n erc20Store.setAllowance(_sender, _spender, newAllowance);\n erc20Proxy.emitApproval(_sender, _spender, newAllowance);\n return true;\n }\n\n /** @notice Requests an increase in the token supply, with the newly created\n * tokens to be added to the balance of the specified account.\n *\n * @dev Returns a unique lock id associated with the request.\n * Anyone can call this function, but confirming the request is authorized\n * by the custodian.\n * NOTE: printing to the zero address is disallowed.\n *\n * @param _receiver The receiving address of the print, if confirmed.\n * @param _value The number of tokens to add to the total supply and the\n * balance of the receiving address, if confirmed.\n *\n * @return lockId A unique identifier for this request.\n */\n function requestPrint(address _receiver, uint256 _value) public returns (bytes32 lockId) {\n require(_receiver != address(0), \"no null value for `_receiver`\");\n require(blocked[msg.sender] != true, \"account blocked\");\n require(blocked[_receiver] != true, \"_receiver must not be blocked\");\n lockId = generateLockId();\n\n pendingPrintMap[lockId] = PendingPrint({\n receiver: _receiver,\n value: _value\n });\n\n emit PrintingLocked(lockId, _receiver, _value);\n }\n\n /** @notice Confirms a pending increase in the token supply.\n *\n * @dev When called by the custodian with a lock id associated with a\n * pending increase, the amount requested to be printed in the print request\n * is printed to the receiving address specified in that same request.\n * NOTE: this function will not execute any print that would overflow the\n * total supply, but it will not revert either.\n *\n * @param _lockId The identifier of a pending print request.\n */\n function confirmPrint(bytes32 _lockId) public onlyCustodian {\n PendingPrint storage print = pendingPrintMap[_lockId];\n\n // reject ‘null’ results from the map lookup\n // this can only be the case if an unknown `_lockId` is received\n address receiver = print.receiver;\n require (receiver != address(0), \"unknown `_lockId`\");\n uint256 value = print.value;\n\n delete pendingPrintMap[_lockId];\n\n uint256 supply = erc20Store.totalSupply();\n uint256 newSupply = supply + value;\n if (newSupply \u003e= supply) {\n erc20Store.setTotalSupply(newSupply);\n erc20Store.addBalance(receiver, value);\n\n emit PrintingConfirmed(_lockId, receiver, value);\n erc20Proxy.emitTransfer(address(0), receiver, value);\n }\n }\n\n /** @notice Burns the specified value from the sender\u0027s balance.\n *\n * @dev Sender\u0027s balanced is subtracted by the amount they wish to burn.\n *\n * @param _value The amount to burn.\n *\n * @return success true if the burn succeeded.\n */\n function burn(uint256 _value) public returns (bool success) {\n require(blocked[msg.sender] != true, \"account blocked\");\n uint256 balanceOfSender = erc20Store.balances(msg.sender);\n require(_value \u003c= balanceOfSender, \"disallow burning more, than amount of the balance\");\n\n erc20Store.setBalance(msg.sender, balanceOfSender - _value);\n erc20Store.setTotalSupply(erc20Store.totalSupply() - _value);\n\n erc20Proxy.emitTransfer(msg.sender, address(0), _value);\n\n return true;\n }\n\n /** @notice Burns the specified value from the balance in question.\n *\n * @dev Suspected balance is subtracted by the amount which will be burnt.\n *\n * @dev If the suspected balance has less than the amount requested, it will be set to 0.\n *\n * @param _from The address of suspected balance.\n *\n * @param _value The amount to burn.\n *\n * @return success true if the burn succeeded.\n */\n function burn(address _from, uint256 _value) public onlyCustodian returns (bool success) {\n uint256 balance = erc20Store.balances(_from);\n if(_value \u003c= balance){\n erc20Store.setBalance(_from, balance - _value);\n erc20Store.setTotalSupply(erc20Store.totalSupply() - _value);\n erc20Proxy.emitTransfer(_from, address(0), _value);\n emit Wiped(_from, _value, _value, balance - _value);\n }\n else {\n erc20Store.setBalance(_from,0);\n erc20Store.setTotalSupply(erc20Store.totalSupply() - balance);\n erc20Proxy.emitTransfer(_from, address(0), balance);\n emit Wiped(_from, _value, balance, 0);\n }\n return true;\n }\n\n /** @notice A function for a sender to issue multiple transfers to multiple\n * different addresses at once. This function is implemented for gas\n * considerations when someone wishes to transfer, as one transaction is\n * cheaper than issuing several distinct individual `transfer` transactions.\n *\n * @dev By specifying a set of destination addresses and values, the\n * sender can issue one transaction to transfer multiple amounts to\n * distinct addresses, rather than issuing each as a separate\n * transaction. The `_tos` and `_values` arrays must be equal length, and\n * an index in one array corresponds to the same index in the other array\n * (e.g. `_tos[0]` will receive `_values[0]`, `_tos[1]` will receive\n * `_values[1]`, and so on.)\n * NOTE: transfers to the zero address are disallowed.\n *\n * @param _tos The destination addresses to receive the transfers.\n * @param _values The values for each destination address.\n * @return success If transfers succeeded.\n */\n function batchTransfer(address[] memory _tos, uint256[] memory _values) public returns (bool success) {\n require(_tos.length == _values.length, \"_tos and _values must be the same length\");\n require(blocked[msg.sender] != true, \"account blocked\");\n uint256 numTransfers = _tos.length;\n uint256 senderBalance = erc20Store.balances(msg.sender);\n\n for (uint256 i = 0; i \u003c numTransfers; i++) {\n address to = _tos[i];\n require(to != address(0), \"no null values for _tos\");\n require(blocked[to] != true, \"_tos must not be blocked\");\n uint256 v = _values[i];\n require(senderBalance \u003e= v, \"insufficient funds\");\n\n if (msg.sender != to) {\n senderBalance -= v;\n erc20Store.addBalance(to, v);\n }\n erc20Proxy.emitTransfer(msg.sender, to, v);\n }\n\n erc20Store.setBalance(msg.sender, senderBalance);\n\n return true;\n }\n\n /** @notice Enables the delegation of transfer control for many\n * accounts to the sweeper account, transferring any balances\n * as well to the given destination.\n *\n * @dev An account delegates transfer control by signing the\n * value of `sweepMsg`. The sweeper account is the only authorized\n * caller of this function, so it must relay signatures on behalf\n * of accounts that delegate transfer control to it. Enabling\n * delegation is idempotent and permanent. If the account has a\n * balance at the time of enabling delegation, its balance is\n * also transferred to the given destination account `_to`.\n * NOTE: transfers to the zero address are disallowed.\n *\n * @param _vs The array of recovery byte components of the ECDSA signatures.\n * @param _rs The array of \u0027R\u0027 components of the ECDSA signatures.\n * @param _ss The array of \u0027S\u0027 components of the ECDSA signatures.\n * @param _to The destination for swept balances.\n */\n function enableSweep(uint8[] memory _vs, bytes32[] memory _rs, bytes32[] memory _ss, address _to) public onlySweeper {\n require(_to != address(0), \"no null value for `_to`\");\n require(blocked[_to] != true, \"_to must not be blocked\");\n require((_vs.length == _rs.length) \u0026\u0026 (_vs.length == _ss.length), \"_vs[], _rs[], _ss lengths are different\");\n\n uint256 numSignatures = _vs.length;\n uint256 sweptBalance = 0;\n\n for (uint256 i = 0; i \u003c numSignatures; ++i) {\n address from = ecrecover(keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\",sweepMsg)), _vs[i], _rs[i], _ss[i]);\n require(blocked[from] != true, \"_froms must not be blocked\");\n // ecrecover returns 0 on malformed input\n if (from != address(0)) {\n sweptSet[from] = true;\n\n uint256 fromBalance = erc20Store.balances(from);\n\n if (fromBalance \u003e 0) {\n sweptBalance += fromBalance;\n\n erc20Store.setBalance(from, 0);\n\n erc20Proxy.emitTransfer(from, _to, fromBalance);\n }\n }\n }\n\n if (sweptBalance \u003e 0) {\n erc20Store.addBalance(_to, sweptBalance);\n }\n }\n\n /** @notice For accounts that have delegated, transfer control\n * to the sweeper, this function transfers their balances to the given\n * destination.\n *\n * @dev The sweeper account is the only authorized caller of\n * this function. This function accepts an array of addresses to have their\n * balances transferred for gas efficiency purposes.\n * NOTE: any address for an account that has not been previously enabled\n * will be ignored.\n * NOTE: transfers to the zero address are disallowed.\n *\n * @param _froms The addresses to have their balances swept.\n * @param _to The destination address of all these transfers.\n */\n function replaySweep(address[] memory _froms, address _to) public onlySweeper {\n require(_to != address(0), \"no null value for `_to`\");\n require(blocked[_to] != true, \"_to must not be blocked\");\n uint256 lenFroms = _froms.length;\n uint256 sweptBalance = 0;\n\n for (uint256 i = 0; i \u003c lenFroms; ++i) {\n address from = _froms[i];\n require(blocked[from] != true, \"_froms must not be blocked\");\n if (sweptSet[from]) {\n uint256 fromBalance = erc20Store.balances(from);\n\n if (fromBalance \u003e 0) {\n sweptBalance += fromBalance;\n\n erc20Store.setBalance(from, 0);\n\n erc20Proxy.emitTransfer(from, _to, fromBalance);\n }\n }\n }\n\n if (sweptBalance \u003e 0) {\n erc20Store.addBalance(_to, sweptBalance);\n }\n }\n\n /** @notice Core logic of the ERC20 `transferFrom` function.\n *\n * @dev This function can only be called by the referenced proxy,\n * which has a `transferFrom` function.\n * Every argument passed to that function as well as the original\n * `msg.sender` gets passed to this function.\n * NOTE: transfers to the zero address are disallowed.\n *\n * @param _sender The address initiating the transfer in a proxy.\n */\n function transferFromWithSender(\n address _sender,\n address _from,\n address _to,\n uint256 _value\n )\n public\n onlyProxy\n returns (bool success)\n {\n require(_to != address(0), \"no null values for `_to`\");\n require(blocked[_sender] != true, \"_sender must not be blocked\");\n require(blocked[_from] != true, \"_from must not be blocked\");\n require(blocked[_to] != true, \"_to must not be blocked\");\n\n uint256 balanceOfFrom = erc20Store.balances(_from);\n require(_value \u003c= balanceOfFrom, \"insufficient funds on `_from` balance\");\n\n uint256 senderAllowance = erc20Store.allowed(_from, _sender);\n require(_value \u003c= senderAllowance, \"insufficient allowance amount\");\n\n erc20Store.setBalance(_from, balanceOfFrom - _value);\n erc20Store.addBalance(_to, _value);\n\n erc20Store.setAllowance(_from, _sender, senderAllowance - _value);\n\n erc20Proxy.emitTransfer(_from, _to, _value);\n\n return true;\n }\n\n /** @notice Core logic of the ERC20 `transfer` function.\n *\n * @dev This function can only be called by the referenced proxy,\n * which has a `transfer` function.\n * Every argument passed to that function as well as the original\n * `msg.sender` gets passed to this function.\n * NOTE: transfers to the zero address are disallowed.\n *\n * @param _sender The address initiating the transfer in a proxy.\n */\n function transferWithSender(\n address _sender,\n address _to,\n uint256 _value\n )\n public\n onlyProxy\n returns (bool success)\n {\n require(_to != address(0), \"no null value for `_to`\");\n require(blocked[_sender] != true, \"_sender must not be blocked\");\n require(blocked[_to] != true, \"_to must not be blocked\");\n\n uint256 balanceOfSender = erc20Store.balances(_sender);\n require(_value \u003c= balanceOfSender, \"insufficient funds\");\n\n erc20Store.setBalance(_sender, balanceOfSender - _value);\n erc20Store.addBalance(_to, _value);\n\n erc20Proxy.emitTransfer(_sender, _to, _value);\n\n return true;\n }\n\n /** @notice Transfers the specified value from the balance in question.\n *\n * @dev Suspected balance is subtracted by the amount which will be transferred.\n *\n * @dev If the suspected balance has less than the amount requested, it will be set to 0.\n *\n * @param _from The address of suspected balance.\n *\n * @param _value The amount to transfer.\n *\n * @return success true if the transfer succeeded.\n */\n function forceTransfer(\n address _from,\n address _to,\n uint256 _value\n )\n public\n onlyCustodian\n returns (bool success)\n {\n require(_to != address(0), \"no null value for `_to`\");\n uint256 balanceOfSender = erc20Store.balances(_from);\n if(_value \u003c= balanceOfSender) {\n erc20Store.setBalance(_from, balanceOfSender - _value);\n erc20Store.addBalance(_to, _value);\n\n erc20Proxy.emitTransfer(_from, _to, _value);\n } else {\n erc20Store.setBalance(_from, 0);\n erc20Store.addBalance(_to, balanceOfSender);\n\n erc20Proxy.emitTransfer(_from, _to, balanceOfSender);\n }\n\n return true;\n }\n\n // METHODS (ERC20 sub interface impl.)\n /// @notice Core logic of the ERC20 `totalSupply` function.\n function totalSupply() public view returns (uint256) {\n return erc20Store.totalSupply();\n }\n\n /// @notice Core logic of the ERC20 `balanceOf` function.\n function balanceOf(address _owner) public view returns (uint256 balance) {\n return erc20Store.balances(_owner);\n }\n\n /// @notice Core logic of the ERC20 `allowance` function.\n function allowance(address _owner, address _spender) public view returns (uint256 remaining) {\n return erc20Store.allowed(_owner, _spender);\n }\n\n /// @dev internal use only.\n function blockWallet(address wallet) public onlyCustodian returns (bool success) {\n blocked[wallet] = true;\n return true;\n }\n\n /// @dev internal use only.\n function unblockWallet(address wallet) public onlyCustodian returns (bool success) {\n blocked[wallet] = false;\n return true;\n }\n\n // EVENTS\n /// @dev Emitted by successful `requestPrint` calls.\n event PrintingLocked(bytes32 _lockId, address _receiver, uint256 _value);\n\n /// @dev Emitted by successful `confirmPrint` calls.\n event PrintingConfirmed(bytes32 _lockId, address _receiver, uint256 _value);\n\n /** @dev Emitted by successful `confirmWipe` calls.\n *\n * @param _value Amount requested to be burned.\n *\n * @param _burned Amount which was burned.\n *\n * @param _balance Amount left on account after burn.\n *\n * @param _from Account which balance was burned.\n */\n event Wiped(address _from, uint256 _value, uint256 _burned, uint _balance);\n}\n\n/** @title A contact to govern hybrid control over increases to the token supply and managing accounts.\n *\n * @notice A contract that acts as a custodian of the active token\n * implementation, and an intermediary between it and the ‘true’ custodian.\n * It preserves the functionality of direct custodianship as well as granting\n * limited control of token supply increases to an additional key.\n *\n * @dev This contract is a layer of indirection between an instance of\n * ERC20Impl and a custodian. The functionality of the custodianship over\n * the token implementation is preserved (printing and custodian changes),\n * but this contract adds the ability for an additional key\n * (the \u0027controller\u0027) to increase the token supply up to a ceiling,\n * and this supply ceiling can only be raised by the custodian.\n *\n */\ncontract Controller is LockRequestable {\n\n // TYPES\n /// @dev The struct type for pending ceiling raises.\n struct PendingCeilingRaise {\n uint256 raiseBy;\n }\n\n /// @dev The struct type for pending wipes.\n struct wipeAddress {\n uint256 value;\n address from;\n }\n\n /// @dev The struct type for pending force transfer requests.\n struct forceTransferRequest {\n uint256 value;\n address from;\n address to;\n }\n\n // MEMBERS\n /// @dev The reference to the active token implementation.\n ERC20Impl public erc20Impl;\n\n /// @dev The address of the account or contract that acts as the custodian.\n Custodian public custodian;\n\n /** @dev The sole authorized caller of limited printing.\n * This account is also authorized to lower the supply ceiling and\n * wiping suspected accounts or force transferring funds from them.\n */\n address public controller;\n\n /** @dev The maximum that the token supply can be increased to\n * through the use of the limited printing feature.\n * The difference between the current total supply and the supply\n * ceiling is what is available to the \u0027controller\u0027 account.\n * The value of the ceiling can only be increased by the custodian.\n */\n uint256 public totalSupplyCeiling;\n\n /// @dev The map of lock ids to pending ceiling raises.\n mapping (bytes32 =\u003e PendingCeilingRaise) public pendingRaiseMap;\n\n /// @dev The map of lock ids to pending wipes.\n mapping (bytes32 =\u003e wipeAddress[]) public pendingWipeMap;\n\n /// @dev The map of lock ids to pending force transfer requests.\n mapping (bytes32 =\u003e forceTransferRequest) public pendingForceTransferRequestMap;\n\n // CONSTRUCTOR\n constructor(\n address _erc20Impl,\n address _custodian,\n address _controller,\n uint256 _initialCeiling\n )\n public\n {\n erc20Impl = ERC20Impl(_erc20Impl);\n custodian = Custodian(_custodian);\n controller = _controller;\n totalSupplyCeiling = _initialCeiling;\n }\n\n // MODIFIERS\n modifier onlyCustodian {\n require(msg.sender == address(custodian), \"only custodian\");\n _;\n }\n modifier onlyController {\n require(msg.sender == controller, \"only controller\");\n _;\n }\n\n modifier onlySigner {\n require(custodian.signerSet(msg.sender) == true, \"only signer\");\n _;\n }\n\n /** @notice Increases the token supply, with the newly created tokens\n * being added to the balance of the specified account.\n *\n * @dev The function checks that the value to print does not\n * exceed the supply ceiling when added to the current total supply.\n * NOTE: printing to the zero address is disallowed.\n *\n * @param _receiver The receiving address of the print.\n * @param _value The number of tokens to add to the total supply and the\n * balance of the receiving address.\n */\n function limitedPrint(address _receiver, uint256 _value) public onlyController {\n uint256 totalSupply = erc20Impl.totalSupply();\n uint256 newTotalSupply = totalSupply + _value;\n\n require(newTotalSupply \u003e= totalSupply, \"new total supply overflow\");\n require(newTotalSupply \u003c= totalSupplyCeiling, \"total supply ceiling overflow\");\n erc20Impl.confirmPrint(erc20Impl.requestPrint(_receiver, _value));\n }\n\n /** @notice Requests wipe of suspected accounts.\n *\n * @dev Returns a unique lock id associated with the request.\n * Only controller can call this function, and only the custodian\n * can confirm the request.\n *\n * @param _froms The array of suspected accounts.\n *\n * @param _values array of amounts by which suspected accounts will be wiped.\n *\n * @return lockId A unique identifier for this request.\n */\n function requestWipe(address[] memory _froms, uint256[] memory _values) public onlyController returns (bytes32 lockId) {\n require(_froms.length == _values.length, \"_froms[] and _values[] must be same length\");\n lockId = generateLockId();\n uint256 amount = _froms.length;\n\n for(uint256 i = 0; i \u003c amount; i++) {\n address from = _froms[i];\n uint256 value = _values[i];\n pendingWipeMap[lockId].push(wipeAddress(value, from));\n }\n\n emit WipeRequested(lockId);\n\n return lockId;\n }\n\n /** @notice Confirms a pending wipe of suspected accounts.\n *\n * @dev When called by the custodian with a lock id associated with a\n * pending wipe, the amount requested is burned from the suspected accounts.\n *\n * @param _lockId The identifier of a pending wipe request.\n */\n function confirmWipe(bytes32 _lockId) public onlyCustodian {\n uint256 amount = pendingWipeMap[_lockId].length;\n for(uint256 i = 0; i \u003c amount; i++) {\n wipeAddress memory addr = pendingWipeMap[_lockId][i];\n address from = addr.from;\n uint256 value = addr.value;\n erc20Impl.burn(from, value);\n }\n\n delete pendingWipeMap[_lockId];\n\n emit WipeCompleted(_lockId);\n }\n\n /** @notice Requests force transfer from the suspected account.\n *\n * @dev Returns a unique lock id associated with the request.\n * Only controller can call this function, and only the custodian\n * can confirm the request.\n *\n * @param _from address of suspected account.\n *\n * @param _to address of reciever.\n *\n * @param _value amount which will be transferred.\n *\n * @return lockId A unique identifier for this request.\n */\n function requestForceTransfer(address _from, address _to, uint256 _value) public onlyController returns (bytes32 lockId) {\n lockId = generateLockId();\n require (_value != 0, \"no zero value transfers\");\n pendingForceTransferRequestMap[lockId] = forceTransferRequest(_value, _from, _to);\n\n emit ForceTransferRequested(lockId, _from, _to, _value);\n\n return lockId;\n }\n\n /** @notice Confirms a pending force transfer request.\n *\n * @dev When called by the custodian with a lock id associated with a\n * pending transfer request, the amount requested is transferred from the suspected account.\n *\n * @param _lockId The identifier of a pending transfer request.\n */\n function confirmForceTransfer(bytes32 _lockId) public onlyCustodian {\n address from = pendingForceTransferRequestMap[_lockId].from;\n address to = pendingForceTransferRequestMap[_lockId].to;\n uint256 value = pendingForceTransferRequestMap[_lockId].value;\n\n delete pendingForceTransferRequestMap[_lockId];\n\n erc20Impl.forceTransfer(from, to, value);\n\n emit ForceTransferCompleted(_lockId, from, to, value);\n }\n\n /** @notice Requests an increase to the supply ceiling.\n *\n * @dev Returns a unique lock id associated with the request.\n * Anyone can call this function, but confirming the request is authorized\n * by the custodian.\n *\n * @param _raiseBy The amount by which to raise the ceiling.\n *\n * @return lockId A unique identifier for this request.\n */\n function requestCeilingRaise(uint256 _raiseBy) public returns (bytes32 lockId) {\n require(_raiseBy != 0, \"no zero ceiling raise\");\n\n lockId = generateLockId();\n\n pendingRaiseMap[lockId] = PendingCeilingRaise({\n raiseBy: _raiseBy\n });\n\n emit CeilingRaiseLocked(lockId, _raiseBy);\n }\n\n /** @notice Confirms a pending increase in the token supply.\n *\n * @dev When called by the custodian with a lock id associated with a\n * pending ceiling increase, the amount requested is added to the\n * current supply ceiling.\n * NOTE: this function will not execute any raise that would overflow the\n * supply ceiling, but it will not revert either.\n *\n * @param _lockId The identifier of a pending ceiling raise request.\n */\n function confirmCeilingRaise(bytes32 _lockId) public onlyCustodian {\n PendingCeilingRaise storage pendingRaise = pendingRaiseMap[_lockId];\n\n // copy locals of references to struct members\n uint256 raiseBy = pendingRaise.raiseBy;\n // accounts for a gibberish _lockId\n require(raiseBy != 0, \"no gibberish _lockId\");\n\n delete pendingRaiseMap[_lockId];\n\n uint256 newCeiling = totalSupplyCeiling + raiseBy;\n // overflow check\n if (newCeiling \u003e= totalSupplyCeiling) {\n totalSupplyCeiling = newCeiling;\n\n emit CeilingRaiseConfirmed(_lockId, raiseBy, newCeiling);\n }\n }\n\n /** @notice Lowers the supply ceiling, further constraining the bound of\n * what can be printed by the controller.\n *\n * @dev The controller is the sole authorized caller of this function,\n * so it is the only account that can elect to lower its limit to increase\n * the token supply.\n *\n * @param _lowerBy The amount by which to lower the supply ceiling.\n */\n function lowerCeiling(uint256 _lowerBy) public onlyController {\n uint256 newCeiling = totalSupplyCeiling - _lowerBy;\n // overflow check\n require(newCeiling \u003c= totalSupplyCeiling, \"totalSupplyCeiling overflow\");\n totalSupplyCeiling = newCeiling;\n\n emit CeilingLowered(_lowerBy, newCeiling);\n }\n\n /** @notice Pass-through control of print confirmation, allowing this\n * contract\u0027s custodian to act as the custodian of the associated\n * active token implementation.\n *\n * @dev This contract is the direct custodian of the active token\n * implementation, but this function allows this contract\u0027s custodian\n * to act as though it were the direct custodian of the active\n * token implementation. Therefore the custodian retains control of\n * unlimited printing.\n *\n * @param _lockId The identifier of a pending print request in\n * the associated active token implementation.\n */\n function confirmPrintProxy(bytes32 _lockId) public onlyCustodian {\n erc20Impl.confirmPrint(_lockId);\n }\n\n /** @notice Pass-through control of custodian change confirmation,\n * allowing this contract\u0027s custodian to act as the custodian of\n * the associated active token implementation.\n *\n * @dev This contract is the direct custodian of the active token\n * implementation, but this function allows this contract\u0027s custodian\n * to act as though it were the direct custodian of the active\n * token implementation. Therefore the custodian retains control of\n * custodian changes.\n *\n * @param _lockId The identifier of a pending custodian change request\n * in the associated active token implementation.\n */\n function confirmCustodianChangeProxy(bytes32 _lockId) public onlyCustodian {\n erc20Impl.confirmCustodianChange(_lockId);\n }\n\n /** @notice Blocks all transactions with a wallet.\n *\n * @dev Only signers from custodian are authorized to call this function\n *\n * @param wallet account which will be blocked.\n */\n function blockWallet(address wallet) public onlySigner {\n erc20Impl.blockWallet(wallet);\n emit Blocked(wallet);\n }\n\n /** @notice Unblocks all transactions with a wallet.\n *\n * @dev Only signers from custodian are authorized to call this function\n *\n * @param wallet account which will be unblocked.\n */\n function unblockWallet(address wallet) public onlySigner {\n erc20Impl.unblockWallet(wallet);\n emit Unblocked(wallet);\n }\n\n // EVENTS\n /// @dev Emitted by successful `requestCeilingRaise` calls.\n event CeilingRaiseLocked(bytes32 _lockId, uint256 _raiseBy);\n\n /// @dev Emitted by successful `confirmCeilingRaise` calls.\n event CeilingRaiseConfirmed(bytes32 _lockId, uint256 _raiseBy, uint256 _newCeiling);\n\n /// @dev Emitted by successful `lowerCeiling` calls.\n event CeilingLowered(uint256 _lowerBy, uint256 _newCeiling);\n\n /// @dev Emitted by successful `blockWallet` calls.\n event Blocked(address _wallet);\n\n /// @dev Emitted by successful `unblockWallet` calls.\n event Unblocked(address _wallet);\n\n /// @dev Emitted by successful `requestForceTransfer` calls.\n event ForceTransferRequested(bytes32 _lockId, address _from, address _to, uint256 _value);\n\n /// @dev Emitted by successful `confirmForceTransfer` calls.\n event ForceTransferCompleted(bytes32 _lockId, address _from, address _to, uint256 _value);\n\n /// @dev Emitted by successful `requestWipe` calls.\n event WipeRequested(bytes32 _lockId);\n\n /// @dev Emitted by successful `confirmWipe` calls.\n event WipeCompleted(bytes32 _lockId);\n}"}}
File 2 of 3: ERC20Impl
pragma solidity ^0.5.10; /** @title A contract for generating unique identifiers * * @notice A contract that provides an identifier generation scheme, * guaranteeing uniqueness across all contracts that inherit from it, * as well as the unpredictability of future identifiers. * * @dev This contract is intended to be inherited by any contract that * implements the callback software pattern for cooperative custodianship. * */ contract LockRequestable { // MEMBERS /// @notice the count of all invocations of `generateLockId`. uint256 public lockRequestCount; // CONSTRUCTOR constructor() public { lockRequestCount = 0; } // FUNCTIONS /** @notice Returns a fresh unique identifier. * * @dev the generation scheme uses three components. * First, the blockhash of the previous block. * Second, the deployed address. * Third, the next value of the counter. * This ensures that identifiers are unique across all contracts * following this scheme, and that future identifiers are * unpredictable. * * @return a 32-byte unique identifier. */ function generateLockId() internal returns (bytes32 lockId) { return keccak256(abi.encodePacked(blockhash(block.number - 1), address(this), ++lockRequestCount)); } } contract ERC20Interface { // METHODS // NOTE: // public getter functions are not currently recognised as an // implementation of the matching abstract function by the compiler. // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#name // function name() public view returns (string); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#symbol // function symbol() public view returns (string); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#totalsupply // function decimals() public view returns (uint8); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#totalsupply function totalSupply() public view returns (uint256); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#balanceof function balanceOf(address _owner) public view returns (uint256 balance); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer function transfer(address _to, uint256 _value) public returns (bool success); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transferfrom function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approve function approve(address _spender, uint256 _value) public returns (bool success); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#allowance function allowance(address _owner, address _spender) public view returns (uint256 remaining); // EVENTS // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer-1 event Transfer(address indexed _from, address indexed _to, uint256 _value); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approval event Approval(address indexed _owner, address indexed _spender, uint256 _value); } /** @title A dual control contract. * * @notice A general-purpose contract that implements dual control over * co-operating contracts through a callback mechanism. * * @dev This contract implements dual control through a 2-of-N * threshold multi-signature scheme. The contract recognizes a set of N signers, * and will unlock requests with signatures from any distinct pair of them. * This contract signals the unlocking through a co-operative callback * scheme. * This contract also provides time lock and revocation features. * Requests made by a 'primary' account have a default time lock applied. * All other requests must pay a 1 ETH stake and have an extended time lock * applied. * A request that is completed will prevent all previous pending requests * that share the same callback from being completed: this is the * revocation feature. * */ contract Custodian { // TYPES /** @dev The `Request` struct stores a pending unlocking. * `callbackAddress` and `callbackSelector` are the data required to * make a callback. The custodian completes the process by * calling `callbackAddress.call(callbackSelector, lockId)`, which * signals to the contract co-operating with the Custodian that * the 2-of-N signatures have been provided and verified. */ struct Request { bytes32 lockId; bytes4 callbackSelector; // bytes4 and address can be packed into 1 word address callbackAddress; uint256 idx; uint256 timestamp; bool extended; } // EVENTS /// @dev Emitted by successful `requestUnlock` calls. event Requested( bytes32 _lockId, address _callbackAddress, bytes4 _callbackSelector, uint256 _nonce, address _whitelistedAddress, bytes32 _requestMsgHash, uint256 _timeLockExpiry ); /// @dev Emitted by `completeUnlock` calls on requests in the time-locked state. event TimeLocked( uint256 _timeLockExpiry, bytes32 _requestMsgHash ); /// @dev Emitted by successful `completeUnlock` calls. event Completed( bytes32 _lockId, bytes32 _requestMsgHash, address _signer1, address _signer2 ); /// @dev Emitted by `completeUnlock` calls where the callback failed. event Failed( bytes32 _lockId, bytes32 _requestMsgHash, address _signer1, address _signer2 ); /// @dev Emitted by successful `extendRequestTimeLock` calls. event TimeLockExtended( uint256 _timeLockExpiry, bytes32 _requestMsgHash ); // MEMBERS /** @dev The count of all requests. * This value is used as a nonce, incorporated into the request hash. */ uint256 public requestCount; /// @dev The set of signers: signatures from two signers unlock a pending request. mapping (address => bool) public signerSet; /// @dev The map of request hashes to pending requests. mapping (bytes32 => Request) public requestMap; /// @dev The map of callback addresses to callback selectors to request indexes. mapping (address => mapping (bytes4 => uint256)) public lastCompletedIdxs; /** @dev The default period (in seconds) to time-lock requests. * All requests will be subject to this default time lock, and the duration * is fixed at contract creation. */ uint256 public defaultTimeLock; /** @dev The extended period (in seconds) to time-lock requests. * Requests not from the primary account are subject to this time lock. * The primary account may also elect to extend the time lock on requests * that originally received the default. */ uint256 public extendedTimeLock; /// @dev The primary account is the privileged account for making requests. address public primary; // CONSTRUCTOR constructor( address[] memory _signers, uint256 _defaultTimeLock, uint256 _extendedTimeLock, address _primary ) public { // check for at least two `_signers` require(_signers.length >= 2, "at least two `_signers`"); // validate time lock params require(_defaultTimeLock <= _extendedTimeLock, "valid timelock params"); defaultTimeLock = _defaultTimeLock; extendedTimeLock = _extendedTimeLock; primary = _primary; // explicitly initialize `requestCount` to zero requestCount = 0; // turn the array into a set for (uint i = 0; i < _signers.length; i++) { // no zero addresses or duplicates require(_signers[i] != address(0) && !signerSet[_signers[i]], "no zero addresses or duplicates"); signerSet[_signers[i]] = true; } } // MODIFIERS modifier onlyPrimary { require(msg.sender == primary, "only primary"); _; } modifier onlySigner { require(signerSet[msg.sender], "only signer"); _; } // METHODS /** @notice Requests an unlocking with a lock identifier and a callback. * * @dev If called by an account other than the primary a 1 ETH stake * must be paid. When the request is unlocked stake will be transferred to the message sender. * This is an anti-spam measure. As well as the callback * and the lock identifier parameters a 'whitelisted address' is required * for compatibility with existing signature schemes. * * @param _lockId The identifier of a pending request in a co-operating contract. * @param _callbackAddress The address of a co-operating contract. * @param _callbackSelector The function selector of a function within * the co-operating contract at address `_callbackAddress`. * @param _whitelistedAddress An address whitelisted in existing * offline control protocols. * * @return requestMsgHash The hash of a request message to be signed. */ function requestUnlock( bytes32 _lockId, address _callbackAddress, bytes4 _callbackSelector, address _whitelistedAddress ) public payable returns (bytes32 requestMsgHash) { require(msg.sender == primary || msg.value >= 1 ether, "sender is primary or stake is paid"); // disallow using a zero value for the callback address require(_callbackAddress != address(0), "no zero value for callback address"); uint256 requestIdx = ++requestCount; // compute a nonce value // - the blockhash prevents prediction of future nonces // - the address of this contract prevents conflicts with co-operating contracts using this scheme // - the counter prevents conflicts arising from multiple txs within the same block uint256 nonce = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), address(this), requestIdx))); requestMsgHash = keccak256( abi.encodePacked( nonce, _whitelistedAddress, uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) ); requestMap[requestMsgHash] = Request({ lockId: _lockId, callbackSelector: _callbackSelector, callbackAddress: _callbackAddress, idx: requestIdx, timestamp: block.timestamp, extended: false }); // compute the expiry time uint256 timeLockExpiry = block.timestamp; if (msg.sender == primary) { timeLockExpiry += defaultTimeLock; } else { timeLockExpiry += extendedTimeLock; // any sender that is not the creator will get the extended time lock requestMap[requestMsgHash].extended = true; } emit Requested(_lockId, _callbackAddress, _callbackSelector, nonce, _whitelistedAddress, requestMsgHash, timeLockExpiry); } /** @notice Completes a pending unlocking with two signatures. * * @dev Given a request message hash as two signatures of it from * two distinct signers in the signer set, this function completes the * unlocking of the pending request by executing the callback. * * @param _requestMsgHash The request message hash of a pending request. * @param _recoveryByte1 The public key recovery byte (27 or 28) * @param _ecdsaR1 The R component of an ECDSA signature (R, S) pair * @param _ecdsaS1 The S component of an ECDSA signature (R, S) pair * @param _recoveryByte2 The public key recovery byte (27 or 28) * @param _ecdsaR2 The R component of an ECDSA signature (R, S) pair * @param _ecdsaS2 The S component of an ECDSA signature (R, S) pair * * @return success True if the callback successfully executed. */ function completeUnlock( bytes32 _requestMsgHash, uint8 _recoveryByte1, bytes32 _ecdsaR1, bytes32 _ecdsaS1, uint8 _recoveryByte2, bytes32 _ecdsaR2, bytes32 _ecdsaS2 ) public onlySigner returns (bool success) { Request storage request = requestMap[_requestMsgHash]; // copy storage to locals before `delete` bytes32 lockId = request.lockId; address callbackAddress = request.callbackAddress; bytes4 callbackSelector = request.callbackSelector; // failing case of the lookup if the callback address is zero require(callbackAddress != address(0), "no zero value for callback address"); // reject confirms of earlier withdrawals buried under later confirmed withdrawals require(request.idx > lastCompletedIdxs[callbackAddress][callbackSelector], "reject confirms of earlier withdrawals buried under later confirmed withdrawals"); address signer1 = ecrecover( keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _requestMsgHash)), _recoveryByte1, _ecdsaR1, _ecdsaS1 ); require(signerSet[signer1], "signer is set"); address signer2 = ecrecover( keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _requestMsgHash)), _recoveryByte2, _ecdsaR2, _ecdsaS2 ); require(signerSet[signer2], "signer is set"); require(signer1 != signer2, "signers are different"); if (request.extended && ((block.timestamp - request.timestamp) < extendedTimeLock)) { emit TimeLocked(request.timestamp + extendedTimeLock, _requestMsgHash); return false; } else if ((block.timestamp - request.timestamp) < defaultTimeLock) { emit TimeLocked(request.timestamp + defaultTimeLock, _requestMsgHash); return false; } else { if (address(this).balance > 0) { // reward sender with anti-spam payments msg.sender.transfer(address(this).balance); } // raise the waterline for the last completed unlocking lastCompletedIdxs[callbackAddress][callbackSelector] = request.idx; // and delete the request delete requestMap[_requestMsgHash]; // invoke callback (success,) = callbackAddress.call(abi.encodeWithSelector(callbackSelector, lockId)); if (success) { emit Completed(lockId, _requestMsgHash, signer1, signer2); } else { emit Failed(lockId, _requestMsgHash, signer1, signer2); } } } /** @notice Reclaim the storage of a pending request that is uncompletable. * * @dev If a pending request shares the callback (address and selector) of * a later request has been completed, then the request can no longer * be completed. This function will reclaim the contract storage of the * pending request. * * @param _requestMsgHash The request message hash of a pending request. */ function deleteUncompletableRequest(bytes32 _requestMsgHash) public { Request storage request = requestMap[_requestMsgHash]; uint256 idx = request.idx; require(0 < idx && idx < lastCompletedIdxs[request.callbackAddress][request.callbackSelector], "there must be a completed latter request with same callback"); delete requestMap[_requestMsgHash]; } /** @notice Extend the time lock of a pending request. * * @dev Requests made by the primary account receive the default time lock. * This function allows the primary account to apply the extended time lock * to one its own requests. * * @param _requestMsgHash The request message hash of a pending request. */ function extendRequestTimeLock(bytes32 _requestMsgHash) public onlyPrimary { Request storage request = requestMap[_requestMsgHash]; // reject ‘null’ results from the map lookup // this can only be the case if an unknown `_requestMsgHash` is received require(request.callbackAddress != address(0), "reject ‘null’ results from the map lookup"); // `extendRequestTimeLock` must be idempotent require(request.extended != true, "`extendRequestTimeLock` must be idempotent"); // set the `extended` flag; note that this is never unset request.extended = true; emit TimeLockExtended(request.timestamp + extendedTimeLock, _requestMsgHash); } } /** @title A contract to inherit upgradeable custodianship. * * @notice A contract that provides re-usable code for upgradeable * custodianship. That custodian may be an account or another contract. * * @dev This contract is intended to be inherited by any contract * requiring a custodian to control some aspect of its functionality. * This contract provides the mechanism for that custodianship to be * passed from one custodian to the next. * */ contract CustodianUpgradeable is LockRequestable { // TYPES /// @dev The struct type for pending custodian changes. struct CustodianChangeRequest { address proposedNew; } // MEMBERS /// @dev The address of the account or contract that acts as the custodian. address public custodian; /// @dev The map of lock ids to pending custodian changes. mapping (bytes32 => CustodianChangeRequest) public custodianChangeReqs; // CONSTRUCTOR constructor( address _custodian ) LockRequestable() public { custodian = _custodian; } // MODIFIERS modifier onlyCustodian { require(msg.sender == custodian, "only custodian"); _; } // PUBLIC FUNCTIONS // (UPGRADE) /** @notice Requests a change of the custodian associated with this contract. * * @dev Returns a unique lock id associated with the request. * Anyone can call this function, but confirming the request is authorized * by the custodian. * * @param _proposedCustodian The address of the new custodian. * @return lockId A unique identifier for this request. */ function requestCustodianChange(address _proposedCustodian) public returns (bytes32 lockId) { require(_proposedCustodian != address(0), "no null value for `_proposedCustodian`"); lockId = generateLockId(); custodianChangeReqs[lockId] = CustodianChangeRequest({ proposedNew: _proposedCustodian }); emit CustodianChangeRequested(lockId, msg.sender, _proposedCustodian); } /** @notice Confirms a pending change of the custodian associated with this contract. * * @dev When called by the current custodian with a lock id associated with a * pending custodian change, the `address custodian` member will be updated with the * requested address. * * @param _lockId The identifier of a pending change request. */ function confirmCustodianChange(bytes32 _lockId) public onlyCustodian { custodian = getCustodianChangeReq(_lockId); delete custodianChangeReqs[_lockId]; emit CustodianChangeConfirmed(_lockId, custodian); } // PRIVATE FUNCTIONS function getCustodianChangeReq(bytes32 _lockId) private view returns (address _proposedNew) { CustodianChangeRequest storage changeRequest = custodianChangeReqs[_lockId]; // reject ‘null’ results from the map lookup // this can only be the case if an unknown `_lockId` is received require(changeRequest.proposedNew != address(0), "reject ‘null’ results from the map lookup"); return changeRequest.proposedNew; } //EVENTS /// @dev Emitted by successful `requestCustodianChange` calls. event CustodianChangeRequested( bytes32 _lockId, address _msgSender, address _proposedCustodian ); /// @dev Emitted by successful `confirmCustodianChange` calls. event CustodianChangeConfirmed(bytes32 _lockId, address _newCustodian); } /** @title A contract to inherit upgradeable token implementations. * * @notice A contract that provides re-usable code for upgradeable * token implementations. It itself inherits from `CustodianUpgradable` * as the upgrade process is controlled by the custodian. * * @dev This contract is intended to be inherited by any contract * requiring a reference to the active token implementation, either * to delegate calls to it, or authorize calls from it. This contract * provides the mechanism for that implementation to be replaced, * which constitutes an implementation upgrade. * */ contract ERC20ImplUpgradeable is CustodianUpgradeable { // TYPES /// @dev The struct type for pending implementation changes. struct ImplChangeRequest { address proposedNew; } // MEMBERS // @dev The reference to the active token implementation. ERC20Impl public erc20Impl; /// @dev The map of lock ids to pending implementation changes. mapping (bytes32 => ImplChangeRequest) public implChangeReqs; // CONSTRUCTOR constructor(address _custodian) CustodianUpgradeable(_custodian) public { erc20Impl = ERC20Impl(0x0); } // MODIFIERS modifier onlyImpl { require(msg.sender == address(erc20Impl), "only ERC20Impl"); _; } // PUBLIC FUNCTIONS // (UPGRADE) /** @notice Requests a change of the active implementation associated * with this contract. * * @dev Returns a unique lock id associated with the request. * Anyone can call this function, but confirming the request is authorized * by the custodian. * * @param _proposedImpl The address of the new active implementation. * @return lockId A unique identifier for this request. */ function requestImplChange(address _proposedImpl) public returns (bytes32 lockId) { require(_proposedImpl != address(0), "no null value for `_proposedImpl`"); lockId = generateLockId(); implChangeReqs[lockId] = ImplChangeRequest({ proposedNew: _proposedImpl }); emit ImplChangeRequested(lockId, msg.sender, _proposedImpl); } /** @notice Confirms a pending change of the active implementation * associated with this contract. * * @dev When called by the custodian with a lock id associated with a * pending change, the `ERC20Impl erc20Impl` member will be updated * with the requested address. * * @param _lockId The identifier of a pending change request. */ function confirmImplChange(bytes32 _lockId) public onlyCustodian { erc20Impl = getImplChangeReq(_lockId); delete implChangeReqs[_lockId]; emit ImplChangeConfirmed(_lockId, address(erc20Impl)); } // PRIVATE FUNCTIONS function getImplChangeReq(bytes32 _lockId) private view returns (ERC20Impl _proposedNew) { ImplChangeRequest storage changeRequest = implChangeReqs[_lockId]; // reject ‘null’ results from the map lookup // this can only be the case if an unknown `_lockId` is received require(changeRequest.proposedNew != address(0), "reject ‘null’ results from the map lookup"); return ERC20Impl(changeRequest.proposedNew); } //EVENTS /// @dev Emitted by successful `requestImplChange` calls. event ImplChangeRequested( bytes32 _lockId, address _msgSender, address _proposedImpl ); /// @dev Emitted by successful `confirmImplChange` calls. event ImplChangeConfirmed(bytes32 _lockId, address _newImpl); } /** @title Public interface to ERC20 compliant token. * * @notice This contract is a permanent entry point to an ERC20 compliant * system of contracts. * * @dev This contract contains no business logic and instead * delegates to an instance of ERC20Impl. This contract also has no storage * that constitutes the operational state of the token. This contract is * upgradeable in the sense that the `custodian` can update the * `erc20Impl` address, thus redirecting the delegation of business logic. * The `custodian` is also authorized to pass custodianship. * */ contract ERC20Proxy is ERC20Interface, ERC20ImplUpgradeable { // MEMBERS /// @notice Returns the name of the token. string public name; /// @notice Returns the symbol of the token. string public symbol; /// @notice Returns the number of decimals the token uses. uint8 public decimals; // CONSTRUCTOR constructor( string memory _name, string memory _symbol, uint8 _decimals, address _custodian ) ERC20ImplUpgradeable(_custodian) public { name = _name; symbol = _symbol; decimals = _decimals; } // PUBLIC FUNCTIONS // (ERC20Interface) /** @notice Returns the total token supply. * * @return the total token supply. */ function totalSupply() public view returns (uint256) { return erc20Impl.totalSupply(); } /** @notice Returns the account balance of another account with an address * `_owner`. * * @return balance the balance of account with address `_owner`. */ function balanceOf(address _owner) public view returns (uint256 balance) { return erc20Impl.balanceOf(_owner); } /** @dev Internal use only. */ function emitTransfer(address _from, address _to, uint256 _value) public onlyImpl { emit Transfer(_from, _to, _value); } /** @notice Transfers `_value` amount of tokens to address `_to`. * * @dev Will fire the `Transfer` event. Will revert if the `_from` * account balance does not have enough tokens to spend. * * @return success true if transfer completes. */ function transfer(address _to, uint256 _value) public returns (bool success) { return erc20Impl.transferWithSender(msg.sender, _to, _value); } /** @notice Transfers `_value` amount of tokens from address `_from` * to address `_to`. * * @dev Will fire the `Transfer` event. Will revert unless the `_from` * account has deliberately authorized the sender of the message * via some mechanism. * * @return success true if transfer completes. */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { return erc20Impl.transferFromWithSender(msg.sender, _from, _to, _value); } /** @dev Internal use only. */ function emitApproval(address _owner, address _spender, uint256 _value) public onlyImpl { emit Approval(_owner, _spender, _value); } /** @notice Allows `_spender` to withdraw from your account multiple times, * up to the `_value` amount. If this function is called again it * overwrites the current allowance with _value. * * @dev Will fire the `Approval` event. * * @return success true if approval completes. */ function approve(address _spender, uint256 _value) public returns (bool success) { return erc20Impl.approveWithSender(msg.sender, _spender, _value); } /** @notice Increases the amount `_spender` is allowed to withdraw from * your account. * This function is implemented to avoid the race condition in standard * ERC20 contracts surrounding the `approve` method. * * @dev Will fire the `Approval` event. This function should be used instead of * `approve`. * * @return success true if approval completes. */ function increaseApproval(address _spender, uint256 _addedValue) public returns (bool success) { return erc20Impl.increaseApprovalWithSender(msg.sender, _spender, _addedValue); } /** @notice Decreases the amount `_spender` is allowed to withdraw from * your account. This function is implemented to avoid the race * condition in standard ERC20 contracts surrounding the `approve` method. * * @dev Will fire the `Approval` event. This function should be used * instead of `approve`. * * @return success true if approval completes. */ function decreaseApproval(address _spender, uint256 _subtractedValue) public returns (bool success) { return erc20Impl.decreaseApprovalWithSender(msg.sender, _spender, _subtractedValue); } /** @notice Returns how much `_spender` is currently allowed to spend from * `_owner`'s balance. * * @return remaining the remaining allowance. */ function allowance(address _owner, address _spender) public view returns (uint256 remaining) { return erc20Impl.allowance(_owner, _spender); } } /** @title ERC20 compliant token balance store. * * @notice This contract serves as the store of balances, allowances, and * supply for the ERC20 compliant token. No business logic exists here. * * @dev This contract contains no business logic and instead * is the final destination for any change in balances, allowances, or token * supply. This contract is upgradeable in the sense that its custodian can * update the `erc20Impl` address, thus redirecting the source of logic that * determines how the balances will be updated. * */ contract ERC20Store is ERC20ImplUpgradeable { // MEMBERS /// @dev The total token supply. uint256 public totalSupply; /// @dev The mapping of balances. mapping (address => uint256) public balances; /// @dev The mapping of allowances. mapping (address => mapping (address => uint256)) public allowed; // CONSTRUCTOR constructor(address _custodian) ERC20ImplUpgradeable(_custodian) public { totalSupply = 0; } // PUBLIC FUNCTIONS // (ERC20 Ledger) /** @notice The function to set the total supply of tokens. * * @dev Intended for use by token implementation functions * that update the total supply. The only authorized caller * is the active implementation. * * @param _newTotalSupply the value to set as the new total supply */ function setTotalSupply( uint256 _newTotalSupply ) public onlyImpl { totalSupply = _newTotalSupply; } /** @notice Sets how much `_owner` allows `_spender` to transfer on behalf * of `_owner`. * * @dev Intended for use by token implementation functions * that update spending allowances. The only authorized caller * is the active implementation. * * @param _owner The account that will allow an on-behalf-of spend. * @param _spender The account that will spend on behalf of the owner. * @param _value The limit of what can be spent. */ function setAllowance( address _owner, address _spender, uint256 _value ) public onlyImpl { allowed[_owner][_spender] = _value; } /** @notice Sets the balance of `_owner` to `_newBalance`. * * @dev Intended for use by token implementation functions * that update balances. The only authorized caller * is the active implementation. * * @param _owner The account that will hold a new balance. * @param _newBalance The balance to set. */ function setBalance( address _owner, uint256 _newBalance ) public onlyImpl { balances[_owner] = _newBalance; } /** @notice Adds `_balanceIncrease` to `_owner`'s balance. * * @dev Intended for use by token implementation functions * that update balances. The only authorized caller * is the active implementation. * WARNING: the caller is responsible for preventing overflow. * * @param _owner The account that will hold a new balance. * @param _balanceIncrease The balance to add. */ function addBalance( address _owner, uint256 _balanceIncrease ) public onlyImpl { balances[_owner] = balances[_owner] + _balanceIncrease; } } /** @title ERC20 compliant token intermediary contract holding core logic. * * @notice This contract serves as an intermediary between the exposed ERC20 * interface in ERC20Proxy and the store of balances in ERC20Store. This * contract contains core logic that the proxy can delegate to * and that the store is called by. * * @dev This contract contains the core logic to implement the * ERC20 specification as well as several extensions. * 1. Changes to the token supply. * 2. Batched transfers. * 3. Relative changes to spending approvals. * 4. Delegated transfer control ('sweeping'). * */ contract ERC20Impl is CustodianUpgradeable { // TYPES /// @dev The struct type for pending increases to the token supply (print). struct PendingPrint { address receiver; uint256 value; } // MEMBERS /// @dev The reference to the proxy. ERC20Proxy public erc20Proxy; /// @dev The reference to the store. ERC20Store public erc20Store; /// @dev The sole authorized caller of delegated transfer control ('sweeping'). address public sweeper; /** @dev The static message to be signed by an external account that * signifies their permission to forward their balance to any arbitrary * address. This is used to consolidate the control of all accounts * backed by a shared keychain into the control of a single key. * Initialized as the concatenation of the address of this contract * and the word "sweep". This concatenation is done to prevent a replay * attack in a subsequent contract, where the sweeping message could * potentially be replayed to re-enable sweeping ability. */ bytes32 public sweepMsg; /** @dev The mapping that stores whether the address in question has * enabled sweeping its contents to another account or not. * If an address maps to "true", it has already enabled sweeping, * and thus does not need to re-sign the `sweepMsg` to enact the sweep. */ mapping (address => bool) public sweptSet; /// @dev The map of lock ids to pending token increases. mapping (bytes32 => PendingPrint) public pendingPrintMap; /// @dev The map of blocked addresses. mapping (address => bool) public blocked; // CONSTRUCTOR constructor( address _erc20Proxy, address _erc20Store, address _custodian, address _sweeper ) CustodianUpgradeable(_custodian) public { require(_sweeper != address(0), "no null value for `_sweeper`"); erc20Proxy = ERC20Proxy(_erc20Proxy); erc20Store = ERC20Store(_erc20Store); sweeper = _sweeper; sweepMsg = keccak256(abi.encodePacked(address(this), "sweep")); } // MODIFIERS modifier onlyProxy { require(msg.sender == address(erc20Proxy), "only ERC20Proxy"); _; } modifier onlySweeper { require(msg.sender == sweeper, "only sweeper"); _; } /** @notice Core logic of the ERC20 `approve` function. * * @dev This function can only be called by the referenced proxy, * which has an `approve` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: approvals for the zero address (unspendable) are disallowed. * * @param _sender The address initiating the approval in a proxy. */ function approveWithSender( address _sender, address _spender, uint256 _value ) public onlyProxy returns (bool success) { require(_spender != address(0), "no null value for `_spender`"); require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_spender] != true, "_spender must not be blocked"); erc20Store.setAllowance(_sender, _spender, _value); erc20Proxy.emitApproval(_sender, _spender, _value); return true; } /** @notice Core logic of the `increaseApproval` function. * * @dev This function can only be called by the referenced proxy, * which has an `increaseApproval` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: approvals for the zero address (unspendable) are disallowed. * * @param _sender The address initiating the approval. */ function increaseApprovalWithSender( address _sender, address _spender, uint256 _addedValue ) public onlyProxy returns (bool success) { require(_spender != address(0),"no null value for_spender"); require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_spender] != true, "_spender must not be blocked"); uint256 currentAllowance = erc20Store.allowed(_sender, _spender); uint256 newAllowance = currentAllowance + _addedValue; require(newAllowance >= currentAllowance, "new allowance must not be smaller than previous"); erc20Store.setAllowance(_sender, _spender, newAllowance); erc20Proxy.emitApproval(_sender, _spender, newAllowance); return true; } /** @notice Core logic of the `decreaseApproval` function. * * @dev This function can only be called by the referenced proxy, * which has a `decreaseApproval` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: approvals for the zero address (unspendable) are disallowed. * * @param _sender The address initiating the approval. */ function decreaseApprovalWithSender( address _sender, address _spender, uint256 _subtractedValue ) public onlyProxy returns (bool success) { require(_spender != address(0), "no unspendable approvals"); // disallow unspendable approvals require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_spender] != true, "_spender must not be blocked"); uint256 currentAllowance = erc20Store.allowed(_sender, _spender); uint256 newAllowance = currentAllowance - _subtractedValue; require(newAllowance <= currentAllowance, "new allowance must not be smaller than previous"); erc20Store.setAllowance(_sender, _spender, newAllowance); erc20Proxy.emitApproval(_sender, _spender, newAllowance); return true; } /** @notice Requests an increase in the token supply, with the newly created * tokens to be added to the balance of the specified account. * * @dev Returns a unique lock id associated with the request. * Anyone can call this function, but confirming the request is authorized * by the custodian. * NOTE: printing to the zero address is disallowed. * * @param _receiver The receiving address of the print, if confirmed. * @param _value The number of tokens to add to the total supply and the * balance of the receiving address, if confirmed. * * @return lockId A unique identifier for this request. */ function requestPrint(address _receiver, uint256 _value) public returns (bytes32 lockId) { require(_receiver != address(0), "no null value for `_receiver`"); require(blocked[msg.sender] != true, "account blocked"); require(blocked[_receiver] != true, "_receiver must not be blocked"); lockId = generateLockId(); pendingPrintMap[lockId] = PendingPrint({ receiver: _receiver, value: _value }); emit PrintingLocked(lockId, _receiver, _value); } /** @notice Confirms a pending increase in the token supply. * * @dev When called by the custodian with a lock id associated with a * pending increase, the amount requested to be printed in the print request * is printed to the receiving address specified in that same request. * NOTE: this function will not execute any print that would overflow the * total supply, but it will not revert either. * * @param _lockId The identifier of a pending print request. */ function confirmPrint(bytes32 _lockId) public onlyCustodian { PendingPrint storage print = pendingPrintMap[_lockId]; // reject ‘null’ results from the map lookup // this can only be the case if an unknown `_lockId` is received address receiver = print.receiver; require (receiver != address(0), "unknown `_lockId`"); uint256 value = print.value; delete pendingPrintMap[_lockId]; uint256 supply = erc20Store.totalSupply(); uint256 newSupply = supply + value; if (newSupply >= supply) { erc20Store.setTotalSupply(newSupply); erc20Store.addBalance(receiver, value); emit PrintingConfirmed(_lockId, receiver, value); erc20Proxy.emitTransfer(address(0), receiver, value); } } /** @notice Burns the specified value from the sender's balance. * * @dev Sender's balanced is subtracted by the amount they wish to burn. * * @param _value The amount to burn. * * @return success true if the burn succeeded. */ function burn(uint256 _value) public returns (bool success) { require(blocked[msg.sender] != true, "account blocked"); uint256 balanceOfSender = erc20Store.balances(msg.sender); require(_value <= balanceOfSender, "disallow burning more, than amount of the balance"); erc20Store.setBalance(msg.sender, balanceOfSender - _value); erc20Store.setTotalSupply(erc20Store.totalSupply() - _value); erc20Proxy.emitTransfer(msg.sender, address(0), _value); return true; } /** @notice Burns the specified value from the balance in question. * * @dev Suspected balance is subtracted by the amount which will be burnt. * * @dev If the suspected balance has less than the amount requested, it will be set to 0. * * @param _from The address of suspected balance. * * @param _value The amount to burn. * * @return success true if the burn succeeded. */ function burn(address _from, uint256 _value) public onlyCustodian returns (bool success) { uint256 balance = erc20Store.balances(_from); if(_value <= balance){ erc20Store.setBalance(_from, balance - _value); erc20Store.setTotalSupply(erc20Store.totalSupply() - _value); erc20Proxy.emitTransfer(_from, address(0), _value); emit Wiped(_from, _value, _value, balance - _value); } else { erc20Store.setBalance(_from,0); erc20Store.setTotalSupply(erc20Store.totalSupply() - balance); erc20Proxy.emitTransfer(_from, address(0), balance); emit Wiped(_from, _value, balance, 0); } return true; } /** @notice A function for a sender to issue multiple transfers to multiple * different addresses at once. This function is implemented for gas * considerations when someone wishes to transfer, as one transaction is * cheaper than issuing several distinct individual `transfer` transactions. * * @dev By specifying a set of destination addresses and values, the * sender can issue one transaction to transfer multiple amounts to * distinct addresses, rather than issuing each as a separate * transaction. The `_tos` and `_values` arrays must be equal length, and * an index in one array corresponds to the same index in the other array * (e.g. `_tos[0]` will receive `_values[0]`, `_tos[1]` will receive * `_values[1]`, and so on.) * NOTE: transfers to the zero address are disallowed. * * @param _tos The destination addresses to receive the transfers. * @param _values The values for each destination address. * @return success If transfers succeeded. */ function batchTransfer(address[] memory _tos, uint256[] memory _values) public returns (bool success) { require(_tos.length == _values.length, "_tos and _values must be the same length"); require(blocked[msg.sender] != true, "account blocked"); uint256 numTransfers = _tos.length; uint256 senderBalance = erc20Store.balances(msg.sender); for (uint256 i = 0; i < numTransfers; i++) { address to = _tos[i]; require(to != address(0), "no null values for _tos"); require(blocked[to] != true, "_tos must not be blocked"); uint256 v = _values[i]; require(senderBalance >= v, "insufficient funds"); if (msg.sender != to) { senderBalance -= v; erc20Store.addBalance(to, v); } erc20Proxy.emitTransfer(msg.sender, to, v); } erc20Store.setBalance(msg.sender, senderBalance); return true; } /** @notice Enables the delegation of transfer control for many * accounts to the sweeper account, transferring any balances * as well to the given destination. * * @dev An account delegates transfer control by signing the * value of `sweepMsg`. The sweeper account is the only authorized * caller of this function, so it must relay signatures on behalf * of accounts that delegate transfer control to it. Enabling * delegation is idempotent and permanent. If the account has a * balance at the time of enabling delegation, its balance is * also transferred to the given destination account `_to`. * NOTE: transfers to the zero address are disallowed. * * @param _vs The array of recovery byte components of the ECDSA signatures. * @param _rs The array of 'R' components of the ECDSA signatures. * @param _ss The array of 'S' components of the ECDSA signatures. * @param _to The destination for swept balances. */ function enableSweep(uint8[] memory _vs, bytes32[] memory _rs, bytes32[] memory _ss, address _to) public onlySweeper { require(_to != address(0), "no null value for `_to`"); require(blocked[_to] != true, "_to must not be blocked"); require((_vs.length == _rs.length) && (_vs.length == _ss.length), "_vs[], _rs[], _ss lengths are different"); uint256 numSignatures = _vs.length; uint256 sweptBalance = 0; for (uint256 i = 0; i < numSignatures; ++i) { address from = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",sweepMsg)), _vs[i], _rs[i], _ss[i]); require(blocked[from] != true, "_froms must not be blocked"); // ecrecover returns 0 on malformed input if (from != address(0)) { sweptSet[from] = true; uint256 fromBalance = erc20Store.balances(from); if (fromBalance > 0) { sweptBalance += fromBalance; erc20Store.setBalance(from, 0); erc20Proxy.emitTransfer(from, _to, fromBalance); } } } if (sweptBalance > 0) { erc20Store.addBalance(_to, sweptBalance); } } /** @notice For accounts that have delegated, transfer control * to the sweeper, this function transfers their balances to the given * destination. * * @dev The sweeper account is the only authorized caller of * this function. This function accepts an array of addresses to have their * balances transferred for gas efficiency purposes. * NOTE: any address for an account that has not been previously enabled * will be ignored. * NOTE: transfers to the zero address are disallowed. * * @param _froms The addresses to have their balances swept. * @param _to The destination address of all these transfers. */ function replaySweep(address[] memory _froms, address _to) public onlySweeper { require(_to != address(0), "no null value for `_to`"); require(blocked[_to] != true, "_to must not be blocked"); uint256 lenFroms = _froms.length; uint256 sweptBalance = 0; for (uint256 i = 0; i < lenFroms; ++i) { address from = _froms[i]; require(blocked[from] != true, "_froms must not be blocked"); if (sweptSet[from]) { uint256 fromBalance = erc20Store.balances(from); if (fromBalance > 0) { sweptBalance += fromBalance; erc20Store.setBalance(from, 0); erc20Proxy.emitTransfer(from, _to, fromBalance); } } } if (sweptBalance > 0) { erc20Store.addBalance(_to, sweptBalance); } } /** @notice Core logic of the ERC20 `transferFrom` function. * * @dev This function can only be called by the referenced proxy, * which has a `transferFrom` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: transfers to the zero address are disallowed. * * @param _sender The address initiating the transfer in a proxy. */ function transferFromWithSender( address _sender, address _from, address _to, uint256 _value ) public onlyProxy returns (bool success) { require(_to != address(0), "no null values for `_to`"); require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_from] != true, "_from must not be blocked"); require(blocked[_to] != true, "_to must not be blocked"); uint256 balanceOfFrom = erc20Store.balances(_from); require(_value <= balanceOfFrom, "insufficient funds on `_from` balance"); uint256 senderAllowance = erc20Store.allowed(_from, _sender); require(_value <= senderAllowance, "insufficient allowance amount"); erc20Store.setBalance(_from, balanceOfFrom - _value); erc20Store.addBalance(_to, _value); erc20Store.setAllowance(_from, _sender, senderAllowance - _value); erc20Proxy.emitTransfer(_from, _to, _value); return true; } /** @notice Core logic of the ERC20 `transfer` function. * * @dev This function can only be called by the referenced proxy, * which has a `transfer` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: transfers to the zero address are disallowed. * * @param _sender The address initiating the transfer in a proxy. */ function transferWithSender( address _sender, address _to, uint256 _value ) public onlyProxy returns (bool success) { require(_to != address(0), "no null value for `_to`"); require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_to] != true, "_to must not be blocked"); uint256 balanceOfSender = erc20Store.balances(_sender); require(_value <= balanceOfSender, "insufficient funds"); erc20Store.setBalance(_sender, balanceOfSender - _value); erc20Store.addBalance(_to, _value); erc20Proxy.emitTransfer(_sender, _to, _value); return true; } /** @notice Transfers the specified value from the balance in question. * * @dev Suspected balance is subtracted by the amount which will be transferred. * * @dev If the suspected balance has less than the amount requested, it will be set to 0. * * @param _from The address of suspected balance. * * @param _value The amount to transfer. * * @return success true if the transfer succeeded. */ function forceTransfer( address _from, address _to, uint256 _value ) public onlyCustodian returns (bool success) { require(_to != address(0), "no null value for `_to`"); uint256 balanceOfSender = erc20Store.balances(_from); if(_value <= balanceOfSender) { erc20Store.setBalance(_from, balanceOfSender - _value); erc20Store.addBalance(_to, _value); erc20Proxy.emitTransfer(_from, _to, _value); } else { erc20Store.setBalance(_from, 0); erc20Store.addBalance(_to, balanceOfSender); erc20Proxy.emitTransfer(_from, _to, balanceOfSender); } return true; } // METHODS (ERC20 sub interface impl.) /// @notice Core logic of the ERC20 `totalSupply` function. function totalSupply() public view returns (uint256) { return erc20Store.totalSupply(); } /// @notice Core logic of the ERC20 `balanceOf` function. function balanceOf(address _owner) public view returns (uint256 balance) { return erc20Store.balances(_owner); } /// @notice Core logic of the ERC20 `allowance` function. function allowance(address _owner, address _spender) public view returns (uint256 remaining) { return erc20Store.allowed(_owner, _spender); } /// @dev internal use only. function blockWallet(address wallet) public onlyCustodian returns (bool success) { blocked[wallet] = true; return true; } /// @dev internal use only. function unblockWallet(address wallet) public onlyCustodian returns (bool success) { blocked[wallet] = false; return true; } // EVENTS /// @dev Emitted by successful `requestPrint` calls. event PrintingLocked(bytes32 _lockId, address _receiver, uint256 _value); /// @dev Emitted by successful `confirmPrint` calls. event PrintingConfirmed(bytes32 _lockId, address _receiver, uint256 _value); /** @dev Emitted by successful `confirmWipe` calls. * * @param _value Amount requested to be burned. * * @param _burned Amount which was burned. * * @param _balance Amount left on account after burn. * * @param _from Account which balance was burned. */ event Wiped(address _from, uint256 _value, uint256 _burned, uint _balance); } /** @title A contact to govern hybrid control over increases to the token supply and managing accounts. * * @notice A contract that acts as a custodian of the active token * implementation, and an intermediary between it and the ‘true’ custodian. * It preserves the functionality of direct custodianship as well as granting * limited control of token supply increases to an additional key. * * @dev This contract is a layer of indirection between an instance of * ERC20Impl and a custodian. The functionality of the custodianship over * the token implementation is preserved (printing and custodian changes), * but this contract adds the ability for an additional key * (the 'controller') to increase the token supply up to a ceiling, * and this supply ceiling can only be raised by the custodian. * */ contract Controller is LockRequestable { // TYPES /// @dev The struct type for pending ceiling raises. struct PendingCeilingRaise { uint256 raiseBy; } /// @dev The struct type for pending wipes. struct wipeAddress { uint256 value; address from; } /// @dev The struct type for pending force transfer requests. struct forceTransferRequest { uint256 value; address from; address to; } // MEMBERS /// @dev The reference to the active token implementation. ERC20Impl public erc20Impl; /// @dev The address of the account or contract that acts as the custodian. Custodian public custodian; /** @dev The sole authorized caller of limited printing. * This account is also authorized to lower the supply ceiling and * wiping suspected accounts or force transferring funds from them. */ address public controller; /** @dev The maximum that the token supply can be increased to * through the use of the limited printing feature. * The difference between the current total supply and the supply * ceiling is what is available to the 'controller' account. * The value of the ceiling can only be increased by the custodian. */ uint256 public totalSupplyCeiling; /// @dev The map of lock ids to pending ceiling raises. mapping (bytes32 => PendingCeilingRaise) public pendingRaiseMap; /// @dev The map of lock ids to pending wipes. mapping (bytes32 => wipeAddress[]) public pendingWipeMap; /// @dev The map of lock ids to pending force transfer requests. mapping (bytes32 => forceTransferRequest) public pendingForceTransferRequestMap; // CONSTRUCTOR constructor( address _erc20Impl, address _custodian, address _controller, uint256 _initialCeiling ) public { erc20Impl = ERC20Impl(_erc20Impl); custodian = Custodian(_custodian); controller = _controller; totalSupplyCeiling = _initialCeiling; } // MODIFIERS modifier onlyCustodian { require(msg.sender == address(custodian), "only custodian"); _; } modifier onlyController { require(msg.sender == controller, "only controller"); _; } modifier onlySigner { require(custodian.signerSet(msg.sender) == true, "only signer"); _; } /** @notice Increases the token supply, with the newly created tokens * being added to the balance of the specified account. * * @dev The function checks that the value to print does not * exceed the supply ceiling when added to the current total supply. * NOTE: printing to the zero address is disallowed. * * @param _receiver The receiving address of the print. * @param _value The number of tokens to add to the total supply and the * balance of the receiving address. */ function limitedPrint(address _receiver, uint256 _value) public onlyController { uint256 totalSupply = erc20Impl.totalSupply(); uint256 newTotalSupply = totalSupply + _value; require(newTotalSupply >= totalSupply, "new total supply overflow"); require(newTotalSupply <= totalSupplyCeiling, "total supply ceiling overflow"); erc20Impl.confirmPrint(erc20Impl.requestPrint(_receiver, _value)); } /** @notice Requests wipe of suspected accounts. * * @dev Returns a unique lock id associated with the request. * Only controller can call this function, and only the custodian * can confirm the request. * * @param _froms The array of suspected accounts. * * @param _values array of amounts by which suspected accounts will be wiped. * * @return lockId A unique identifier for this request. */ function requestWipe(address[] memory _froms, uint256[] memory _values) public onlyController returns (bytes32 lockId) { require(_froms.length == _values.length, "_froms[] and _values[] must be same length"); lockId = generateLockId(); uint256 amount = _froms.length; for(uint256 i = 0; i < amount; i++) { address from = _froms[i]; uint256 value = _values[i]; pendingWipeMap[lockId].push(wipeAddress(value, from)); } emit WipeRequested(lockId); return lockId; } /** @notice Confirms a pending wipe of suspected accounts. * * @dev When called by the custodian with a lock id associated with a * pending wipe, the amount requested is burned from the suspected accounts. * * @param _lockId The identifier of a pending wipe request. */ function confirmWipe(bytes32 _lockId) public onlyCustodian { uint256 amount = pendingWipeMap[_lockId].length; for(uint256 i = 0; i < amount; i++) { wipeAddress memory addr = pendingWipeMap[_lockId][i]; address from = addr.from; uint256 value = addr.value; erc20Impl.burn(from, value); } delete pendingWipeMap[_lockId]; emit WipeCompleted(_lockId); } /** @notice Requests force transfer from the suspected account. * * @dev Returns a unique lock id associated with the request. * Only controller can call this function, and only the custodian * can confirm the request. * * @param _from address of suspected account. * * @param _to address of reciever. * * @param _value amount which will be transferred. * * @return lockId A unique identifier for this request. */ function requestForceTransfer(address _from, address _to, uint256 _value) public onlyController returns (bytes32 lockId) { lockId = generateLockId(); require (_value != 0, "no zero value transfers"); pendingForceTransferRequestMap[lockId] = forceTransferRequest(_value, _from, _to); emit ForceTransferRequested(lockId, _from, _to, _value); return lockId; } /** @notice Confirms a pending force transfer request. * * @dev When called by the custodian with a lock id associated with a * pending transfer request, the amount requested is transferred from the suspected account. * * @param _lockId The identifier of a pending transfer request. */ function confirmForceTransfer(bytes32 _lockId) public onlyCustodian { address from = pendingForceTransferRequestMap[_lockId].from; address to = pendingForceTransferRequestMap[_lockId].to; uint256 value = pendingForceTransferRequestMap[_lockId].value; delete pendingForceTransferRequestMap[_lockId]; erc20Impl.forceTransfer(from, to, value); emit ForceTransferCompleted(_lockId, from, to, value); } /** @notice Requests an increase to the supply ceiling. * * @dev Returns a unique lock id associated with the request. * Anyone can call this function, but confirming the request is authorized * by the custodian. * * @param _raiseBy The amount by which to raise the ceiling. * * @return lockId A unique identifier for this request. */ function requestCeilingRaise(uint256 _raiseBy) public returns (bytes32 lockId) { require(_raiseBy != 0, "no zero ceiling raise"); lockId = generateLockId(); pendingRaiseMap[lockId] = PendingCeilingRaise({ raiseBy: _raiseBy }); emit CeilingRaiseLocked(lockId, _raiseBy); } /** @notice Confirms a pending increase in the token supply. * * @dev When called by the custodian with a lock id associated with a * pending ceiling increase, the amount requested is added to the * current supply ceiling. * NOTE: this function will not execute any raise that would overflow the * supply ceiling, but it will not revert either. * * @param _lockId The identifier of a pending ceiling raise request. */ function confirmCeilingRaise(bytes32 _lockId) public onlyCustodian { PendingCeilingRaise storage pendingRaise = pendingRaiseMap[_lockId]; // copy locals of references to struct members uint256 raiseBy = pendingRaise.raiseBy; // accounts for a gibberish _lockId require(raiseBy != 0, "no gibberish _lockId"); delete pendingRaiseMap[_lockId]; uint256 newCeiling = totalSupplyCeiling + raiseBy; // overflow check if (newCeiling >= totalSupplyCeiling) { totalSupplyCeiling = newCeiling; emit CeilingRaiseConfirmed(_lockId, raiseBy, newCeiling); } } /** @notice Lowers the supply ceiling, further constraining the bound of * what can be printed by the controller. * * @dev The controller is the sole authorized caller of this function, * so it is the only account that can elect to lower its limit to increase * the token supply. * * @param _lowerBy The amount by which to lower the supply ceiling. */ function lowerCeiling(uint256 _lowerBy) public onlyController { uint256 newCeiling = totalSupplyCeiling - _lowerBy; // overflow check require(newCeiling <= totalSupplyCeiling, "totalSupplyCeiling overflow"); totalSupplyCeiling = newCeiling; emit CeilingLowered(_lowerBy, newCeiling); } /** @notice Pass-through control of print confirmation, allowing this * contract's custodian to act as the custodian of the associated * active token implementation. * * @dev This contract is the direct custodian of the active token * implementation, but this function allows this contract's custodian * to act as though it were the direct custodian of the active * token implementation. Therefore the custodian retains control of * unlimited printing. * * @param _lockId The identifier of a pending print request in * the associated active token implementation. */ function confirmPrintProxy(bytes32 _lockId) public onlyCustodian { erc20Impl.confirmPrint(_lockId); } /** @notice Pass-through control of custodian change confirmation, * allowing this contract's custodian to act as the custodian of * the associated active token implementation. * * @dev This contract is the direct custodian of the active token * implementation, but this function allows this contract's custodian * to act as though it were the direct custodian of the active * token implementation. Therefore the custodian retains control of * custodian changes. * * @param _lockId The identifier of a pending custodian change request * in the associated active token implementation. */ function confirmCustodianChangeProxy(bytes32 _lockId) public onlyCustodian { erc20Impl.confirmCustodianChange(_lockId); } /** @notice Blocks all transactions with a wallet. * * @dev Only signers from custodian are authorized to call this function * * @param wallet account which will be blocked. */ function blockWallet(address wallet) public onlySigner { erc20Impl.blockWallet(wallet); emit Blocked(wallet); } /** @notice Unblocks all transactions with a wallet. * * @dev Only signers from custodian are authorized to call this function * * @param wallet account which will be unblocked. */ function unblockWallet(address wallet) public onlySigner { erc20Impl.unblockWallet(wallet); emit Unblocked(wallet); } // EVENTS /// @dev Emitted by successful `requestCeilingRaise` calls. event CeilingRaiseLocked(bytes32 _lockId, uint256 _raiseBy); /// @dev Emitted by successful `confirmCeilingRaise` calls. event CeilingRaiseConfirmed(bytes32 _lockId, uint256 _raiseBy, uint256 _newCeiling); /// @dev Emitted by successful `lowerCeiling` calls. event CeilingLowered(uint256 _lowerBy, uint256 _newCeiling); /// @dev Emitted by successful `blockWallet` calls. event Blocked(address _wallet); /// @dev Emitted by successful `unblockWallet` calls. event Unblocked(address _wallet); /// @dev Emitted by successful `requestForceTransfer` calls. event ForceTransferRequested(bytes32 _lockId, address _from, address _to, uint256 _value); /// @dev Emitted by successful `confirmForceTransfer` calls. event ForceTransferCompleted(bytes32 _lockId, address _from, address _to, uint256 _value); /// @dev Emitted by successful `requestWipe` calls. event WipeRequested(bytes32 _lockId); /// @dev Emitted by successful `confirmWipe` calls. event WipeCompleted(bytes32 _lockId); }
File 3 of 3: ERC20Store
pragma solidity ^0.5.10; /** @title A contract for generating unique identifiers * * @notice A contract that provides an identifier generation scheme, * guaranteeing uniqueness across all contracts that inherit from it, * as well as the unpredictability of future identifiers. * * @dev This contract is intended to be inherited by any contract that * implements the callback software pattern for cooperative custodianship. * */ contract LockRequestable { // MEMBERS /// @notice the count of all invocations of `generateLockId`. uint256 public lockRequestCount; // CONSTRUCTOR constructor() public { lockRequestCount = 0; } // FUNCTIONS /** @notice Returns a fresh unique identifier. * * @dev the generation scheme uses three components. * First, the blockhash of the previous block. * Second, the deployed address. * Third, the next value of the counter. * This ensures that identifiers are unique across all contracts * following this scheme, and that future identifiers are * unpredictable. * * @return a 32-byte unique identifier. */ function generateLockId() internal returns (bytes32 lockId) { return keccak256(abi.encodePacked(blockhash(block.number - 1), address(this), ++lockRequestCount)); } } contract ERC20Interface { // METHODS // NOTE: // public getter functions are not currently recognised as an // implementation of the matching abstract function by the compiler. // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#name // function name() public view returns (string); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#symbol // function symbol() public view returns (string); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#totalsupply // function decimals() public view returns (uint8); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#totalsupply function totalSupply() public view returns (uint256); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#balanceof function balanceOf(address _owner) public view returns (uint256 balance); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer function transfer(address _to, uint256 _value) public returns (bool success); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transferfrom function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approve function approve(address _spender, uint256 _value) public returns (bool success); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#allowance function allowance(address _owner, address _spender) public view returns (uint256 remaining); // EVENTS // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer-1 event Transfer(address indexed _from, address indexed _to, uint256 _value); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approval event Approval(address indexed _owner, address indexed _spender, uint256 _value); } /** @title A dual control contract. * * @notice A general-purpose contract that implements dual control over * co-operating contracts through a callback mechanism. * * @dev This contract implements dual control through a 2-of-N * threshold multi-signature scheme. The contract recognizes a set of N signers, * and will unlock requests with signatures from any distinct pair of them. * This contract signals the unlocking through a co-operative callback * scheme. * This contract also provides time lock and revocation features. * Requests made by a 'primary' account have a default time lock applied. * All other requests must pay a 1 ETH stake and have an extended time lock * applied. * A request that is completed will prevent all previous pending requests * that share the same callback from being completed: this is the * revocation feature. * */ contract Custodian { // TYPES /** @dev The `Request` struct stores a pending unlocking. * `callbackAddress` and `callbackSelector` are the data required to * make a callback. The custodian completes the process by * calling `callbackAddress.call(callbackSelector, lockId)`, which * signals to the contract co-operating with the Custodian that * the 2-of-N signatures have been provided and verified. */ struct Request { bytes32 lockId; bytes4 callbackSelector; // bytes4 and address can be packed into 1 word address callbackAddress; uint256 idx; uint256 timestamp; bool extended; } // EVENTS /// @dev Emitted by successful `requestUnlock` calls. event Requested( bytes32 _lockId, address _callbackAddress, bytes4 _callbackSelector, uint256 _nonce, address _whitelistedAddress, bytes32 _requestMsgHash, uint256 _timeLockExpiry ); /// @dev Emitted by `completeUnlock` calls on requests in the time-locked state. event TimeLocked( uint256 _timeLockExpiry, bytes32 _requestMsgHash ); /// @dev Emitted by successful `completeUnlock` calls. event Completed( bytes32 _lockId, bytes32 _requestMsgHash, address _signer1, address _signer2 ); /// @dev Emitted by `completeUnlock` calls where the callback failed. event Failed( bytes32 _lockId, bytes32 _requestMsgHash, address _signer1, address _signer2 ); /// @dev Emitted by successful `extendRequestTimeLock` calls. event TimeLockExtended( uint256 _timeLockExpiry, bytes32 _requestMsgHash ); // MEMBERS /** @dev The count of all requests. * This value is used as a nonce, incorporated into the request hash. */ uint256 public requestCount; /// @dev The set of signers: signatures from two signers unlock a pending request. mapping (address => bool) public signerSet; /// @dev The map of request hashes to pending requests. mapping (bytes32 => Request) public requestMap; /// @dev The map of callback addresses to callback selectors to request indexes. mapping (address => mapping (bytes4 => uint256)) public lastCompletedIdxs; /** @dev The default period (in seconds) to time-lock requests. * All requests will be subject to this default time lock, and the duration * is fixed at contract creation. */ uint256 public defaultTimeLock; /** @dev The extended period (in seconds) to time-lock requests. * Requests not from the primary account are subject to this time lock. * The primary account may also elect to extend the time lock on requests * that originally received the default. */ uint256 public extendedTimeLock; /// @dev The primary account is the privileged account for making requests. address public primary; // CONSTRUCTOR constructor( address[] memory _signers, uint256 _defaultTimeLock, uint256 _extendedTimeLock, address _primary ) public { // check for at least two `_signers` require(_signers.length >= 2, "at least two `_signers`"); // validate time lock params require(_defaultTimeLock <= _extendedTimeLock, "valid timelock params"); defaultTimeLock = _defaultTimeLock; extendedTimeLock = _extendedTimeLock; primary = _primary; // explicitly initialize `requestCount` to zero requestCount = 0; // turn the array into a set for (uint i = 0; i < _signers.length; i++) { // no zero addresses or duplicates require(_signers[i] != address(0) && !signerSet[_signers[i]], "no zero addresses or duplicates"); signerSet[_signers[i]] = true; } } // MODIFIERS modifier onlyPrimary { require(msg.sender == primary, "only primary"); _; } modifier onlySigner { require(signerSet[msg.sender], "only signer"); _; } // METHODS /** @notice Requests an unlocking with a lock identifier and a callback. * * @dev If called by an account other than the primary a 1 ETH stake * must be paid. When the request is unlocked stake will be transferred to the message sender. * This is an anti-spam measure. As well as the callback * and the lock identifier parameters a 'whitelisted address' is required * for compatibility with existing signature schemes. * * @param _lockId The identifier of a pending request in a co-operating contract. * @param _callbackAddress The address of a co-operating contract. * @param _callbackSelector The function selector of a function within * the co-operating contract at address `_callbackAddress`. * @param _whitelistedAddress An address whitelisted in existing * offline control protocols. * * @return requestMsgHash The hash of a request message to be signed. */ function requestUnlock( bytes32 _lockId, address _callbackAddress, bytes4 _callbackSelector, address _whitelistedAddress ) public payable returns (bytes32 requestMsgHash) { require(msg.sender == primary || msg.value >= 1 ether, "sender is primary or stake is paid"); // disallow using a zero value for the callback address require(_callbackAddress != address(0), "no zero value for callback address"); uint256 requestIdx = ++requestCount; // compute a nonce value // - the blockhash prevents prediction of future nonces // - the address of this contract prevents conflicts with co-operating contracts using this scheme // - the counter prevents conflicts arising from multiple txs within the same block uint256 nonce = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), address(this), requestIdx))); requestMsgHash = keccak256( abi.encodePacked( nonce, _whitelistedAddress, uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) ) ); requestMap[requestMsgHash] = Request({ lockId: _lockId, callbackSelector: _callbackSelector, callbackAddress: _callbackAddress, idx: requestIdx, timestamp: block.timestamp, extended: false }); // compute the expiry time uint256 timeLockExpiry = block.timestamp; if (msg.sender == primary) { timeLockExpiry += defaultTimeLock; } else { timeLockExpiry += extendedTimeLock; // any sender that is not the creator will get the extended time lock requestMap[requestMsgHash].extended = true; } emit Requested(_lockId, _callbackAddress, _callbackSelector, nonce, _whitelistedAddress, requestMsgHash, timeLockExpiry); } /** @notice Completes a pending unlocking with two signatures. * * @dev Given a request message hash as two signatures of it from * two distinct signers in the signer set, this function completes the * unlocking of the pending request by executing the callback. * * @param _requestMsgHash The request message hash of a pending request. * @param _recoveryByte1 The public key recovery byte (27 or 28) * @param _ecdsaR1 The R component of an ECDSA signature (R, S) pair * @param _ecdsaS1 The S component of an ECDSA signature (R, S) pair * @param _recoveryByte2 The public key recovery byte (27 or 28) * @param _ecdsaR2 The R component of an ECDSA signature (R, S) pair * @param _ecdsaS2 The S component of an ECDSA signature (R, S) pair * * @return success True if the callback successfully executed. */ function completeUnlock( bytes32 _requestMsgHash, uint8 _recoveryByte1, bytes32 _ecdsaR1, bytes32 _ecdsaS1, uint8 _recoveryByte2, bytes32 _ecdsaR2, bytes32 _ecdsaS2 ) public onlySigner returns (bool success) { Request storage request = requestMap[_requestMsgHash]; // copy storage to locals before `delete` bytes32 lockId = request.lockId; address callbackAddress = request.callbackAddress; bytes4 callbackSelector = request.callbackSelector; // failing case of the lookup if the callback address is zero require(callbackAddress != address(0), "no zero value for callback address"); // reject confirms of earlier withdrawals buried under later confirmed withdrawals require(request.idx > lastCompletedIdxs[callbackAddress][callbackSelector], "reject confirms of earlier withdrawals buried under later confirmed withdrawals"); address signer1 = ecrecover( keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _requestMsgHash)), _recoveryByte1, _ecdsaR1, _ecdsaS1 ); require(signerSet[signer1], "signer is set"); address signer2 = ecrecover( keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _requestMsgHash)), _recoveryByte2, _ecdsaR2, _ecdsaS2 ); require(signerSet[signer2], "signer is set"); require(signer1 != signer2, "signers are different"); if (request.extended && ((block.timestamp - request.timestamp) < extendedTimeLock)) { emit TimeLocked(request.timestamp + extendedTimeLock, _requestMsgHash); return false; } else if ((block.timestamp - request.timestamp) < defaultTimeLock) { emit TimeLocked(request.timestamp + defaultTimeLock, _requestMsgHash); return false; } else { if (address(this).balance > 0) { // reward sender with anti-spam payments msg.sender.transfer(address(this).balance); } // raise the waterline for the last completed unlocking lastCompletedIdxs[callbackAddress][callbackSelector] = request.idx; // and delete the request delete requestMap[_requestMsgHash]; // invoke callback (success,) = callbackAddress.call(abi.encodeWithSelector(callbackSelector, lockId)); if (success) { emit Completed(lockId, _requestMsgHash, signer1, signer2); } else { emit Failed(lockId, _requestMsgHash, signer1, signer2); } } } /** @notice Reclaim the storage of a pending request that is uncompletable. * * @dev If a pending request shares the callback (address and selector) of * a later request has been completed, then the request can no longer * be completed. This function will reclaim the contract storage of the * pending request. * * @param _requestMsgHash The request message hash of a pending request. */ function deleteUncompletableRequest(bytes32 _requestMsgHash) public { Request storage request = requestMap[_requestMsgHash]; uint256 idx = request.idx; require(0 < idx && idx < lastCompletedIdxs[request.callbackAddress][request.callbackSelector], "there must be a completed latter request with same callback"); delete requestMap[_requestMsgHash]; } /** @notice Extend the time lock of a pending request. * * @dev Requests made by the primary account receive the default time lock. * This function allows the primary account to apply the extended time lock * to one its own requests. * * @param _requestMsgHash The request message hash of a pending request. */ function extendRequestTimeLock(bytes32 _requestMsgHash) public onlyPrimary { Request storage request = requestMap[_requestMsgHash]; // reject ‘null’ results from the map lookup // this can only be the case if an unknown `_requestMsgHash` is received require(request.callbackAddress != address(0), "reject ‘null’ results from the map lookup"); // `extendRequestTimeLock` must be idempotent require(request.extended != true, "`extendRequestTimeLock` must be idempotent"); // set the `extended` flag; note that this is never unset request.extended = true; emit TimeLockExtended(request.timestamp + extendedTimeLock, _requestMsgHash); } } /** @title A contract to inherit upgradeable custodianship. * * @notice A contract that provides re-usable code for upgradeable * custodianship. That custodian may be an account or another contract. * * @dev This contract is intended to be inherited by any contract * requiring a custodian to control some aspect of its functionality. * This contract provides the mechanism for that custodianship to be * passed from one custodian to the next. * */ contract CustodianUpgradeable is LockRequestable { // TYPES /// @dev The struct type for pending custodian changes. struct CustodianChangeRequest { address proposedNew; } // MEMBERS /// @dev The address of the account or contract that acts as the custodian. address public custodian; /// @dev The map of lock ids to pending custodian changes. mapping (bytes32 => CustodianChangeRequest) public custodianChangeReqs; // CONSTRUCTOR constructor( address _custodian ) LockRequestable() public { custodian = _custodian; } // MODIFIERS modifier onlyCustodian { require(msg.sender == custodian, "only custodian"); _; } // PUBLIC FUNCTIONS // (UPGRADE) /** @notice Requests a change of the custodian associated with this contract. * * @dev Returns a unique lock id associated with the request. * Anyone can call this function, but confirming the request is authorized * by the custodian. * * @param _proposedCustodian The address of the new custodian. * @return lockId A unique identifier for this request. */ function requestCustodianChange(address _proposedCustodian) public returns (bytes32 lockId) { require(_proposedCustodian != address(0), "no null value for `_proposedCustodian`"); lockId = generateLockId(); custodianChangeReqs[lockId] = CustodianChangeRequest({ proposedNew: _proposedCustodian }); emit CustodianChangeRequested(lockId, msg.sender, _proposedCustodian); } /** @notice Confirms a pending change of the custodian associated with this contract. * * @dev When called by the current custodian with a lock id associated with a * pending custodian change, the `address custodian` member will be updated with the * requested address. * * @param _lockId The identifier of a pending change request. */ function confirmCustodianChange(bytes32 _lockId) public onlyCustodian { custodian = getCustodianChangeReq(_lockId); delete custodianChangeReqs[_lockId]; emit CustodianChangeConfirmed(_lockId, custodian); } // PRIVATE FUNCTIONS function getCustodianChangeReq(bytes32 _lockId) private view returns (address _proposedNew) { CustodianChangeRequest storage changeRequest = custodianChangeReqs[_lockId]; // reject ‘null’ results from the map lookup // this can only be the case if an unknown `_lockId` is received require(changeRequest.proposedNew != address(0), "reject ‘null’ results from the map lookup"); return changeRequest.proposedNew; } //EVENTS /// @dev Emitted by successful `requestCustodianChange` calls. event CustodianChangeRequested( bytes32 _lockId, address _msgSender, address _proposedCustodian ); /// @dev Emitted by successful `confirmCustodianChange` calls. event CustodianChangeConfirmed(bytes32 _lockId, address _newCustodian); } /** @title A contract to inherit upgradeable token implementations. * * @notice A contract that provides re-usable code for upgradeable * token implementations. It itself inherits from `CustodianUpgradable` * as the upgrade process is controlled by the custodian. * * @dev This contract is intended to be inherited by any contract * requiring a reference to the active token implementation, either * to delegate calls to it, or authorize calls from it. This contract * provides the mechanism for that implementation to be replaced, * which constitutes an implementation upgrade. * */ contract ERC20ImplUpgradeable is CustodianUpgradeable { // TYPES /// @dev The struct type for pending implementation changes. struct ImplChangeRequest { address proposedNew; } // MEMBERS // @dev The reference to the active token implementation. ERC20Impl public erc20Impl; /// @dev The map of lock ids to pending implementation changes. mapping (bytes32 => ImplChangeRequest) public implChangeReqs; // CONSTRUCTOR constructor(address _custodian) CustodianUpgradeable(_custodian) public { erc20Impl = ERC20Impl(0x0); } // MODIFIERS modifier onlyImpl { require(msg.sender == address(erc20Impl), "only ERC20Impl"); _; } // PUBLIC FUNCTIONS // (UPGRADE) /** @notice Requests a change of the active implementation associated * with this contract. * * @dev Returns a unique lock id associated with the request. * Anyone can call this function, but confirming the request is authorized * by the custodian. * * @param _proposedImpl The address of the new active implementation. * @return lockId A unique identifier for this request. */ function requestImplChange(address _proposedImpl) public returns (bytes32 lockId) { require(_proposedImpl != address(0), "no null value for `_proposedImpl`"); lockId = generateLockId(); implChangeReqs[lockId] = ImplChangeRequest({ proposedNew: _proposedImpl }); emit ImplChangeRequested(lockId, msg.sender, _proposedImpl); } /** @notice Confirms a pending change of the active implementation * associated with this contract. * * @dev When called by the custodian with a lock id associated with a * pending change, the `ERC20Impl erc20Impl` member will be updated * with the requested address. * * @param _lockId The identifier of a pending change request. */ function confirmImplChange(bytes32 _lockId) public onlyCustodian { erc20Impl = getImplChangeReq(_lockId); delete implChangeReqs[_lockId]; emit ImplChangeConfirmed(_lockId, address(erc20Impl)); } // PRIVATE FUNCTIONS function getImplChangeReq(bytes32 _lockId) private view returns (ERC20Impl _proposedNew) { ImplChangeRequest storage changeRequest = implChangeReqs[_lockId]; // reject ‘null’ results from the map lookup // this can only be the case if an unknown `_lockId` is received require(changeRequest.proposedNew != address(0), "reject ‘null’ results from the map lookup"); return ERC20Impl(changeRequest.proposedNew); } //EVENTS /// @dev Emitted by successful `requestImplChange` calls. event ImplChangeRequested( bytes32 _lockId, address _msgSender, address _proposedImpl ); /// @dev Emitted by successful `confirmImplChange` calls. event ImplChangeConfirmed(bytes32 _lockId, address _newImpl); } /** @title Public interface to ERC20 compliant token. * * @notice This contract is a permanent entry point to an ERC20 compliant * system of contracts. * * @dev This contract contains no business logic and instead * delegates to an instance of ERC20Impl. This contract also has no storage * that constitutes the operational state of the token. This contract is * upgradeable in the sense that the `custodian` can update the * `erc20Impl` address, thus redirecting the delegation of business logic. * The `custodian` is also authorized to pass custodianship. * */ contract ERC20Proxy is ERC20Interface, ERC20ImplUpgradeable { // MEMBERS /// @notice Returns the name of the token. string public name; /// @notice Returns the symbol of the token. string public symbol; /// @notice Returns the number of decimals the token uses. uint8 public decimals; // CONSTRUCTOR constructor( string memory _name, string memory _symbol, uint8 _decimals, address _custodian ) ERC20ImplUpgradeable(_custodian) public { name = _name; symbol = _symbol; decimals = _decimals; } // PUBLIC FUNCTIONS // (ERC20Interface) /** @notice Returns the total token supply. * * @return the total token supply. */ function totalSupply() public view returns (uint256) { return erc20Impl.totalSupply(); } /** @notice Returns the account balance of another account with an address * `_owner`. * * @return balance the balance of account with address `_owner`. */ function balanceOf(address _owner) public view returns (uint256 balance) { return erc20Impl.balanceOf(_owner); } /** @dev Internal use only. */ function emitTransfer(address _from, address _to, uint256 _value) public onlyImpl { emit Transfer(_from, _to, _value); } /** @notice Transfers `_value` amount of tokens to address `_to`. * * @dev Will fire the `Transfer` event. Will revert if the `_from` * account balance does not have enough tokens to spend. * * @return success true if transfer completes. */ function transfer(address _to, uint256 _value) public returns (bool success) { return erc20Impl.transferWithSender(msg.sender, _to, _value); } /** @notice Transfers `_value` amount of tokens from address `_from` * to address `_to`. * * @dev Will fire the `Transfer` event. Will revert unless the `_from` * account has deliberately authorized the sender of the message * via some mechanism. * * @return success true if transfer completes. */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { return erc20Impl.transferFromWithSender(msg.sender, _from, _to, _value); } /** @dev Internal use only. */ function emitApproval(address _owner, address _spender, uint256 _value) public onlyImpl { emit Approval(_owner, _spender, _value); } /** @notice Allows `_spender` to withdraw from your account multiple times, * up to the `_value` amount. If this function is called again it * overwrites the current allowance with _value. * * @dev Will fire the `Approval` event. * * @return success true if approval completes. */ function approve(address _spender, uint256 _value) public returns (bool success) { return erc20Impl.approveWithSender(msg.sender, _spender, _value); } /** @notice Increases the amount `_spender` is allowed to withdraw from * your account. * This function is implemented to avoid the race condition in standard * ERC20 contracts surrounding the `approve` method. * * @dev Will fire the `Approval` event. This function should be used instead of * `approve`. * * @return success true if approval completes. */ function increaseApproval(address _spender, uint256 _addedValue) public returns (bool success) { return erc20Impl.increaseApprovalWithSender(msg.sender, _spender, _addedValue); } /** @notice Decreases the amount `_spender` is allowed to withdraw from * your account. This function is implemented to avoid the race * condition in standard ERC20 contracts surrounding the `approve` method. * * @dev Will fire the `Approval` event. This function should be used * instead of `approve`. * * @return success true if approval completes. */ function decreaseApproval(address _spender, uint256 _subtractedValue) public returns (bool success) { return erc20Impl.decreaseApprovalWithSender(msg.sender, _spender, _subtractedValue); } /** @notice Returns how much `_spender` is currently allowed to spend from * `_owner`'s balance. * * @return remaining the remaining allowance. */ function allowance(address _owner, address _spender) public view returns (uint256 remaining) { return erc20Impl.allowance(_owner, _spender); } } /** @title ERC20 compliant token balance store. * * @notice This contract serves as the store of balances, allowances, and * supply for the ERC20 compliant token. No business logic exists here. * * @dev This contract contains no business logic and instead * is the final destination for any change in balances, allowances, or token * supply. This contract is upgradeable in the sense that its custodian can * update the `erc20Impl` address, thus redirecting the source of logic that * determines how the balances will be updated. * */ contract ERC20Store is ERC20ImplUpgradeable { // MEMBERS /// @dev The total token supply. uint256 public totalSupply; /// @dev The mapping of balances. mapping (address => uint256) public balances; /// @dev The mapping of allowances. mapping (address => mapping (address => uint256)) public allowed; // CONSTRUCTOR constructor(address _custodian) ERC20ImplUpgradeable(_custodian) public { totalSupply = 0; } // PUBLIC FUNCTIONS // (ERC20 Ledger) /** @notice The function to set the total supply of tokens. * * @dev Intended for use by token implementation functions * that update the total supply. The only authorized caller * is the active implementation. * * @param _newTotalSupply the value to set as the new total supply */ function setTotalSupply( uint256 _newTotalSupply ) public onlyImpl { totalSupply = _newTotalSupply; } /** @notice Sets how much `_owner` allows `_spender` to transfer on behalf * of `_owner`. * * @dev Intended for use by token implementation functions * that update spending allowances. The only authorized caller * is the active implementation. * * @param _owner The account that will allow an on-behalf-of spend. * @param _spender The account that will spend on behalf of the owner. * @param _value The limit of what can be spent. */ function setAllowance( address _owner, address _spender, uint256 _value ) public onlyImpl { allowed[_owner][_spender] = _value; } /** @notice Sets the balance of `_owner` to `_newBalance`. * * @dev Intended for use by token implementation functions * that update balances. The only authorized caller * is the active implementation. * * @param _owner The account that will hold a new balance. * @param _newBalance The balance to set. */ function setBalance( address _owner, uint256 _newBalance ) public onlyImpl { balances[_owner] = _newBalance; } /** @notice Adds `_balanceIncrease` to `_owner`'s balance. * * @dev Intended for use by token implementation functions * that update balances. The only authorized caller * is the active implementation. * WARNING: the caller is responsible for preventing overflow. * * @param _owner The account that will hold a new balance. * @param _balanceIncrease The balance to add. */ function addBalance( address _owner, uint256 _balanceIncrease ) public onlyImpl { balances[_owner] = balances[_owner] + _balanceIncrease; } } /** @title ERC20 compliant token intermediary contract holding core logic. * * @notice This contract serves as an intermediary between the exposed ERC20 * interface in ERC20Proxy and the store of balances in ERC20Store. This * contract contains core logic that the proxy can delegate to * and that the store is called by. * * @dev This contract contains the core logic to implement the * ERC20 specification as well as several extensions. * 1. Changes to the token supply. * 2. Batched transfers. * 3. Relative changes to spending approvals. * 4. Delegated transfer control ('sweeping'). * */ contract ERC20Impl is CustodianUpgradeable { // TYPES /// @dev The struct type for pending increases to the token supply (print). struct PendingPrint { address receiver; uint256 value; } // MEMBERS /// @dev The reference to the proxy. ERC20Proxy public erc20Proxy; /// @dev The reference to the store. ERC20Store public erc20Store; /// @dev The sole authorized caller of delegated transfer control ('sweeping'). address public sweeper; /** @dev The static message to be signed by an external account that * signifies their permission to forward their balance to any arbitrary * address. This is used to consolidate the control of all accounts * backed by a shared keychain into the control of a single key. * Initialized as the concatenation of the address of this contract * and the word "sweep". This concatenation is done to prevent a replay * attack in a subsequent contract, where the sweeping message could * potentially be replayed to re-enable sweeping ability. */ bytes32 public sweepMsg; /** @dev The mapping that stores whether the address in question has * enabled sweeping its contents to another account or not. * If an address maps to "true", it has already enabled sweeping, * and thus does not need to re-sign the `sweepMsg` to enact the sweep. */ mapping (address => bool) public sweptSet; /// @dev The map of lock ids to pending token increases. mapping (bytes32 => PendingPrint) public pendingPrintMap; /// @dev The map of blocked addresses. mapping (address => bool) public blocked; // CONSTRUCTOR constructor( address _erc20Proxy, address _erc20Store, address _custodian, address _sweeper ) CustodianUpgradeable(_custodian) public { require(_sweeper != address(0), "no null value for `_sweeper`"); erc20Proxy = ERC20Proxy(_erc20Proxy); erc20Store = ERC20Store(_erc20Store); sweeper = _sweeper; sweepMsg = keccak256(abi.encodePacked(address(this), "sweep")); } // MODIFIERS modifier onlyProxy { require(msg.sender == address(erc20Proxy), "only ERC20Proxy"); _; } modifier onlySweeper { require(msg.sender == sweeper, "only sweeper"); _; } /** @notice Core logic of the ERC20 `approve` function. * * @dev This function can only be called by the referenced proxy, * which has an `approve` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: approvals for the zero address (unspendable) are disallowed. * * @param _sender The address initiating the approval in a proxy. */ function approveWithSender( address _sender, address _spender, uint256 _value ) public onlyProxy returns (bool success) { require(_spender != address(0), "no null value for `_spender`"); require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_spender] != true, "_spender must not be blocked"); erc20Store.setAllowance(_sender, _spender, _value); erc20Proxy.emitApproval(_sender, _spender, _value); return true; } /** @notice Core logic of the `increaseApproval` function. * * @dev This function can only be called by the referenced proxy, * which has an `increaseApproval` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: approvals for the zero address (unspendable) are disallowed. * * @param _sender The address initiating the approval. */ function increaseApprovalWithSender( address _sender, address _spender, uint256 _addedValue ) public onlyProxy returns (bool success) { require(_spender != address(0),"no null value for_spender"); require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_spender] != true, "_spender must not be blocked"); uint256 currentAllowance = erc20Store.allowed(_sender, _spender); uint256 newAllowance = currentAllowance + _addedValue; require(newAllowance >= currentAllowance, "new allowance must not be smaller than previous"); erc20Store.setAllowance(_sender, _spender, newAllowance); erc20Proxy.emitApproval(_sender, _spender, newAllowance); return true; } /** @notice Core logic of the `decreaseApproval` function. * * @dev This function can only be called by the referenced proxy, * which has a `decreaseApproval` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: approvals for the zero address (unspendable) are disallowed. * * @param _sender The address initiating the approval. */ function decreaseApprovalWithSender( address _sender, address _spender, uint256 _subtractedValue ) public onlyProxy returns (bool success) { require(_spender != address(0), "no unspendable approvals"); // disallow unspendable approvals require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_spender] != true, "_spender must not be blocked"); uint256 currentAllowance = erc20Store.allowed(_sender, _spender); uint256 newAllowance = currentAllowance - _subtractedValue; require(newAllowance <= currentAllowance, "new allowance must not be smaller than previous"); erc20Store.setAllowance(_sender, _spender, newAllowance); erc20Proxy.emitApproval(_sender, _spender, newAllowance); return true; } /** @notice Requests an increase in the token supply, with the newly created * tokens to be added to the balance of the specified account. * * @dev Returns a unique lock id associated with the request. * Anyone can call this function, but confirming the request is authorized * by the custodian. * NOTE: printing to the zero address is disallowed. * * @param _receiver The receiving address of the print, if confirmed. * @param _value The number of tokens to add to the total supply and the * balance of the receiving address, if confirmed. * * @return lockId A unique identifier for this request. */ function requestPrint(address _receiver, uint256 _value) public returns (bytes32 lockId) { require(_receiver != address(0), "no null value for `_receiver`"); require(blocked[msg.sender] != true, "account blocked"); require(blocked[_receiver] != true, "_receiver must not be blocked"); lockId = generateLockId(); pendingPrintMap[lockId] = PendingPrint({ receiver: _receiver, value: _value }); emit PrintingLocked(lockId, _receiver, _value); } /** @notice Confirms a pending increase in the token supply. * * @dev When called by the custodian with a lock id associated with a * pending increase, the amount requested to be printed in the print request * is printed to the receiving address specified in that same request. * NOTE: this function will not execute any print that would overflow the * total supply, but it will not revert either. * * @param _lockId The identifier of a pending print request. */ function confirmPrint(bytes32 _lockId) public onlyCustodian { PendingPrint storage print = pendingPrintMap[_lockId]; // reject ‘null’ results from the map lookup // this can only be the case if an unknown `_lockId` is received address receiver = print.receiver; require (receiver != address(0), "unknown `_lockId`"); uint256 value = print.value; delete pendingPrintMap[_lockId]; uint256 supply = erc20Store.totalSupply(); uint256 newSupply = supply + value; if (newSupply >= supply) { erc20Store.setTotalSupply(newSupply); erc20Store.addBalance(receiver, value); emit PrintingConfirmed(_lockId, receiver, value); erc20Proxy.emitTransfer(address(0), receiver, value); } } /** @notice Burns the specified value from the sender's balance. * * @dev Sender's balanced is subtracted by the amount they wish to burn. * * @param _value The amount to burn. * * @return success true if the burn succeeded. */ function burn(uint256 _value) public returns (bool success) { require(blocked[msg.sender] != true, "account blocked"); uint256 balanceOfSender = erc20Store.balances(msg.sender); require(_value <= balanceOfSender, "disallow burning more, than amount of the balance"); erc20Store.setBalance(msg.sender, balanceOfSender - _value); erc20Store.setTotalSupply(erc20Store.totalSupply() - _value); erc20Proxy.emitTransfer(msg.sender, address(0), _value); return true; } /** @notice Burns the specified value from the balance in question. * * @dev Suspected balance is subtracted by the amount which will be burnt. * * @dev If the suspected balance has less than the amount requested, it will be set to 0. * * @param _from The address of suspected balance. * * @param _value The amount to burn. * * @return success true if the burn succeeded. */ function burn(address _from, uint256 _value) public onlyCustodian returns (bool success) { uint256 balance = erc20Store.balances(_from); if(_value <= balance){ erc20Store.setBalance(_from, balance - _value); erc20Store.setTotalSupply(erc20Store.totalSupply() - _value); erc20Proxy.emitTransfer(_from, address(0), _value); emit Wiped(_from, _value, _value, balance - _value); } else { erc20Store.setBalance(_from,0); erc20Store.setTotalSupply(erc20Store.totalSupply() - balance); erc20Proxy.emitTransfer(_from, address(0), balance); emit Wiped(_from, _value, balance, 0); } return true; } /** @notice A function for a sender to issue multiple transfers to multiple * different addresses at once. This function is implemented for gas * considerations when someone wishes to transfer, as one transaction is * cheaper than issuing several distinct individual `transfer` transactions. * * @dev By specifying a set of destination addresses and values, the * sender can issue one transaction to transfer multiple amounts to * distinct addresses, rather than issuing each as a separate * transaction. The `_tos` and `_values` arrays must be equal length, and * an index in one array corresponds to the same index in the other array * (e.g. `_tos[0]` will receive `_values[0]`, `_tos[1]` will receive * `_values[1]`, and so on.) * NOTE: transfers to the zero address are disallowed. * * @param _tos The destination addresses to receive the transfers. * @param _values The values for each destination address. * @return success If transfers succeeded. */ function batchTransfer(address[] memory _tos, uint256[] memory _values) public returns (bool success) { require(_tos.length == _values.length, "_tos and _values must be the same length"); require(blocked[msg.sender] != true, "account blocked"); uint256 numTransfers = _tos.length; uint256 senderBalance = erc20Store.balances(msg.sender); for (uint256 i = 0; i < numTransfers; i++) { address to = _tos[i]; require(to != address(0), "no null values for _tos"); require(blocked[to] != true, "_tos must not be blocked"); uint256 v = _values[i]; require(senderBalance >= v, "insufficient funds"); if (msg.sender != to) { senderBalance -= v; erc20Store.addBalance(to, v); } erc20Proxy.emitTransfer(msg.sender, to, v); } erc20Store.setBalance(msg.sender, senderBalance); return true; } /** @notice Enables the delegation of transfer control for many * accounts to the sweeper account, transferring any balances * as well to the given destination. * * @dev An account delegates transfer control by signing the * value of `sweepMsg`. The sweeper account is the only authorized * caller of this function, so it must relay signatures on behalf * of accounts that delegate transfer control to it. Enabling * delegation is idempotent and permanent. If the account has a * balance at the time of enabling delegation, its balance is * also transferred to the given destination account `_to`. * NOTE: transfers to the zero address are disallowed. * * @param _vs The array of recovery byte components of the ECDSA signatures. * @param _rs The array of 'R' components of the ECDSA signatures. * @param _ss The array of 'S' components of the ECDSA signatures. * @param _to The destination for swept balances. */ function enableSweep(uint8[] memory _vs, bytes32[] memory _rs, bytes32[] memory _ss, address _to) public onlySweeper { require(_to != address(0), "no null value for `_to`"); require(blocked[_to] != true, "_to must not be blocked"); require((_vs.length == _rs.length) && (_vs.length == _ss.length), "_vs[], _rs[], _ss lengths are different"); uint256 numSignatures = _vs.length; uint256 sweptBalance = 0; for (uint256 i = 0; i < numSignatures; ++i) { address from = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",sweepMsg)), _vs[i], _rs[i], _ss[i]); require(blocked[from] != true, "_froms must not be blocked"); // ecrecover returns 0 on malformed input if (from != address(0)) { sweptSet[from] = true; uint256 fromBalance = erc20Store.balances(from); if (fromBalance > 0) { sweptBalance += fromBalance; erc20Store.setBalance(from, 0); erc20Proxy.emitTransfer(from, _to, fromBalance); } } } if (sweptBalance > 0) { erc20Store.addBalance(_to, sweptBalance); } } /** @notice For accounts that have delegated, transfer control * to the sweeper, this function transfers their balances to the given * destination. * * @dev The sweeper account is the only authorized caller of * this function. This function accepts an array of addresses to have their * balances transferred for gas efficiency purposes. * NOTE: any address for an account that has not been previously enabled * will be ignored. * NOTE: transfers to the zero address are disallowed. * * @param _froms The addresses to have their balances swept. * @param _to The destination address of all these transfers. */ function replaySweep(address[] memory _froms, address _to) public onlySweeper { require(_to != address(0), "no null value for `_to`"); require(blocked[_to] != true, "_to must not be blocked"); uint256 lenFroms = _froms.length; uint256 sweptBalance = 0; for (uint256 i = 0; i < lenFroms; ++i) { address from = _froms[i]; require(blocked[from] != true, "_froms must not be blocked"); if (sweptSet[from]) { uint256 fromBalance = erc20Store.balances(from); if (fromBalance > 0) { sweptBalance += fromBalance; erc20Store.setBalance(from, 0); erc20Proxy.emitTransfer(from, _to, fromBalance); } } } if (sweptBalance > 0) { erc20Store.addBalance(_to, sweptBalance); } } /** @notice Core logic of the ERC20 `transferFrom` function. * * @dev This function can only be called by the referenced proxy, * which has a `transferFrom` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: transfers to the zero address are disallowed. * * @param _sender The address initiating the transfer in a proxy. */ function transferFromWithSender( address _sender, address _from, address _to, uint256 _value ) public onlyProxy returns (bool success) { require(_to != address(0), "no null values for `_to`"); require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_from] != true, "_from must not be blocked"); require(blocked[_to] != true, "_to must not be blocked"); uint256 balanceOfFrom = erc20Store.balances(_from); require(_value <= balanceOfFrom, "insufficient funds on `_from` balance"); uint256 senderAllowance = erc20Store.allowed(_from, _sender); require(_value <= senderAllowance, "insufficient allowance amount"); erc20Store.setBalance(_from, balanceOfFrom - _value); erc20Store.addBalance(_to, _value); erc20Store.setAllowance(_from, _sender, senderAllowance - _value); erc20Proxy.emitTransfer(_from, _to, _value); return true; } /** @notice Core logic of the ERC20 `transfer` function. * * @dev This function can only be called by the referenced proxy, * which has a `transfer` function. * Every argument passed to that function as well as the original * `msg.sender` gets passed to this function. * NOTE: transfers to the zero address are disallowed. * * @param _sender The address initiating the transfer in a proxy. */ function transferWithSender( address _sender, address _to, uint256 _value ) public onlyProxy returns (bool success) { require(_to != address(0), "no null value for `_to`"); require(blocked[_sender] != true, "_sender must not be blocked"); require(blocked[_to] != true, "_to must not be blocked"); uint256 balanceOfSender = erc20Store.balances(_sender); require(_value <= balanceOfSender, "insufficient funds"); erc20Store.setBalance(_sender, balanceOfSender - _value); erc20Store.addBalance(_to, _value); erc20Proxy.emitTransfer(_sender, _to, _value); return true; } /** @notice Transfers the specified value from the balance in question. * * @dev Suspected balance is subtracted by the amount which will be transferred. * * @dev If the suspected balance has less than the amount requested, it will be set to 0. * * @param _from The address of suspected balance. * * @param _value The amount to transfer. * * @return success true if the transfer succeeded. */ function forceTransfer( address _from, address _to, uint256 _value ) public onlyCustodian returns (bool success) { require(_to != address(0), "no null value for `_to`"); uint256 balanceOfSender = erc20Store.balances(_from); if(_value <= balanceOfSender) { erc20Store.setBalance(_from, balanceOfSender - _value); erc20Store.addBalance(_to, _value); erc20Proxy.emitTransfer(_from, _to, _value); } else { erc20Store.setBalance(_from, 0); erc20Store.addBalance(_to, balanceOfSender); erc20Proxy.emitTransfer(_from, _to, balanceOfSender); } return true; } // METHODS (ERC20 sub interface impl.) /// @notice Core logic of the ERC20 `totalSupply` function. function totalSupply() public view returns (uint256) { return erc20Store.totalSupply(); } /// @notice Core logic of the ERC20 `balanceOf` function. function balanceOf(address _owner) public view returns (uint256 balance) { return erc20Store.balances(_owner); } /// @notice Core logic of the ERC20 `allowance` function. function allowance(address _owner, address _spender) public view returns (uint256 remaining) { return erc20Store.allowed(_owner, _spender); } /// @dev internal use only. function blockWallet(address wallet) public onlyCustodian returns (bool success) { blocked[wallet] = true; return true; } /// @dev internal use only. function unblockWallet(address wallet) public onlyCustodian returns (bool success) { blocked[wallet] = false; return true; } // EVENTS /// @dev Emitted by successful `requestPrint` calls. event PrintingLocked(bytes32 _lockId, address _receiver, uint256 _value); /// @dev Emitted by successful `confirmPrint` calls. event PrintingConfirmed(bytes32 _lockId, address _receiver, uint256 _value); /** @dev Emitted by successful `confirmWipe` calls. * * @param _value Amount requested to be burned. * * @param _burned Amount which was burned. * * @param _balance Amount left on account after burn. * * @param _from Account which balance was burned. */ event Wiped(address _from, uint256 _value, uint256 _burned, uint _balance); } /** @title A contact to govern hybrid control over increases to the token supply and managing accounts. * * @notice A contract that acts as a custodian of the active token * implementation, and an intermediary between it and the ‘true’ custodian. * It preserves the functionality of direct custodianship as well as granting * limited control of token supply increases to an additional key. * * @dev This contract is a layer of indirection between an instance of * ERC20Impl and a custodian. The functionality of the custodianship over * the token implementation is preserved (printing and custodian changes), * but this contract adds the ability for an additional key * (the 'controller') to increase the token supply up to a ceiling, * and this supply ceiling can only be raised by the custodian. * */ contract Controller is LockRequestable { // TYPES /// @dev The struct type for pending ceiling raises. struct PendingCeilingRaise { uint256 raiseBy; } /// @dev The struct type for pending wipes. struct wipeAddress { uint256 value; address from; } /// @dev The struct type for pending force transfer requests. struct forceTransferRequest { uint256 value; address from; address to; } // MEMBERS /// @dev The reference to the active token implementation. ERC20Impl public erc20Impl; /// @dev The address of the account or contract that acts as the custodian. Custodian public custodian; /** @dev The sole authorized caller of limited printing. * This account is also authorized to lower the supply ceiling and * wiping suspected accounts or force transferring funds from them. */ address public controller; /** @dev The maximum that the token supply can be increased to * through the use of the limited printing feature. * The difference between the current total supply and the supply * ceiling is what is available to the 'controller' account. * The value of the ceiling can only be increased by the custodian. */ uint256 public totalSupplyCeiling; /// @dev The map of lock ids to pending ceiling raises. mapping (bytes32 => PendingCeilingRaise) public pendingRaiseMap; /// @dev The map of lock ids to pending wipes. mapping (bytes32 => wipeAddress[]) public pendingWipeMap; /// @dev The map of lock ids to pending force transfer requests. mapping (bytes32 => forceTransferRequest) public pendingForceTransferRequestMap; // CONSTRUCTOR constructor( address _erc20Impl, address _custodian, address _controller, uint256 _initialCeiling ) public { erc20Impl = ERC20Impl(_erc20Impl); custodian = Custodian(_custodian); controller = _controller; totalSupplyCeiling = _initialCeiling; } // MODIFIERS modifier onlyCustodian { require(msg.sender == address(custodian), "only custodian"); _; } modifier onlyController { require(msg.sender == controller, "only controller"); _; } modifier onlySigner { require(custodian.signerSet(msg.sender) == true, "only signer"); _; } /** @notice Increases the token supply, with the newly created tokens * being added to the balance of the specified account. * * @dev The function checks that the value to print does not * exceed the supply ceiling when added to the current total supply. * NOTE: printing to the zero address is disallowed. * * @param _receiver The receiving address of the print. * @param _value The number of tokens to add to the total supply and the * balance of the receiving address. */ function limitedPrint(address _receiver, uint256 _value) public onlyController { uint256 totalSupply = erc20Impl.totalSupply(); uint256 newTotalSupply = totalSupply + _value; require(newTotalSupply >= totalSupply, "new total supply overflow"); require(newTotalSupply <= totalSupplyCeiling, "total supply ceiling overflow"); erc20Impl.confirmPrint(erc20Impl.requestPrint(_receiver, _value)); } /** @notice Requests wipe of suspected accounts. * * @dev Returns a unique lock id associated with the request. * Only controller can call this function, and only the custodian * can confirm the request. * * @param _froms The array of suspected accounts. * * @param _values array of amounts by which suspected accounts will be wiped. * * @return lockId A unique identifier for this request. */ function requestWipe(address[] memory _froms, uint256[] memory _values) public onlyController returns (bytes32 lockId) { require(_froms.length == _values.length, "_froms[] and _values[] must be same length"); lockId = generateLockId(); uint256 amount = _froms.length; for(uint256 i = 0; i < amount; i++) { address from = _froms[i]; uint256 value = _values[i]; pendingWipeMap[lockId].push(wipeAddress(value, from)); } emit WipeRequested(lockId); return lockId; } /** @notice Confirms a pending wipe of suspected accounts. * * @dev When called by the custodian with a lock id associated with a * pending wipe, the amount requested is burned from the suspected accounts. * * @param _lockId The identifier of a pending wipe request. */ function confirmWipe(bytes32 _lockId) public onlyCustodian { uint256 amount = pendingWipeMap[_lockId].length; for(uint256 i = 0; i < amount; i++) { wipeAddress memory addr = pendingWipeMap[_lockId][i]; address from = addr.from; uint256 value = addr.value; erc20Impl.burn(from, value); } delete pendingWipeMap[_lockId]; emit WipeCompleted(_lockId); } /** @notice Requests force transfer from the suspected account. * * @dev Returns a unique lock id associated with the request. * Only controller can call this function, and only the custodian * can confirm the request. * * @param _from address of suspected account. * * @param _to address of reciever. * * @param _value amount which will be transferred. * * @return lockId A unique identifier for this request. */ function requestForceTransfer(address _from, address _to, uint256 _value) public onlyController returns (bytes32 lockId) { lockId = generateLockId(); require (_value != 0, "no zero value transfers"); pendingForceTransferRequestMap[lockId] = forceTransferRequest(_value, _from, _to); emit ForceTransferRequested(lockId, _from, _to, _value); return lockId; } /** @notice Confirms a pending force transfer request. * * @dev When called by the custodian with a lock id associated with a * pending transfer request, the amount requested is transferred from the suspected account. * * @param _lockId The identifier of a pending transfer request. */ function confirmForceTransfer(bytes32 _lockId) public onlyCustodian { address from = pendingForceTransferRequestMap[_lockId].from; address to = pendingForceTransferRequestMap[_lockId].to; uint256 value = pendingForceTransferRequestMap[_lockId].value; delete pendingForceTransferRequestMap[_lockId]; erc20Impl.forceTransfer(from, to, value); emit ForceTransferCompleted(_lockId, from, to, value); } /** @notice Requests an increase to the supply ceiling. * * @dev Returns a unique lock id associated with the request. * Anyone can call this function, but confirming the request is authorized * by the custodian. * * @param _raiseBy The amount by which to raise the ceiling. * * @return lockId A unique identifier for this request. */ function requestCeilingRaise(uint256 _raiseBy) public returns (bytes32 lockId) { require(_raiseBy != 0, "no zero ceiling raise"); lockId = generateLockId(); pendingRaiseMap[lockId] = PendingCeilingRaise({ raiseBy: _raiseBy }); emit CeilingRaiseLocked(lockId, _raiseBy); } /** @notice Confirms a pending increase in the token supply. * * @dev When called by the custodian with a lock id associated with a * pending ceiling increase, the amount requested is added to the * current supply ceiling. * NOTE: this function will not execute any raise that would overflow the * supply ceiling, but it will not revert either. * * @param _lockId The identifier of a pending ceiling raise request. */ function confirmCeilingRaise(bytes32 _lockId) public onlyCustodian { PendingCeilingRaise storage pendingRaise = pendingRaiseMap[_lockId]; // copy locals of references to struct members uint256 raiseBy = pendingRaise.raiseBy; // accounts for a gibberish _lockId require(raiseBy != 0, "no gibberish _lockId"); delete pendingRaiseMap[_lockId]; uint256 newCeiling = totalSupplyCeiling + raiseBy; // overflow check if (newCeiling >= totalSupplyCeiling) { totalSupplyCeiling = newCeiling; emit CeilingRaiseConfirmed(_lockId, raiseBy, newCeiling); } } /** @notice Lowers the supply ceiling, further constraining the bound of * what can be printed by the controller. * * @dev The controller is the sole authorized caller of this function, * so it is the only account that can elect to lower its limit to increase * the token supply. * * @param _lowerBy The amount by which to lower the supply ceiling. */ function lowerCeiling(uint256 _lowerBy) public onlyController { uint256 newCeiling = totalSupplyCeiling - _lowerBy; // overflow check require(newCeiling <= totalSupplyCeiling, "totalSupplyCeiling overflow"); totalSupplyCeiling = newCeiling; emit CeilingLowered(_lowerBy, newCeiling); } /** @notice Pass-through control of print confirmation, allowing this * contract's custodian to act as the custodian of the associated * active token implementation. * * @dev This contract is the direct custodian of the active token * implementation, but this function allows this contract's custodian * to act as though it were the direct custodian of the active * token implementation. Therefore the custodian retains control of * unlimited printing. * * @param _lockId The identifier of a pending print request in * the associated active token implementation. */ function confirmPrintProxy(bytes32 _lockId) public onlyCustodian { erc20Impl.confirmPrint(_lockId); } /** @notice Pass-through control of custodian change confirmation, * allowing this contract's custodian to act as the custodian of * the associated active token implementation. * * @dev This contract is the direct custodian of the active token * implementation, but this function allows this contract's custodian * to act as though it were the direct custodian of the active * token implementation. Therefore the custodian retains control of * custodian changes. * * @param _lockId The identifier of a pending custodian change request * in the associated active token implementation. */ function confirmCustodianChangeProxy(bytes32 _lockId) public onlyCustodian { erc20Impl.confirmCustodianChange(_lockId); } /** @notice Blocks all transactions with a wallet. * * @dev Only signers from custodian are authorized to call this function * * @param wallet account which will be blocked. */ function blockWallet(address wallet) public onlySigner { erc20Impl.blockWallet(wallet); emit Blocked(wallet); } /** @notice Unblocks all transactions with a wallet. * * @dev Only signers from custodian are authorized to call this function * * @param wallet account which will be unblocked. */ function unblockWallet(address wallet) public onlySigner { erc20Impl.unblockWallet(wallet); emit Unblocked(wallet); } // EVENTS /// @dev Emitted by successful `requestCeilingRaise` calls. event CeilingRaiseLocked(bytes32 _lockId, uint256 _raiseBy); /// @dev Emitted by successful `confirmCeilingRaise` calls. event CeilingRaiseConfirmed(bytes32 _lockId, uint256 _raiseBy, uint256 _newCeiling); /// @dev Emitted by successful `lowerCeiling` calls. event CeilingLowered(uint256 _lowerBy, uint256 _newCeiling); /// @dev Emitted by successful `blockWallet` calls. event Blocked(address _wallet); /// @dev Emitted by successful `unblockWallet` calls. event Unblocked(address _wallet); /// @dev Emitted by successful `requestForceTransfer` calls. event ForceTransferRequested(bytes32 _lockId, address _from, address _to, uint256 _value); /// @dev Emitted by successful `confirmForceTransfer` calls. event ForceTransferCompleted(bytes32 _lockId, address _from, address _to, uint256 _value); /// @dev Emitted by successful `requestWipe` calls. event WipeRequested(bytes32 _lockId); /// @dev Emitted by successful `confirmWipe` calls. event WipeCompleted(bytes32 _lockId); }