Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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