Skip to content

Commit

Permalink
Simplify Stake
Browse files Browse the repository at this point in the history
See issue #314.
  • Loading branch information
abukosek committed Jul 6, 2018
1 parent 0336583 commit 31d686f
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 880 deletions.
192 changes: 73 additions & 119 deletions ethereum/contracts/Stake.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,74 +3,68 @@ pragma solidity ^0.4.23;
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; }

import "./ERC20Interface.sol";
import "./UintSet.sol";

// The Ekiden Stake token. It is ERC20 compatible, but also includes
// the notion of escrow accounts in addition to allowances. Escrow
// Accounts hold tokens that the stakeholder cannot use until the
// escrow account is closed.
// the notion of escrow in addition to allowances. Escrow holds tokens
// that the stakeholder cannot use until the escrow is closed.
contract Stake is ERC20Interface {
using UintSet for UintSet.Data;
// The convention used here is that contract input parameters are
// prefixed with an underscore, and output parameters (in returns
// list) are suffixed with an underscore.

// Maximum amount of stake that an account can hold.
uint256 constant AMOUNT_MAX = ~uint256(0);

event Burn(address indexed from, uint256 value);
event EscrowCreate(uint indexed escrow_id,
address indexed owner,
address indexed target,
uint256 escrow_amount,
bytes32 aux);
event EscrowClose(uint indexed escrow_id, bytes32 aux,
address indexed owner, uint256 value_returned,
address indexed target, uint256 value_claimed);

// This event is emitted when new escrow is added for the given owner.
event EscrowAdd(address indexed owner,
uint256 amount_added);
// This event is emitted when a part of the escrow is taken away from the owner.
event EscrowTake(address indexed owner,
uint256 amount_taken);
// This event is emitted when the remaining escrow is released back to the owner.
event EscrowRelease(address indexed owner,
uint256 amount_returned);

struct StakeEscrowInfo {
uint256 amount; // Total stake, including inaccessible tokens
// that are in escrow.
uint256 escrowed; // sum_{a \in accounts} escrow_map[a].amount
// that are in escrow.
uint256 escrowed; // Tokens in escrow.

UintSet.Data escrows; // Set containing all the escrow account
// ids created by the stakeholder.

// ERC20 allowances. Unlike escrows, these permit an address to
// ERC20 allowances. Unlike escrow, these permit an address to
// transfer an amount without setting the tokens aside, so there
// is no guarantee that the allowance amount would actually be
// available when the entity with the allowance needs it.
mapping(address => uint) allowances;
}

// Currently only the target may close the escrow account,
// claiming/taking an amount that is at most the amount deposited
// into the escrow account.
struct EscrowAccount {
address owner;
address target;
uint256 amount;
bytes32 aux; // what the escrow account is used for?
}

// ERC20 public variables; set once, at constract instantiation.
string public name;
string public symbol;
uint8 public constant decimals = 18;
uint256 public totalSupply;

StakeEscrowInfo[] accounts; // zero index is never used (mapping default)
EscrowAccount[] escrows; // zero index is never used

// Mapping `stakes` maps Ethereum address to index in `accounts` array.
mapping(address => uint) stakes;

constructor(uint256 _initial_supply, string _tokenName, string _tokenSymbol) public {
// Hardcoded addresses for the Consensus and Registry contracts.
// Only the Consensus can call `takeEscrow` and only the Registry can call
// `releaseEscrow`, so we need to have their addresses to check that.
address consensus_contract_addr;
address registry_contract_addr;

constructor(uint256 _initial_supply, string _tokenName, string _tokenSymbol, address _consensus_contract_addr, address _registry_contract_addr) public {
StakeEscrowInfo memory dummy_stake;
accounts.push(dummy_stake); // fill in zero index
EscrowAccount memory dummy_escrow;
escrows.push(dummy_escrow); // fill in zero index
totalSupply = _initial_supply * 10 ** uint256(decimals);
_depositStake(msg.sender, totalSupply);
name = _tokenName;
symbol = _tokenSymbol;
consensus_contract_addr = _consensus_contract_addr;
registry_contract_addr = _registry_contract_addr;
}

function() public {
Expand All @@ -86,7 +80,6 @@ contract Stake is ERC20Interface {
ix_ = accounts.length;
stakes[_addr] = ix_;
accounts.push(entry);
accounts[ix_].escrows.init(); // init must be called when in storage
}

function _depositStake(address _owner, uint256 _additional_stake) private {
Expand Down Expand Up @@ -281,116 +274,77 @@ contract Stake is ERC20Interface {
success_ = true;
}

function allocateEscrow(address _target, uint256 _amount, bytes32 _aux) external
returns (uint escrow_id_) {
escrow_id_ = _allocateEscrow(msg.sender, _target, _amount, _aux);
emit EscrowCreate(escrow_id_, msg.sender, _target, _amount, _aux);
function addEscrow(uint256 _amount) external returns (uint256 total_escrow_so_far_) {
total_escrow_so_far_ = _addEscrow(msg.sender, _amount);
emit EscrowCreate(msg.sender, _amount);
}

function _allocateEscrow(address _owner, address _target, uint256 _amount, bytes32 _aux)
private returns (uint escrow_id_) {
function _addEscrow(address _owner, uint256 _amount) private returns (uint256 total_escrow_so_far_) {
uint owner_ix = stakes[_owner];
require (owner_ix != 0);
// NoStakeAccount
require (_amount <= accounts[owner_ix].amount - accounts[owner_ix].escrowed);
// InsufficientFunds

accounts[owner_ix].escrowed += _amount;
EscrowAccount memory ea;
ea.owner = _owner;
ea.target = _target;
ea.amount = _amount;
ea.aux = _aux;
escrow_id_ = escrows.length;
escrows.push(ea); // copies to storage
accounts[owner_ix].escrows.addEntry(escrow_id_);

total_escrow_so_far_ = accounts[owner_ix].escrowed;
}

// The information is publicly available in the blockchain, so we
// might as well allow the public to get the information via an API
// call, instead of reconstructing it from the blockchain.
function listActiveEscrowsIterator(address owner) external view
returns (bool has_next_, uint state_) {
uint owner_ix = stakes[owner];
if (owner_ix == 0) {
has_next_ = false;
} else {
has_next_ = accounts[owner_ix].escrows.size() != 0;
}
state_ = 0;
}

function listActiveEscrowsGet(address _owner, uint _state) external view
returns (uint id_, address target_, uint256 amount_, bytes32 aux_,
bool has_next_, uint next_state_) {
function fetchEscrowAmount(address _owner) external view returns (uint256 total_escrow_so_far_) {
uint owner_ix = stakes[_owner];
require(owner_ix != 0);
require(_state < accounts[owner_ix].escrows.size());
uint escrow_ix = accounts[owner_ix].escrows.get(_state);
assert(escrow_ix != 0);
assert(escrow_ix < escrows.length);
EscrowAccount memory ea = escrows[escrow_ix];
assert(ea.owner == _owner);

id_ = escrow_ix;
target_ = ea.target;
amount_ = ea.amount;
aux_ = ea.aux;

next_state_ = _state + 1;
has_next_ = next_state_ < accounts[owner_ix].escrows.size();
}

function fetchEscrowById(uint _escrow_id) external view
returns (address owner_, address target_, uint256 amount_, bytes32 aux_) {
require(_escrow_id != 0);
EscrowAccount memory ea = escrows[_escrow_id]; // copy to memory
require(ea.owner != 0x0); // deleted escrow account will have zero address
owner_ = ea.owner; // out
target_ = ea.target; // out
amount_ = ea.amount; // out
aux_ = ea.aux;
total_escrow_so_far_ = accounts[owner_ix].escrowed;
}

function takeAndReleaseEscrow(uint _escrow_id, uint256 _amount_requested) external {
require(_escrow_id != 0);
EscrowAccount memory ea = escrows[_escrow_id];
require(ea.owner != 0x0); // deleted escrow account will have zero address
address owner = ea.owner; // NoEscrowAccount if previously deleted
uint owner_ix = stakes[owner];
require(owner_ix != 0); // NoStakeAccount
require(_amount_requested <= ea.amount); // RequestExceedsEscrowedFunds
uint amount_to_return = ea.amount - _amount_requested;
require(msg.sender == ea.target); // CallerNotEscrowTarget

uint sender_ix = stakes[msg.sender];
if (sender_ix == 0) {
sender_ix = _addNewStakeEscrowInfo(msg.sender, 0, 0);
function takeEscrow(address _owner, uint256 _amount_requested) external returns (uint256 amount_taken_) {
// Only the Consensus contract may take escrow.
require(msg.sender == consensus_contract_addr);

// Set up a stake account for the Consensus if it doesn't have one yet.
uint consensus_ix = stakes[msg.sender];
if (consensus_ix == 0) {
consensus_ix = _addNewStakeEscrowInfo(msg.sender, 0, 0);
}

// check some invariants
require(ea.amount <= accounts[owner_ix].escrowed);
uint owner_ix = stakes[_owner];
require(owner_ix != 0);
require(_amount_requested <= accounts[owner_ix].escrowed); // RequestExceedsEscrowedFunds

// Check invariants.
require(accounts[owner_ix].escrowed <= accounts[owner_ix].amount);
require(accounts[consensus_ix].amount <= AMOUNT_MAX - _amount_requested); // WouldOverflow

// require(amount_requested <= accounts[owner_ix].amount );
// implies by
// _amount_requested <= ea.amount
// <= accounts[owner_ix].escrowed
// <= accounts[owner_ix].amount
// Take the escrowed stake from the owner's account and deposit it
// in the Consensus contract's account.
accounts[owner_ix].amount -= _amount_requested;
accounts[owner_ix].escrowed -= _amount_requested;
accounts[consensus_ix].amount += _amount_requested;

require(accounts[sender_ix].amount <= AMOUNT_MAX - _amount_requested);
// WouldOverflow
amount_taken_ = _amount_requested;
emit EscrowTake(_owner, _amount_requested);
}

accounts[owner_ix].amount -= _amount_requested;
accounts[owner_ix].escrowed -= ea.amount;
accounts[owner_ix].escrows.removeEntry(_escrow_id);
accounts[sender_ix].amount += _amount_requested;

// delete escrows[_escrow_id];
escrows[_escrow_id].owner = 0x0;
escrows[_escrow_id].target = 0x0;
escrows[_escrow_id].amount = 0;
emit EscrowClose(_escrow_id, ea.aux, owner, amount_to_return, msg.sender, _amount_requested);
function releaseEscrow(address _owner) external returns (uint256 amount_returned_) {
// Only the Registry contract may release escrow.
require(msg.sender == registry_contract_addr);

uint owner_ix = stakes[_owner];
require(owner_ix != 0);

// Check invariants.
require(accounts[owner_ix].escrowed <= accounts[owner_ix].amount);

// The remainder of the escrow is released back to the owner.
uint256 amount_returned = accounts[owner_ix].escrowed;
accounts[owner_ix].escrowed = 0;

amount_returned_ = amount_returned;
emit EscrowRelease(_owner, amount_returned);
}
}

10 changes: 2 additions & 8 deletions ethereum/migrations/2_deploy_contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ var MockEpoch = artifacts.require("./MockEpoch.sol");
var OasisEpoch = artifacts.require("./OasisEpoch.sol");
var ContractRegistry = artifacts.require("./ContractRegistry.sol")
var EntityRegistry = artifacts.require("./EntityRegistry.sol");
var UintSet = artifacts.require("./UintSet.sol");
var Stake = artifacts.require("./Stake.sol");
var DisputeResolution = artifacts.require("./DisputeResolution");

Expand All @@ -16,9 +15,7 @@ const deploy = async function (deployer, network) {
// The tests only use the standard epoch timesource anyway.
await deployer.deploy([OasisEpoch, MockEpoch]);
await deployer.deploy(RandomBeacon, OasisEpoch.address);
await deployer.deploy(UintSet);
await deployer.link(UintSet, Stake);
await deployer.deploy(Stake, 1, "EkidenStake", "E$");
await deployer.deploy(Stake, 1, "EkidenStake", "E$", /* insert Consensus.address here */, /* insert EntityRegistry.address here */);
await deployer.deploy(ContractRegistry, OasisEpoch.address);
await deployer.deploy(EntityRegistry, OasisEpoch.address, Stake.address);
await deployer.deploy(DisputeResolution);
Expand All @@ -29,9 +26,7 @@ const deploy = async function (deployer, network) {
await deployer.deploy([OasisEpoch, MockEpoch]);

// Stake
await deployer.deploy(UintSet);
await deployer.link(UintSet, Stake);
await deployer.deploy(Stake, 1000000000, "EkidenStake", "E$");
await deployer.deploy(Stake, 1000000000, "EkidenStake", "E$", /* insert Consensus.address here */, /* insert EntityRegistry.address here */);

let instance = await deployer.deploy(ContractDeployer, OasisEpoch.address, MockEpoch.address, Stake.address);
let instance_addrs = await Promise.all([
Expand All @@ -44,7 +39,6 @@ const deploy = async function (deployer, network) {
]);
await deployer.deploy(DisputeResolution);


// Pass all the contract addresses to truffle_deploy in the rust
// side as a simple JSON formatted dictionary.
let addrs = {
Expand Down
Loading

0 comments on commit 31d686f

Please sign in to comment.