├── BackupAddressWallet.sol ├── FastWithdrawal.sol ├── GasPriceFloor.sol ├── MMRStorage.sol └── Ownable.sol /BackupAddressWallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | import "./Ownable.sol"; 4 | 5 | /** 6 | * @title BackupAddressWallet 7 | * @dev BackupAddressWallet is basically a simple wallet that makes it possible 8 | * to transfer funds to a backup address if the owner ever loses their keys. 9 | * The basic mechanism here is that some user puts up a bond (bondAmount, 10 | * specified by the owner) to initiate a transfer. If the owner doesn't cancel 11 | * the transfer within some period of time (backupCancelPeriod, also specified 12 | * by the owner), then the funds are transferred and the bond refunded. If the 13 | * owner does cancel, then the bond is added to the contract's balance and not 14 | * refunded. This requires that the owner check if a transfer has been 15 | * initiated at least once per backupCancelPeriod. 16 | */ 17 | contract BackupAddressWallet is Ownable { 18 | address public backup; 19 | address public transferInitiator; 20 | uint256 public bondAmount; 21 | uint256 public bondPaid; 22 | uint256 public backupCancelPeriod; 23 | uint256 public backupStartTime; 24 | 25 | event TransferStarted(address indexed _initiator, uint256 _startTime); 26 | event TransferCancelled(); 27 | event TransferCompleted(uint256 _amount); 28 | event Transfer(address indexed _receiver, uint256 _amount); 29 | 30 | /** 31 | * @dev Reverts if transfer hasn't been initiated. 32 | */ 33 | modifier transferStarted() { 34 | require(backupStartTime > 0); 35 | _; 36 | } 37 | 38 | /** 39 | * @dev Reverts if transfer has been started. 40 | */ 41 | modifier transferNotStarted() { 42 | require(backupStartTime == 0); 43 | _; 44 | } 45 | 46 | /** 47 | * @dev Reverts if transfer period has not elapsed. 48 | */ 49 | modifier transferPeriodElapsed() { 50 | require(block.timestamp - backupStartTime > backupCancelPeriod); 51 | _; 52 | } 53 | 54 | /** 55 | * @dev Reverts if the message value is not exactly the bond amount. 56 | */ 57 | modifier transferBondEnough() { 58 | require(msg.value == bondAmount); 59 | _; 60 | } 61 | 62 | /** 63 | * @dev Constructor, takes the backup address, cancellation period, and bond amount 64 | * @param _backup Backup address 65 | * @param _backupCancelPeriod Period in which transfer can be cancelled 66 | * @param _bondAmount Bond required to initiate a transfer 67 | */ 68 | constructor(address _backup, uint256 _backupCancelPeriod, uint256 _bondAmount) public { 69 | backup = _backup; 70 | backupCancelPeriod = _backupCancelPeriod; 71 | bondAmount = _bondAmount; 72 | } 73 | 74 | function () public payable { } 75 | 76 | /** 77 | * @dev Set the backup address to something else 78 | * @param _backup New backup address 79 | */ 80 | function setBackup(address _backup) public onlyOwner { 81 | backup = _backup; 82 | } 83 | 84 | /** 85 | * @dev Set the bond amount to something else 86 | * @param _bondAmount New bond amount 87 | */ 88 | function setBondAmount(uint256 _bondAmount) public onlyOwner { 89 | bondAmount = _bondAmount; 90 | } 91 | 92 | /** 93 | * @dev Start the backup transfer challenge period, requires a bond 94 | */ 95 | function startBackupTransfer() public payable transferNotStarted transferBondEnough { 96 | backupStartTime = block.timestamp; 97 | transferInitiator = msg.sender; 98 | bondPaid = bondAmount; 99 | 100 | emit TransferStarted(transferInitiator, backupStartTime); 101 | } 102 | 103 | /** 104 | * @dev Cancel the backup transfer and slash the bond 105 | */ 106 | function cancelBackupTransfer() public transferStarted onlyOwner { 107 | backupStartTime = 0; 108 | transferInitiator = 0; 109 | 110 | emit TransferCancelled(); 111 | } 112 | 113 | /** 114 | * @dev Complete the backup transfer and refund the bond 115 | */ 116 | function completeBackupTransfer() public transferStarted transferPeriodElapsed { 117 | transferInitiator.transfer(bondPaid); 118 | uint256 balance = getBalance(); 119 | backup.transfer(balance); 120 | 121 | bondPaid = 0; 122 | backupStartTime = 0; 123 | 124 | emit TransferCompleted(balance); 125 | } 126 | 127 | /** 128 | * @dev Allows the owner to transfer funds from this account 129 | * @param _receiver Address to send to 130 | * @param _amount Amount to transfer 131 | */ 132 | function transfer(address _receiver, uint256 _amount) public onlyOwner { 133 | _receiver.transfer(_amount); 134 | 135 | emit Transfer(_receiver, _amount); 136 | } 137 | 138 | /** 139 | * @dev Allows the owner to withdraw funds 140 | * @param _amount Amount to withdraw 141 | */ 142 | function withdraw(uint256 _amount) public onlyOwner { 143 | transfer(owner, _amount); 144 | } 145 | 146 | /** 147 | * @dev Returns the contract's balance 148 | * @return The contract's balance, in wei 149 | */ 150 | function getBalance() public view returns (uint256) { 151 | return address(this).balance; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /FastWithdrawal.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | import "./PlasmaCore.sol"; 4 | import "./RootChain.sol"; 5 | 6 | contract FastWithdrawal { 7 | using PlasmaCore for bytes; 8 | 9 | 10 | /* 11 | * Storage 12 | */ 13 | 14 | mapping (address => mapping (uint256 => ExitToken)) public exits; 15 | 16 | struct ExitToken { 17 | address owner; 18 | uint256 price; 19 | bool buyable; 20 | } 21 | 22 | 23 | /* 24 | * Events 25 | */ 26 | 27 | event ExitTokenCreated( 28 | address indexed plasmaAddress, 29 | address indexed owner, 30 | uint256 amount 31 | ); 32 | 33 | 34 | /* 35 | * Public functions 36 | */ 37 | 38 | /** 39 | * @dev Starts a fast exit by proxying through this contract. Requires that the sender prove they sent the funds. 40 | * @param _plasmaAddress Address of the Plasma contract to withdraw from. 41 | * @param _inputId ID of the input UTXO. 42 | * @param _inputTx RLP encoded transaction that created the input. 43 | * @param _inputTxInclusionProof Proof that the transaction was included. 44 | * @param _outputIndex Which output in the input transaction was spent. 45 | * @param _outputId ID of the output transaction. 46 | * @param _outputTx RLP encoded transaction that created the output to this contract. 47 | * @param _outputTxInclusionProof Proof that the transaction was included. 48 | * @param _price Price at which this exit can be bought. 49 | */ 50 | function startFastExit( 51 | address _plasmaAddress, 52 | uint256 _inputId, 53 | bytes _inputTx, 54 | bytes _inputTxInclusionProof, 55 | uint256 _outputIndex, 56 | uint256 _outputId, 57 | bytes _outputTx, 58 | bytes _outputTxInclusionProof, 59 | uint256 _price 60 | ) 61 | public 62 | payable 63 | { 64 | RootChain rootChain = RootChain(_plasmaAddress); 65 | 66 | // Verify the owner of this exit. 67 | require(_outputTx.getInputId(0) == _inputId); 68 | require(rootChain.transactionIncluded(_inputTx, _inputId, _inputTxInclusionProof)); 69 | require(_inputTx.getOutput(_outputIndex).owner == msg.sender); 70 | 71 | // Start the exit. 72 | rootChain.startExit.value(msg.value)(_outputId, _outputTx, _outputTxInclusionProof); 73 | 74 | // Update the mapping. 75 | uint256 amount; 76 | (, amount) = rootChain.exits(_outputId); 77 | exits[_plasmaAddress][_outputId] = ExitToken({ 78 | owner: msg.sender, 79 | price: _price, 80 | buyable: true 81 | }); 82 | 83 | emit ExitTokenCreated(_plasmaAddress, msg.sender, amount); 84 | } 85 | 86 | /** 87 | * @dev Allows a user to buy an exit. 88 | * @param _plasmaAddress Address of the Plasma contract containing this exit. 89 | * @param _outputId Identifier of the output being purchased. 90 | */ 91 | function buyExit( 92 | address _plasmaAddress, 93 | uint256 _outputId 94 | ) 95 | public 96 | payable 97 | { 98 | ExitToken storage exitToken = exits[_plasmaAddress][_outputId]; 99 | 100 | // Validate the purchase. 101 | require(exitToken.buyable); 102 | require(msg.value == exitToken.price); 103 | 104 | // Send the money. 105 | exitToken.owner.transfer(msg.value); 106 | 107 | // Update the token info. 108 | exitToken.owner = msg.sender; 109 | exitToken.buyable = false; 110 | } 111 | 112 | /** 113 | * @dev Fallback is used to send out funds to the owner of the given exit. 114 | */ 115 | function () 116 | public 117 | payable 118 | { 119 | uint256 outputId = _bytesToUint256(msg.data); 120 | ExitToken storage exitToken = exits[msg.sender][outputId]; 121 | if (exitToken.owner != address(0)) { 122 | exitToken.owner.send(msg.value); 123 | } 124 | delete exitToken.owner; 125 | } 126 | 127 | 128 | /* 129 | * Internal functions 130 | */ 131 | 132 | /** 133 | * @dev Converts bytes to uint256, assumes 32 bytes long. 134 | * @param _b Bytes to convert to uint256. 135 | * @return Converted uint256. 136 | */ 137 | function _bytesToUint256(bytes _b) 138 | internal 139 | pure 140 | returns (uint256) 141 | { 142 | uint256 x; 143 | assembly { 144 | x := mload(add(_b, 32)) 145 | } 146 | return x; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /GasPriceFloor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /** 4 | * I had a lot of fun thinking through this. 5 | * Basically, this contract allows the owner of the contract to fix the minimum gas price for a period of time. 6 | * The owner should first fund the contract with a sufficient balance. 7 | * Then, the owner can set `gasPrice` and `endBlock`. 8 | * `killTrees()` basically burns any gas sent along with the transaction. 9 | * Anyone who calls `killTrees()` with a gas price equal to `gasPrice` will have their gas refunded. 10 | * However, this refund is *only* valid until `endBlock`. 11 | * Miners can therefore call `killTrees()` at any time during the period to fill the remainder of a block at the specified gas price. 12 | * Because miners always have the option to call `killTrees()`, other users *must* pay at least `gasPrice` to have their transactions included. 13 | * This ends up being an efficient way to set a price floor on gas prices. 14 | */ 15 | 16 | contract GasPriceFloor { 17 | address public owner; 18 | uint256 public gasPrice; 19 | uint256 public endBlock; 20 | 21 | function () public payable {} 22 | 23 | modifier onlyOwner() { 24 | require(msg.sender == owner, "Invalid sender."); 25 | _; 26 | } 27 | 28 | constructor () public { 29 | owner = msg.sender; 30 | } 31 | 32 | function setGasPrice(uint256 _newGasPrice) public onlyOwner { 33 | gasPrice = _newGasPrice; 34 | } 35 | 36 | function setEndBlock(uint256 _newEndBlock) public onlyOwner { 37 | endBlock = _newEndBlock; 38 | } 39 | 40 | 41 | function killTrees() public { 42 | // Check that this burn is valid. 43 | require(block.number < endBlock, "Invalid block number."); 44 | require(tx.gasprice == gasPrice, "Invalid gas price."); 45 | 46 | uint256 totalRefund = (gasleft() + 19000) * gasPrice; // Approximation. 47 | 48 | // Speed up global warming. 49 | while (gasleft() > 10000) {} 50 | 51 | // Pay the user for their contribution. 52 | msg.sender.transfer(totalRefund); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MMRStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | contract MMRStorage { 4 | /* 5 | * Storage 6 | */ 7 | 8 | uint256 public commitments = 1; 9 | bytes32[32] public slots; 10 | 11 | 12 | /* 13 | * Public functions 14 | */ 15 | 16 | /** 17 | * @dev Commits some bytes32 data to the MMR. 18 | * @param _data Data to commit. 19 | */ 20 | function commit(bytes32 _data) 21 | public 22 | { 23 | uint256 slot = _firstSetBit(commitments); 24 | bytes32 root = _data; 25 | if (commitments & 1 == 0) { 26 | root = _merklize(root, _firstSetBit(slot)); 27 | } 28 | slots[slot] = root; 29 | commitments += 1; 30 | } 31 | 32 | 33 | /* 34 | * Internal functions 35 | */ 36 | 37 | /** 38 | * @dev Finds the index of the first set bit of an integer. 39 | * @param _x Integer to query. 40 | * @return Index of the first set bit. 41 | */ 42 | function _firstSetBit(uint256 _x) 43 | internal 44 | pure 45 | returns (uint256 index) 46 | { 47 | for (index = 0; index < 32; index++) { 48 | if (_bitSet(_x, index)) { 49 | return index; 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * @dev Determines whether some bit of an integer is set. 56 | * @param _x Integer to query. 57 | * @param _index Bit to check. 58 | * @return True if the bit is set, false otherwise. 59 | */ 60 | function _bitSet(uint256 _x, uint256 _index) 61 | internal 62 | pure 63 | returns (bool) 64 | { 65 | return _x >> _index & 1 == 1; 66 | } 67 | 68 | /** 69 | * @dev Merklizes a range, along with some data. 70 | * @param _data New leaf node. 71 | * @param _range Range of the MMR to merklize. 72 | * @return Root of the tree. 73 | */ 74 | function _merklize(bytes32 _data, uint256 _range) 75 | internal 76 | view 77 | returns (bytes32) 78 | { 79 | bytes32 root = _data; 80 | for (uint i = 0; i <= _range; i++) { 81 | root = keccak256(abi.encodePacked(root, slots[i])); 82 | } 83 | return root; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | /** 4 | * @title Ownable 5 | * @dev The Ownable contract has an owner address, and provides basic authorization control 6 | * functions, this simplifies the implementation of "user permissions". 7 | */ 8 | contract Ownable { 9 | address public owner; 10 | 11 | 12 | event OwnershipRenounced(address indexed previousOwner); 13 | event OwnershipTransferred( 14 | address indexed previousOwner, 15 | address indexed newOwner 16 | ); 17 | 18 | 19 | /** 20 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 21 | * account. 22 | */ 23 | constructor() public { 24 | owner = msg.sender; 25 | } 26 | 27 | /** 28 | * @dev Throws if called by any account other than the owner. 29 | */ 30 | modifier onlyOwner() { 31 | require(msg.sender == owner); 32 | _; 33 | } 34 | 35 | /** 36 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 37 | * @param newOwner The address to transfer ownership to. 38 | */ 39 | function transferOwnership(address newOwner) public onlyOwner { 40 | require(newOwner != address(0)); 41 | emit OwnershipTransferred(owner, newOwner); 42 | owner = newOwner; 43 | } 44 | 45 | /** 46 | * @dev Allows the current owner to relinquish control of the contract. 47 | */ 48 | function renounceOwnership() public onlyOwner { 49 | emit OwnershipRenounced(owner); 50 | owner = address(0); 51 | } 52 | } 53 | --------------------------------------------------------------------------------