Last active
November 11, 2019 12:51
-
-
Save rjl493456442/cdc005badb3c2baaa2a2a90552154d5a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| pragma solidity ^0.5.12; | |
| /** | |
| * @title AccountBook | |
| * @author Gary Rong<[email protected]> | |
| * @dev Implementation of the account book which les server can use to | |
| * @dev process micropayments from customers. | |
| */ | |
| contract AccountBook { | |
| /* | |
| Events | |
| */ | |
| // withdrawEvent is emitted if customer opens a request to withdraw | |
| // deposit. | |
| event withdrawEvent(address indexed addr, uint256 amount); | |
| // balanceChangedEvent is emitted when the deposit balance of customer | |
| // is changed. | |
| event balanceChangedEvent(address indexed addr, uint256 oldBalance, uint256 newBalance); | |
| /* | |
| Definitions | |
| */ | |
| // withdrawRequest defines all necessary fields of | |
| // the withdraw request from customers | |
| struct withdrawRequest { | |
| uint256 amount; // the amount requested to withdraw | |
| uint256 createdAt; // the created block number | |
| } | |
| /* | |
| Modifier | |
| */ | |
| modifier onlyOwner() { | |
| require(msg.sender == owner); | |
| _; | |
| } | |
| /* | |
| Public Functions | |
| */ | |
| constructor(uint256 _challengeTimeWindow) public { | |
| owner = msg.sender; | |
| // challengeTimeWindow is decided by owner itself. In theory | |
| // the longer challenge time window, the safer for owner. But | |
| // in the mean time, it will take longer for customers to withdraw | |
| // money. The inconvenience may lead to lose potentional customers. | |
| challengeTimeWindow = _challengeTimeWindow; | |
| } | |
| // deposit adds the amount of money into the account book | |
| function deposit() payable public { | |
| uint256 balance = deposits[msg.sender]; | |
| require(balance + msg.value > balance, "addition overflow or zero deposit"); | |
| deposits[msg.sender] += msg.value; | |
| emit balanceChangedEvent(msg.sender, balance, balance+msg.value); | |
| } | |
| // withdraw opens a request to withdraw the deposit from the | |
| // account book. Caller has to wait challengeTimeWindow blocks | |
| // time which leaves enough time window for owner to challenge | |
| // the withdraw amount. | |
| // @amount: the amount of deposit to withdraw | |
| function withdraw(uint256 amount) public { | |
| // Ensure it's a meaningful withdraw request | |
| if (amount == 0) { | |
| return; | |
| } | |
| // Ensure the customer has enough deposit to withdraw | |
| if (deposits[msg.sender] < amount) { | |
| return; | |
| } | |
| // Account book can only process one withdraw request | |
| // at the same time. | |
| if (withdrawRequests[msg.sender].amount > 0) { | |
| return; | |
| } | |
| withdrawRequests[msg.sender] = withdrawRequest({amount: amount, createdAt: block.number}); | |
| emit withdrawEvent(msg.sender, amount); | |
| } | |
| // claim withdraws the deposit from the account book which | |
| // has passed the challenge period. | |
| function claim() public { | |
| uint256 amount = withdrawRequests[msg.sender].amount; | |
| // Short circuit if the withdrawal amount is zero. | |
| // There are several situations can lead to this case: | |
| // * there is no withdraw request at all | |
| // * there is no withdrawal deposit since all of the deposit | |
| // is used and cashed by owner of the account book | |
| if (amount == 0) { | |
| return; | |
| } | |
| // Ensure the request has passed the challenge period. | |
| if (block.number - withdrawRequests[msg.sender].createdAt < challengeTimeWindow) { | |
| return; | |
| } | |
| // Decrease the balance of customer before transfer. | |
| uint256 balance = deposits[msg.sender]; | |
| deposits[msg.sender] -= amount; | |
| delete withdrawRequests[msg.sender]; // Release the withdraw lock. | |
| msg.sender.transfer(amount); | |
| emit balanceChangedEvent(msg.sender, balance, balance-amount); | |
| } | |
| // cash claims the specified amount of money from payer's deposit with | |
| // offchain signature. | |
| // | |
| // For cash operation, since it's called by owner itself, so that it's | |
| // unnecessary to leave a challenge time window. | |
| // | |
| // This function can be called in two cases: | |
| // * the owner of account book thinks there are too many payments made | |
| // by customers | |
| // * if the customer opens a withdraw request which tries to withdraw | |
| // some spent money, it's a way to challenge the request. | |
| // | |
| // @payer: the address of payer who has made a few micropayments off-chain. | |
| // @amount : the amount of money owner wants to cash | |
| // @sig_v : the v-value of the signature | |
| // @sig_r : the r-value of the signature | |
| // @sig_s : the s-value of the signature | |
| function cash(address payer, uint256 amount, uint8 sig_v, bytes32 sig_r, bytes32 sig_s) onlyOwner public { | |
| // In order to prevent the owner to double-cash the | |
| // cheque of customer, we record the cashed amount | |
| // in contract. Only higher signed amount can make | |
| // a valid cash operation. | |
| require(amount > paids[payer]); | |
| // Check the digital signature of the cheque. | |
| // | |
| // EIP 191 style signatures | |
| // | |
| // Arguments when calculating hash to validate | |
| // 1: byte(0x19) - the initial 0x19 byte | |
| // 2: byte(0) - the version byte (data with intended validator) | |
| // 3: this - the validator address | |
| // -- Application specific data | |
| // 4 : amount the amount of paid money | |
| bytes32 hash = keccak256(abi.encodePacked(byte(0x19), byte(0), this, amount)); | |
| require(payer == ecrecover(hash, sig_v, sig_r, sig_s)); | |
| // Cash all cheques to owner's account directly. | |
| uint256 balance = deposits[payer]; | |
| uint256 newBalance; | |
| uint256 diff = amount - paids[payer]; | |
| // Move the money into owner's address | |
| if (balance >= diff) { | |
| // Payer has enough deposit to cover all spends | |
| newBalance = balance - diff; | |
| deposits[payer] = newBalance; | |
| owner.transfer(diff); | |
| emit balanceChangedEvent(payer, balance, newBalance); | |
| } else if (balance >= 0) { | |
| // It can happen that payer doesn't have enough deposit to cover spends. | |
| // In theory owner should reject all "invalid" cheques off-chain. But if | |
| // some errors occur that owner accept the "useless" cheque, we still support | |
| // owner to cash all "spent" money. | |
| delete deposits[payer]; | |
| // Transfer all remaing money into owner's pocket. | |
| owner.transfer(balance); | |
| emit balanceChangedEvent(payer, balance, 0); | |
| } | |
| paids[payer] = amount; // Record all cashed amount in order to prevent "double-cash" | |
| // If customer want to withdraw some spent money, reject | |
| // it by decreaing the amount or just delete the request. | |
| // It's the challenge action for invalid withdraw request. | |
| if (withdrawRequests[payer].amount > newBalance) { | |
| if (newBalance == 0) { | |
| delete withdrawRequests[payer]; | |
| } else { | |
| withdrawRequests[payer].amount = newBalance; | |
| } | |
| } | |
| } | |
| /* | |
| Fields | |
| */ | |
| // paids is the map which contains the cumulative paid amount | |
| // in wei from each customer | |
| mapping(address => uint256) public paids; | |
| // deposits is the map which contains the deposit of customers. | |
| // Customers can withdraw unused deposit back. | |
| mapping(address => uint256) public deposits; | |
| // withdrawRequests is the map which contains all withdraw | |
| // requests from customers, no matter to withdraw all deposit | |
| // or a part. | |
| mapping(address => withdrawRequest) public withdrawRequests; | |
| // owner is the address of the account book owner(the address | |
| // of les server). | |
| address payable public owner; | |
| // challengeTimeWindow is the maximum time that owner can perform | |
| // challenge when customer requests deposit withdrawal. It's count | |
| // by block number. | |
| uint256 public challengeTimeWindow; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment