Commit e7205a06 authored by didi's avatar didi
Browse files

* Funds of incoming streams can now be used before the stream is closed

* Stream objects nomore stored duplicated
* Refactoring with focus on readability
refs #2
parent 944ab64d
// TODO: use the SafeMath lib of openzeppelin (implicit overflow checks etc)?
// Status of this contract: PoC of a basic ERC-20 token with streaming functionality (1 outgoing/incoming stream per account)
pragma solidity ^0.4.11;
pragma solidity ^0.4.13;
// Status of this contract: PoC of a basic ERC-20 token with streaming functionality (1 outgoing/incoming stream per account)
// TODO: decide on vocabularity for start/open, stop/close, dry/run out of funds/underwater (distinguish between low and no current funding)
contract Streem {
uint256 public totalSupply;
......@@ -25,19 +25,25 @@ contract Streem {
uint256 startTimestamp;
}
// TODO: map to array of Streams (support more than 1 per account)
mapping (address => Stream) outStreams;
mapping (address => Stream) inStreams;
/*
* In order to avoid duplicated storage of Stream objects (for mappings from sender and receiver),
* a construct with manual pointer (uint) is used.
* See https://ethereum.stackexchange.com/questions/23274/storing-the-same-struct-in-two-mappings
*/
mapping(address => uint) outStreamPtrs;
mapping(address => uint) inStreamPtrs;
Stream[] streams;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event StreamOpened(address indexed _from, address indexed _to, uint256 _perSecond);
event StreamClosed(address indexed _from, address indexed _to, uint256 _perSecond, uint256 _settledBalance, uint256 _outstandingBalance);
// constructor
function Streem(uint initialSupply) {
owner = msg.sender;
settledBalances[msg.sender] = initialSupply;
totalSupply = initialSupply;
streams.push(Stream(0,0,0,0)); // empty first element for implicit null-like semantics
}
// TODO: this is just for the test token. Issuance mechanism for mainnet token to be decided.
......@@ -47,54 +53,74 @@ contract Streem {
totalSupply += amount;
}
// TODO: return value?
/*
* opens a stream from the transaction sender to the given receiver with the given speed.
* Will succeed only if the sender has no stream open and if it has funds for at least one second
*/
function openStream(address receiver, uint256 perSecond) {
// TODO: right now it will just overwrite the config of a previous stream if any.
assert(balanceOf(msg.sender) > 0);
assert(! exists(getOutStreamOf(msg.sender)));
assert(balanceOf(msg.sender) > perSecond);
// now is an alias to block.timestamp. See http://solidity.readthedocs.io/en/develop/units-and-global-variables.html?highlight=blocknumber
var s = Stream(msg.sender, receiver, perSecond, now);
outStreams[msg.sender] = s;
inStreams[receiver] = s;
var streamId = streams.push(Stream(msg.sender, receiver, perSecond, now)) - 1; // id = array_length - 1
outStreamPtrs[msg.sender] = streamId;
inStreamPtrs[receiver] = streamId;
StreamOpened(msg.sender, receiver, perSecond);
}
// settle and close
// returns the settled balance and the outstanding balance (> 0 if underfunded)
function settleStream(Stream s) internal returns (uint, uint) {
var bal = streamBalance(s);
uint dt = uint(bal / s.perSecond);
// since we don't allow fractional seconds, the possible settleBalance may be lower than the actual streamBalance (TODO: sure?)
var settleBal = dt * s.perSecond;
var naiveBal = naiveStreamBalance(s); // remember before manipulating the stream
settledBalances[s.sender] -= settleBal;
settledBalances[s.receiver] += settleBal; // inS.receiver == msg.sender
// TODO: make sure we really don't need an extra field for this intermediate settlement.
// For correct behaviour, it's irrelevant what the start time of the stream is.
// Applications can rely on the StreamOpened-Event for the UI.
// Still, the field may need a name better reflecting this flexible use.
s.startTimestamp += dt;
// TODO: disable checks in prod if they cost gas and the logic is proofed
assert(s.startTimestamp <= now);
assert(settleBal <= naiveBal);
return (settleBal, naiveBal - settleBal);
}
// settle and close the outgoing stream
function closeStream() {
var stream = outStreams[msg.sender];
// fail if no stream is open. TODO: support more than 1 per pair
assert(stream.startTimestamp != 0);
uint256 streamBal = (now - stream.startTimestamp) * stream.perSecond;
uint256 settleBal = 0;
uint256 outstandingBal = 0;
if(streamBal <= settledBalances[msg.sender]) {
settleBal = streamBal;
} else {
// special case: the receiver (partially) defaults on the stream
settleBal = settledBalances[msg.sender];
outstandingBal = streamBal - settleBal;
}
settledBalances[msg.sender] -= settleBal;
settledBalances[stream.receiver] += settleBal;
var outS = getOutStreamOf(msg.sender);
assert(exists(outS));
var (settleBal, outstandingBal) = settleStream(outS);
delete inStreams[stream.receiver];
delete outStreams[msg.sender];
StreamClosed(msg.sender, outS.receiver, outS.perSecond, settleBal, outstandingBal);
StreamClosed(msg.sender, stream.receiver, stream.perSecond, settleBal, outstandingBal);
// make sure this remains the last statement because the stream handle is a reference
deleteOutStreamOf(msg.sender);
}
// TODO: needs to consider the dynamic balance (streams included). How to deal with _value > staticBalance? Allow negative staticBalance?
// ERC-20 compliant function for discrete transfers
function transfer(address _to, uint256 _value) {
assert(_value > 0 && balanceOf(msg.sender) >= _value);
// As a temporary solution, allow only transfers for which we have enough funds already "settled". TODO: fix
assert(settledBalances[msg.sender] >= _value);
// if the settled balance doesn't suffice, settle the available funds of the ingoing stream.
if(settledBalances[msg.sender] < _value) {
var inS = getInStreamOf(msg.sender);
settleStream(inS);
// lets check again! TODO: once the logic was validated / proofed, this checks should be superfluous
assert(balanceOf(msg.sender) >= _value);
}
settledBalances[msg.sender] -= _value;
settledBalances[_to] += _value;
Transfer(msg.sender, _to, _value);
}
// Solidity (so far) has no simple null check, using startTimestamp as guard (assuming 1970 will not return).
// Solidity (so far) has no simple null check, using startTimestamp as guard (assuming 1970 will not come back).
function exists(Stream s) internal constant returns (bool) {
return s.startTimestamp != 0;
}
......@@ -104,7 +130,22 @@ contract Streem {
return a < b ? a : b;
}
// returns the balance of a stream, ignoring the possibility of it running out of funds
// returns a reference (!) to the outgoing stream of the given address. Caller needs to check existence on the return value.
function getOutStreamOf(address addr) internal constant returns (Stream storage) {
return streams[outStreamPtrs[addr]];
}
function getInStreamOf(address addr) internal constant returns (Stream storage) {
return streams[inStreamPtrs[addr]];
}
// as long as only senders can close a stream, this is only needed for outstreams
function deleteOutStreamOf(address addr) internal {
var sid = outStreamPtrs[addr];
delete streams[sid];
}
// returns the naive "should be" balance of a stream, ignoring the possibility of it running out of funds
function naiveStreamBalance(Stream s) internal constant returns (uint256) {
return (now - s.startTimestamp) * s.perSecond;
}
......@@ -113,13 +154,13 @@ contract Streem {
* returns the "real" (based on sender solvency) balance of a stream.
* This takes the perspective of the sender, making the stream under investigation an outgoingStream.
* Implements min(outgoingStreamBalance, staticBalance + incomingStreamBalance)
* TODO: due to the possible recursion, this will lead to an endless loop in circular relations, e.g. A -> B, B -> A
* TODO: due to the involved recursion, this will lead to an endless loop in circular relations, e.g. A -> B, B -> A
*/
function streamBalance(Stream s) internal constant returns (uint256) {
// naming: osb -> outgoingStreamBalance, isb -> incomingStreamBalance, sb -> static balance
uint256 osb = naiveStreamBalance(s);
var inS = inStreams[s.sender];
var inS = getInStreamOf(s.sender);
uint256 isb = exists(inS) ? streamBalance(inS) : 0;
uint sb = settledBalances[s.sender];
......@@ -131,36 +172,36 @@ contract Streem {
// and a higher than real balance if an incoming stream went "under water".
// note that this is NOT the actual balance, just a theoretical value
// TODO: refactor
function owedBalanceOf(address _owner) constant returns (int256 balance) {
uint256 inStreamBal = 0;
var inStream = inStreams[_owner];
// no prettier null check possible? https://ethereum.stackexchange.com/questions/871/what-is-the-zero-empty-or-null-value-of-a-struct
if(inStream.startTimestamp != 0) {
inStreamBal = (now - inStream.startTimestamp) * inStream.perSecond;
}
// function owedBalanceOf(address _owner) constant returns (int256 balance) {
// uint256 inStreamBal = 0;
// var inStream = inStreams[_owner];
// // no prettier null check possible? https://ethereum.stackexchange.com/questions/871/what-is-the-zero-empty-or-null-value-of-a-struct
// if(inStream.startTimestamp != 0) {
// inStreamBal = (now - inStream.startTimestamp) * inStream.perSecond;
// }
//
// uint256 outStreamBal = 0;
// var outStream = outStreams[_owner];
// if(outStream.startTimestamp != 0) {
// outStreamBal = (now - outStream.startTimestamp) * outStream.perSecond;
// }
//
// // TODO: check overflow before casting
// balance = int256(settledBalances[_owner] + inStreamBal - outStreamBal);
// return balance;
// }
uint256 outStreamBal = 0;
var outStream = outStreams[_owner];
if(outStream.startTimestamp != 0) {
outStreamBal = (now - outStream.startTimestamp) * outStream.perSecond;
}
// TODO: check overflow before casting
balance = int256(settledBalances[_owner] + inStreamBal - outStreamBal);
return balance;
}
// the ERC-20 standard requires an uint return value
// TODO: remove duplication. Maybe call honestBalance instead and take min(0, bal)
function balanceOf(address _owner) constant returns (uint256) {
var inS = inStreams[_owner];
var inS = getInStreamOf(_owner);
// no prettier null check possible? https://ethereum.stackexchange.com/questions/871/what-is-the-zero-empty-or-null-value-of-a-struct
uint256 inStreamBal = exists(inS) ? streamBalance(inS) : 0;
var outS = outStreams[_owner];
var outS = getOutStreamOf(_owner);
uint256 outStreamBal = exists(outS) ? streamBalance(outS) : 0;
// TODO: check overflow before casting
return settledBalances[_owner] + inStreamBal - outStreamBal;
}
// TODO: implement the empty function?
}
......@@ -48,17 +48,25 @@ const contract = {
},
{
"constant": true,
"inputs": [
"inputs": [],
"name": "getOutStreamTimestamp",
"outputs": [
{
"name": "_owner",
"type": "address"
"name": "",
"type": "uint256"
}
],
"name": "owedBalanceOf",
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "balance",
"type": "int256"
"name": "",
"type": "uint8"
}
],
"payable": false,
......@@ -67,11 +75,11 @@ const contract = {
{
"constant": true,
"inputs": [],
"name": "decimals",
"name": "getInStreamTimestamp",
"outputs": [
{
"name": "",
"type": "uint8"
"type": "uint256"
}
],
"payable": false,
......@@ -134,6 +142,14 @@ const contract = {
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "touchOutStream",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
......@@ -259,7 +275,7 @@ const contract = {
"type": "event"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b604051602080610ac683398101604052515b60018054600160a060020a03191633600160a060020a031690811790915560009081526002602052604081208290558190555b505b610a64806100626000396000f300606060405236156100935763ffffffff60e060020a60003504166306fdde0381146100955780631207f0c11461012557806318160ddd1461014657806326a90b7d14610168578063313ce5671461019657806364a80c0c146101bc57806370a08231146101dd5780637ae2b5c71461020b57806395d89b41146102335780639dad9382146102c3578063a9059cbb146102d5575bfe5b341561009d57fe5b6100a56102f6565b6040805160208082528351818301528351919283929083019185019080838382156100eb575b8051825260208311156100eb57601f1990920191602091820191016100cb565b505050905090810190601f1680156101175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561012d57fe5b610144600160a060020a0360043516602435610319565b005b341561014e57fe5b61015661035e565b60408051918252519081900360200190f35b341561017057fe5b610156600160a060020a0360043516610364565b60408051918252519081900360200190f35b341561019e57fe5b6101a66103fc565b6040805160ff9092168252519081900360200190f35b34156101c457fe5b610144600160a060020a0360043516602435610401565b005b34156101e557fe5b610156600160a060020a0360043516610512565b60408051918252519081900360200190f35b341561021357fe5b610156600435602435610696565b60408051918252519081900360200190f35b341561023b57fe5b6100a56106b0565b6040805160208082528351818301528351919283929083019185019080838382156100eb575b8051825260208311156100eb57601f1990920191602091820191016100cb565b505050905090810190601f1680156101175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156102cb57fe5b6101446106d0565b005b34156102dd57fe5b610144600160a060020a0360043516602435610848565b005b604080518082019091526006815260d060020a6553747265656d02602082015281565b60015433600160a060020a039081169116146103355760006000fd5b600160a060020a03821660009081526002602052604081208054830190558054820190555b5050565b60005481565b600160a060020a03811660009081526004602052604081206003810154829190829081901561039d578260020154836003015442030293505b5050600160a060020a0384166000908152600360208190526040822090810154156103d2578060020154816003015442030291505b600160a060020a038616600090815260026020526040902054840182900394505b50505050919050565b600081565b610409610a11565b600061041433610512565b1161041b57fe5b5060408051608081018252600160a060020a0333811680835285821660208085018281528587018881524260608801908152600086815260038086528a82208a518154908b16600160a060020a0319918216178255865160018084018054928e1692841692909217909155865160028085019190915586519385019390935589855260048952938d90208c518154908d169083161781559651938701805494909b1693169290921790985591519183019190915551940193909355845186815294519394909391927f4baaa557c21346b70bdc9482890b5d7e315f6a2123611e74004857ebecde068692918290030190a35b505050565b600160a060020a038082166000908152600460209081526040808320815160808101835281548616815260018201549095169285019290925260028201549084015260038101546060840152909182908190819061056f906108f7565b61057a5760006105bd565b604080516080810182528554600160a060020a0390811682526001870154166020820152600286015491810191909152600385015460608201526105bd90610904565b5b600160a060020a038088166000908152600360208181526040928390208351608081018552815486168152600182015490951691850191909152600281015492840192909252810154606083015291945090925061061b906108f7565b610626576000610669565b604080516080810182528354600160a060020a03908116825260018501541660208201526002840154918101919091526003830154606082015261066990610904565b5b600160a060020a0387166000908152600260205260409020548401819003955090505b50505050919050565b60008183106106a557816106a7565b825b90505b92915050565b604080518082019091526003815260e960020a6229aa2902602082015281565b600160a060020a03331660009081526003602081905260408220908101549091908190819015156106fd57fe5b83600201548460030154420302925060009150600090506002600033600160a060020a0316600160a060020a03168152602001908152602001600020548311151561074a57829150610769565b5050600160a060020a0333166000908152600260205260409020548082035b600160a060020a0333811660008181526002602081815260408084208054899003905560018a810180548816865282862080548b019055805488168652600484528286208054600160a060020a031990811682558184018054821690558187018890556003918201889055888852818652848820805482168155938401805490911690558286018790559190910194909455925491890154835190815290810187905280830186905291519316927f96c5271ec05cb2683bdc50cf109341f5a4e45b02907df1c23a8855bddbe030a19181900360600190a35b50505050565b60008111801561086057508061085d33610512565b10155b151561086857fe5b600160a060020a0333166000908152600260205260409020548190101561088b57fe5b600160a060020a03338116600081815260026020908152604080832080548790039055938616808352918490208054860190558351858152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35b5050565b606081015115155b919050565b60006000600060006000610917866109fe565b8651600160a060020a03908116600090815260046020908152604091829020825160808101845281548516815260018201549094169184019190915260028101549183019190915260038101546060830152919550909350610978906108f7565b6109835760006109c6565b604080516080810182528454600160a060020a0390811682526001860154166020820152600285015491810191909152600384015460608201526109c690610904565b5b8651600160a060020a031660009081526002602052604090205490925090506109f284828401610696565b94505b50505050919050565b604081015160608201514203025b919050565b604080516080810182526000808252602082018190529181018290526060810191909152905600a165627a7a72305820895b2b4a5129e2edc484c0d1034d3d1402a6c5e9fbb0f6b26e8c0d5412e19d210029",
"unlinked_binary": "0x60606040526000600655341561001157fe5b604051602080610a1c83398101604052515b60018054600160a060020a03191633600160a060020a031690811790915560009081526002602052604081208290558190555b505b6109b5806100676000396000f300606060405236156100a95763ffffffff60e060020a60003504166306fdde0381146100ab5780631207f0c11461013b57806318160ddd1461015c5780632db2cbf81461017e578063313ce567146101a05780634e04fd88146101c657806364a80c0c146101e857806370a08231146102095780637ae2b5c714610237578063840749b61461025f57806395d89b41146102715780639dad938214610301578063a9059cbb14610313575bfe5b34156100b357fe5b6100bb610334565b604080516020808252835181830152835191928392908301918501908083838215610101575b80518252602083111561010157601f1990920191602091820191016100e1565b505050905090810190601f16801561012d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561014357fe5b61015a600160a060020a0360043516602435610357565b005b341561016457fe5b61016c61039c565b60408051918252519081900360200190f35b341561018657fe5b61016c6103a2565b60408051918252519081900360200190f35b34156101a857fe5b6101b06103b7565b6040805160ff9092168252519081900360200190f35b34156101ce57fe5b61016c6103bc565b60408051918252519081900360200190f35b34156101f057fe5b61015a600160a060020a03600435166024356103dd565b005b341561021157fe5b61016c600160a060020a03600435166104fb565b60408051918252519081900360200190f35b341561023f57fe5b61016c600435602435610591565b60408051918252519081900360200190f35b341561026757fe5b61015a6105ab565b005b341561027957fe5b6100bb6105bd565b604080516020808252835181830152835191928392908301918501908083838215610101575b80518252602083111561010157601f1990920191602091820191016100e1565b505050905090810190601f16801561012d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561030957fe5b61015a6105dd565b005b341561031b57fe5b61015a600160a060020a036004351660243561070a565b005b604080518082019091526006815260d060020a6553747265656d02602082015281565b60015433600160a060020a039081169116146103735760006000fd5b600160a060020a03821660009081526002602052604081208054830190558054820190555b5050565b60005481565b60006103ad336107b9565b6060015190505b90565b600081565b60006103ad6103ca336107b9565b6020015161083e565b6060015190505b90565b60006103e8336104fb565b116103ef57fe5b60806040519081016040528033600160a060020a0316815260200183600160a060020a0316815260200182815260200142815250600560065481548110151561043457fe5b906000526020600020906004020160005b5081518154600160a060020a0319908116600160a060020a0392831617835560208085015160018086018054909416918516919091179092556040808601516002860155606090950151600394850155600680543385166000818152968452878720829055948916808752600484529587902081905590920190915583518581529351929391927f4baaa557c21346b70bdc9482890b5d7e315f6a2123611e74004857ebecde06869281900390910190a35b5050565b6000610505610962565b600061050f610962565b600061051a8661083e565b9350610525846108c3565b610530576000610539565b610539846108d0565b5b9250610545866107b9565b9150610550826108c3565b61055b576000610564565b610564826108d0565b5b600160a060020a0387166000908152600260205260409020548401819003955090505b50505050919050565b60008183106105a057816105a2565b825b90505b92915050565b60636105b6336107b9565b606001525b565b604080518082019091526003815260e960020a6229aa2902602082015281565b6105e5610962565b6000600060006105f4336107b9565b6060810151909450151561060457fe5b5050506040808201516060830151600160a060020a0333166000908152600260205292832054429190910390910291908190831161064457829150610663565b5050600160a060020a0333166000908152600260205260409020548082035b600160a060020a033381166000908152600260209081526040808320805487900390559087015190921681522080548301905561069e610962565b93508360200151600160a060020a031633600160a060020a03167f96c5271ec05cb2683bdc50cf109341f5a4e45b02907df1c23a8855bddbe030a18660400151858560405180848152602001838152602001828152602001935050505060405180910390a35b50505050565b60008111801561072257508061071f336104fb565b10155b151561072a57fe5b600160a060020a0333166000908152600260205260409020548190101561074d57fe5b600160a060020a03338116600081815260026020908152604080832080548790039055938616808352918490208054860190558351858152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35b5050565b6107c1610962565b600160a060020a0382166000908152600360205260409020546005805490919081106107e957fe5b906000526020600020906004020160005b50604080516080810182528254600160a060020a0390811682526001840154166020820152600283015491810191909152600390910154606082015290505b919050565b610846610962565b600160a060020a0382166000908152600460205260409020546005805490919081106107e957fe5b906000526020600020906004020160005b50604080516080810182528254600160a060020a0390811682526001840154166020820152600283015491810191909152600390910154606082015290505b919050565b606081015115155b919050565b600060006108dc610962565b600060006108e98661094f565b93506108f8866000015161083e565b9250610903836108c3565b61090e576000610917565b610917836108d0565b5b8651600160a060020a0316600090815260026020526040902054909250905061094384828401610591565b94505b50505050919050565b604081015160608201514203025b919050565b604080516080810182526000808252602082018190529181018290526060810191909152905600a165627a7a72305820f73cb99c62e4cac93cff6d3023c6d323a37bbcfc302ca5f675ee99cfa638b32d0029",
"networks": {
"1500075197859": {
"events": {
......@@ -763,5 +779,5 @@ const contract = {
}
},
"schema_version": "0.0.5",
"updated_at": 1501346911351
"updated_at": 1501348572226
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment