Created
July 6, 2020 12:24
-
-
Save agatsoh/d8b2016ba2243df6c940d396ec2f5b5c 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.16; | |
interface Token { | |
/// @return total amount of tokens | |
function totalSupply() external view returns (uint256 supply); | |
/// @param _owner The address from which the balance will be retrieved | |
/// @return The balance | |
function balanceOf(address _owner) external view returns (uint256 balance); | |
/// @notice send `_value` token to `_to` from `msg.sender` | |
/// @param _to The address of the recipient | |
/// @param _value The amount of token to be transferred | |
/// @return Whether the transfer was successful or not | |
function transfer(address _to, uint256 _value) external returns (bool success); | |
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` | |
/// @param _from The address of the sender | |
/// @param _to The address of the recipient | |
/// @param _value The amount of token to be transferred | |
/// @return Whether the transfer was successful or not | |
function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); | |
/// @notice `msg.sender` approves `_spender` to spend `_value` tokens | |
/// @param _spender The address of the account able to transfer the tokens | |
/// @param _value The amount of wei to be approved for transfer | |
/// @return Whether the approval was successful or not | |
function approve(address _spender, uint256 _value) external returns (bool success); | |
/// @param _owner The address of the account owning tokens | |
/// @param _spender The address of the account able to transfer the tokens | |
/// @return Amount of remaining tokens allowed to spent | |
function allowance(address _owner, address _spender) external view returns (uint256 remaining); | |
event Transfer(address indexed _from, address indexed _to, uint256 _value); | |
event Approval(address indexed _owner, address indexed _spender, uint256 _value); | |
// Optionally implemented function to show the number of decimals for the token | |
function decimals() external view returns (uint8 decimals); | |
} | |
contract Utils { | |
enum MessageTypeId { | |
None, | |
BalanceProof, | |
BalanceProofUpdate, | |
Withdraw, | |
CooperativeSettle, | |
IOU, | |
MSReward | |
} | |
/// @notice Check if a contract exists | |
/// @param contract_address The address to check whether a contract is | |
/// deployed or not | |
/// @return True if a contract exists, false otherwise | |
function contractExists(address contract_address) public view returns (bool) { | |
uint size; | |
assembly { | |
size := extcodesize(contract_address) | |
} | |
return size > 0; | |
} | |
} | |
contract UserDeposit is Utils { | |
uint constant public withdraw_delay = 100; // time before withdraw is allowed in blocks | |
// Token to be used for the deposit | |
Token public token; | |
// Trusted contracts (can execute `transfer`) | |
address public msc_address; | |
address public one_to_n_address; | |
// Total amount of tokens that have been deposited. This is monotonous and | |
// doing a transfer or withdrawing tokens will not decrease total_deposit! | |
mapping(address => uint256) public total_deposit; | |
// Current user's balance, ignoring planned withdraws | |
mapping(address => uint256) public balances; | |
mapping(address => WithdrawPlan) public withdraw_plans; | |
// The sum of all balances | |
uint256 public whole_balance = 0; | |
// Deposit limit for this whole contract | |
uint256 public whole_balance_limit; | |
/* | |
* Structs | |
*/ | |
struct WithdrawPlan { | |
uint256 amount; | |
uint256 withdraw_block; // earliest block at which withdraw is allowed | |
} | |
/* | |
* Events | |
*/ | |
event BalanceReduced(address indexed owner, uint newBalance); | |
event WithdrawPlanned(address indexed withdrawer, uint plannedBalance); | |
/* | |
* Modifiers | |
*/ | |
modifier canTransfer() { | |
require(msg.sender == msc_address || msg.sender == one_to_n_address, "unknown caller"); | |
_; | |
} | |
/* | |
* Constructor | |
*/ | |
/// @notice Set the default values for the smart contract | |
/// @param _token_address The address of the token to use for rewards | |
constructor(address _token_address, uint256 _whole_balance_limit) | |
public | |
{ | |
// check token contract | |
require(_token_address != address(0x0), "token at address zero"); | |
require(contractExists(_token_address), "token has no code"); | |
token = Token(_token_address); | |
require(token.totalSupply() > 0, "token has no total supply"); // Check if the contract is indeed a token contract | |
// check and set the whole balance limit | |
require(_whole_balance_limit > 0, "whole balance limit is zero"); | |
whole_balance_limit = _whole_balance_limit; | |
} | |
/// @notice Specify trusted contracts. This has to be done outside of the | |
/// constructor to avoid cyclic dependencies. | |
/// @param _msc_address Address of the MonitoringService contract | |
/// @param _one_to_n_address Address of the OneToN contract | |
function init(address _msc_address, address _one_to_n_address) | |
external | |
{ | |
// prevent changes of trusted contracts after initialization | |
require(msc_address == address(0x0) && one_to_n_address == address(0x0), "already initialized"); | |
// check monitoring service contract | |
require(_msc_address != address(0x0), "MS contract at address zero"); | |
require(contractExists(_msc_address), "MS contract has no code"); | |
msc_address = _msc_address; | |
// check one to n contract | |
require(_one_to_n_address != address(0x0), "OneToN at address zero"); | |
require(contractExists(_one_to_n_address), "OneToN has no code"); | |
one_to_n_address = _one_to_n_address; | |
} | |
/// @notice Deposit tokens. The amount of transferred tokens will be | |
/// `new_total_deposit - total_deposit[beneficiary]`. This makes the | |
/// function behavior predictable and idempotent. Can be called several | |
/// times and on behalf of other accounts. | |
/// @param beneficiary The account benefiting from the deposit | |
/// @param new_total_deposit The total sum of tokens that have been | |
/// deposited by the user by calling this function. | |
function deposit(address beneficiary, uint256 new_total_deposit) | |
external | |
{ | |
require(new_total_deposit > total_deposit[beneficiary], "deposit not increasing"); | |
// Calculate the actual amount of tokens that will be transferred | |
uint256 added_deposit = new_total_deposit - total_deposit[beneficiary]; | |
balances[beneficiary] += added_deposit; | |
total_deposit[beneficiary] += added_deposit; | |
// Update whole_balance, but take care against overflows. | |
require(whole_balance + added_deposit >= whole_balance, "overflowing deposit"); | |
whole_balance += added_deposit; | |
// Decline deposit if the whole balance is bigger than the limit. | |
require(whole_balance <= whole_balance_limit, "too much deposit"); | |
// Actual transfer. | |
require(token.transferFrom(msg.sender, address(this), added_deposit), "tokens didn't transfer"); | |
} | |
/// @notice Internally transfer deposits between two addresses. | |
/// Sender and receiver must be different or the transaction will fail. | |
/// @param sender Account from which the amount will be deducted | |
/// @param receiver Account to which the amount will be credited | |
/// @param amount Amount of tokens to be transferred | |
/// @return true if transfer has been done successfully, otherwise false | |
function transfer( | |
address sender, | |
address receiver, | |
uint256 amount | |
) | |
canTransfer() | |
external | |
returns (bool success) | |
{ | |
require(sender != receiver, "sender == receiver"); | |
if (balances[sender] >= amount && amount > 0) { | |
balances[sender] -= amount; | |
balances[receiver] += amount; | |
emit BalanceReduced(sender, balances[sender]); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/// @notice Announce intention to withdraw tokens. | |
/// Sets the planned withdraw amount and resets the withdraw_block. | |
/// There is only one planned withdrawal at a time, the old one gets overwritten. | |
/// @param amount Maximum amount of tokens to be withdrawn | |
function planWithdraw(uint256 amount) | |
external | |
{ | |
require(amount > 0, "withdrawing zero"); | |
require(balances[msg.sender] >= amount, "withdrawing too much"); | |
withdraw_plans[msg.sender] = WithdrawPlan({ | |
amount: amount, | |
withdraw_block: block.number + withdraw_delay | |
}); | |
emit WithdrawPlanned(msg.sender, balances[msg.sender] - amount); | |
} | |
/// @notice Execute a planned withdrawal | |
/// Will only work after the withdraw_delay has expired. | |
/// An amount lower or equal to the planned amount may be withdrawn. | |
/// Removes the withdraw plan even if not the full amount has been | |
/// withdrawn. | |
/// @param amount Amount of tokens to be withdrawn | |
function withdraw(uint256 amount) | |
external | |
{ | |
WithdrawPlan storage withdraw_plan = withdraw_plans[msg.sender]; | |
require(amount <= withdraw_plan.amount, "withdrawing more than planned"); | |
require(withdraw_plan.withdraw_block <= block.number, "withdrawing too early"); | |
uint256 withdrawable = min(amount, balances[msg.sender]); | |
balances[msg.sender] -= withdrawable; | |
// Update whole_balance, but take care against underflows. | |
require(whole_balance - withdrawable <= whole_balance, "underflow in whole_balance"); | |
whole_balance -= withdrawable; | |
emit BalanceReduced(msg.sender, balances[msg.sender]); | |
delete withdraw_plans[msg.sender]; | |
require(token.transfer(msg.sender, withdrawable), "tokens didn't transfer"); | |
} | |
/// @notice The owner's balance with planned withdrawals deducted | |
/// @param owner Address for which the balance should be returned | |
/// @return The remaining balance after planned withdrawals | |
function effectiveBalance(address owner) | |
external | |
view | |
returns (uint256 remaining_balance) | |
{ | |
WithdrawPlan storage withdraw_plan = withdraw_plans[owner]; | |
if (withdraw_plan.amount > balances[owner]) { | |
return 0; | |
} | |
return balances[owner] - withdraw_plan.amount; | |
} | |
function min(uint256 a, uint256 b) pure internal returns (uint256) | |
{ | |
return a > b ? b : a; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment