Commit 4dcaff27 authored by didi's avatar didi
Browse files

balanceOf now returns correct results if the sender ran out of funds,...

balanceOf now returns correct results if the sender ran out of funds, refactoring, added Readme with textual description of challenges and options
parent 5d4c27fc
# How to run
Needs truffle installed: `npm install -g truffle`
Start testrpc: `truffle-testrpc`
Then, in another tab run `./contract-changed.sh` whenever the contract should be re-compiled JS bindings updated.
TODO: Add watcher option.
# The dynamic challenge
When checking the balance of a receiving address or closing an incoming stream, it's not enough to just look at the static balance of the sender.
**Sender has incoming stream(s)**: In this case looking at only staticBalance may return a too low result and lead to unnecessary drying of the stream.
**Sender has outgoing stream(s)**: In this case looking at only staticBalance may return a too high result, leading to a drying stream not being noticed.
Also, when doing a discrete transfer, it may now happen that the staticBalance is smaller than balance. Thus the amount can't just be subtracted from staticBalance.
Can we make staticBalance an int or would that introduce other issues?
## Options
### Only one stream per account
In this case an account could have only either an outgoing or an incoming stream.
Thus a receiver could rely on the sender not having any other stream, thus looking at staticBalance alone would be enough.
### Max one incoming and one outgoing stream
In this case a receiver could rely on the sender not having any other stream draining staticBalance.
Still she'd need for account for a potential parallel incoming stream.
Problem: there could be a circular relationship. E.g. A streams to B and B streams to A.
How to implement this without the potential for an endless recursion?
a) Avoid creation of such a constellation (e.g. have *openStream()* check it)
b) Find a way to implement it safely (should be possible, but I don't yet have an idea how)
Either way, we still need to protect from the risk of running out of gas due to many dependencies.
A possible solution could be a kind of maintenance cronjob which regularly creates kind of snapshots which cut down the dependencies on other streams.
That could be achieved by some kind of transient staticBalance reflecting a snapshot (what if all streams were closed at this point in time).
Such a value would increase the probability of receivers not needing to check incoming streams of the sender in case the transient staticBalance minus outgoing streams remained above the required threshold.
However for calculating the remaining runway for a stream, this may not help (?) - (does the gas limit apply for local execution?)
### Limited number of streams per account
This could probably be implemented with arrays.
Would need a clear strategy (or multiple) for how to deal with dry streams - similar to how insolvencies are dealt with.
Basic strategies are:
* first come first serve (e.g. older streams are served first)
* proportional: needs to know the point in time starting from which streams are underfunded / dry
* priority classes: could be combined with either the first come first serve or proportional strategy
Guess: Allowing 2 incoming and 2 outgoing streams would allow to construct all compositions possible with arbitrary number of streams by using *intermediate* accounts for aggregation or splitting.
### Arbitrary number of streams per account
Logically identical to the *limited number of streams* option.
But probably a considerably bigger implementation challenge in terms of complexity and gas cost. E.g. fixed size array nomore an option.
A possibility could be to have *openStream()* measure the complexity of the dependencies based on the involved addresses and not execute if it exceeds some safety threshold.
\ No newline at end of file
......@@ -3,6 +3,7 @@
pragma solidity ^0.4.11;
// 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;
string public constant name = "Streem";
......@@ -76,6 +77,7 @@ contract Streem {
StreamClosed(msg.sender, stream.receiver, stream.perSecond, settleBal, outstandingBal);
}
// TODO: needs to consider the dynamic balance (streams included). How to deal with _value > staticBalance? Allow negative staticBalance?
function transfer(address _to, uint256 _value) {
//Default assumes totalSupply can't be over max (2^256 - 1).
//If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
......@@ -87,9 +89,44 @@ contract Streem {
Transfer(msg.sender, _to, _value);
}
// this balance function can return a negative value if a stream went "under water"
// Solidity (so far) has no simple null check, using startTimestamp as guard (assuming 1970 will not return).
function exists(Stream s) internal constant returns (bool) {
return s.startTimestamp != 0;
}
// TODO: what's best practice for using uint vs uint256?
function min(uint a, uint b) constant returns (uint) {
return a < b ? a : b;
}
// returns the 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;
}
/*
* 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
*/
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];
uint256 isb = exists(inS) ? streamBalance(inS) : 0;
uint sb = staticBalances[s.sender];
return min(osb, sb + isb);
}
// this balance function can return a negative value if an outgoing stream went "under water"
// 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
function honestBalanceOf(address _owner) constant returns (int256 balance) {
// 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
......@@ -110,22 +147,15 @@ contract Streem {
// 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 balance) {
uint256 inStreamBal = 0;
var inStream = inStreams[_owner];
function balanceOf(address _owner) constant returns (uint256) {
var inS = 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 inStreamBal = exists(inS) ? streamBalance(inS) : 0;
uint256 outStreamBal = 0;
var outStream = outStreams[_owner];
if(outStream.startTimestamp != 0) {
outStreamBal = (now - outStream.startTimestamp) * outStream.perSecond;
}
var outS = outStreams[_owner];
uint256 outStreamBal = exists(inS) ? streamBalance(outS) : 0;
// TODO: check overflow before casting
balance = staticBalances[_owner] + inStreamBal - outStreamBal;
return balance;
return staticBalances[_owner] + inStreamBal - outStreamBal;
}
}
\ No newline at end of file
}
......@@ -46,6 +46,24 @@ const contract = {
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "owedBalanceOf",
"outputs": [
{
"name": "balance",
"type": "int256"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
......@@ -87,7 +105,29 @@ const contract = {
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "a",
"type": "uint256"
},
{
"name": "b",
"type": "uint256"
}
],
"name": "min",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
......@@ -132,24 +172,6 @@ const contract = {
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "honestBalanceOf",
"outputs": [
{
"name": "balance",
"type": "int256"
}
],
"payable": false,
"type": "function"
},
{
"inputs": [
{
......@@ -237,7 +259,7 @@ const contract = {
"type": "event"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b60405160208061086183398101604052515b60018054600160a060020a03191633600160a060020a031690811790915560009081526002602052604081208290558190555b505b6107ff806100626000396000f300606060405236156100885763ffffffff60e060020a60003504166306fdde03811461008a5780631207f0c11461011a57806318160ddd1461013b578063313ce5671461015d57806364a80c0c1461018357806370a08231146101a457806395d89b41146101d25780639dad938214610262578063a9059cbb14610274578063afb028bf146101a4575bfe5b341561009257fe5b61009a6102c3565b6040805160208082528351818301528351919283929083019185019080838382156100e0575b8051825260208311156100e057601f1990920191602091820191016100c0565b505050905090810190601f16801561010c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561012257fe5b610139600160a060020a03600435166024356102e6565b005b341561014357fe5b61014b61032b565b60408051918252519081900360200190f35b341561016557fe5b61016d610331565b6040805160ff9092168252519081900360200190f35b341561018b57fe5b610139600160a060020a0360043516602435610336565b005b34156101ac57fe5b61014b600160a060020a0360043516610447565b60408051918252519081900360200190f35b34156101da57fe5b61009a6104df565b6040805160208082528351818301528351919283929083019185019080838382156100e0575b8051825260208311156100e057601f1990920191602091820191016100c0565b505050905090810190601f16801561010c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561026a57fe5b6101396104ff565b005b341561027c57fe5b610139600160a060020a0360043516602435610677565b005b34156101ac57fe5b61014b600160a060020a0360043516610447565b60408051918252519081900360200190f35b604080518082019091526006815260d060020a6553747265656d02602082015281565b60015433600160a060020a039081169116146103025760006000fd5b600160a060020a03821660009081526002602052604081208054830190558054820190555b5050565b60005481565b600081565b61033e6107ac565b600061034933610447565b1161035057fe5b5060408051608081018252600160a060020a0333811680835285821660208085018281528587018881524260608801908152600086815260038086528a82208a518154908b16600160a060020a0319918216178255865160018084018054928e1692841692909217909155865160028085019190915586519385019390935589855260048952938d90208c518154908d169083161781559651938701805494909b1693169290921790985591519183019190915551940193909355845186815294519394909391927f4baaa557c21346b70bdc9482890b5d7e315f6a2123611e74004857ebecde068692918290030190a35b505050565b600160a060020a038116600090815260046020526040812060038101548291908290819015610480578260020154836003015442030293505b5050600160a060020a0384166000908152600360208190526040822090810154156104b5578060020154816003015442030291505b600160a060020a038616600090815260026020526040902054840182900394505b50505050919050565b604080518082019091526003815260e960020a6229aa2902602082015281565b600160a060020a033316600090815260036020819052604082209081015490919081908190151561052c57fe5b83600201548460030154420302925060009150600090506002600033600160a060020a0316600160a060020a03168152602001908152602001600020548311151561057957829150610598565b5050600160a060020a0333166000908152600260205260409020548082035b600160a060020a0333811660008181526002602081815260408084208054899003905560018a810180548816865282862080548b019055805488168652600484528286208054600160a060020a031990811682558184018054821690558187018890556003918201889055888852818652848820805482168155938401805490911690558286018790559190910194909455925491890154835190815290810187905280830186905291519316927f96c5271ec05cb2683bdc50cf109341f5a4e45b02907df1c23a8855bddbe030a19181900360600190a35b50505050565b600160a060020a0333166000908152600260205260409020548190108015906106a05750600081115b15156106a857fe5b600160a060020a03338116600081815260026020908152604080832080548790039055938616808352918490208054860190558351858152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35b5050565b600160a060020a038116600090815260046020526040812060038101548291908290819015610480578260020154836003015442030293505b5050600160a060020a0384166000908152600360208190526040822090810154156104b5578060020154816003015442030291505b600160a060020a038616600090815260026020526040902054840182900394505b50505050919050565b604080516080810182526000808252602082018190529181018290526060810191909152905600a165627a7a72305820e4e8748f3249ff28d2864433dc74aade406d84c8b20660086ee15de4eb3297430029",
"unlinked_binary": "0x6060604052341561000c57fe5b604051602080610ab483398101604052515b60018054600160a060020a03191633600160a060020a031690811790915560009081526002602052604081208290558190555b505b610a52806100626000396000f300606060405236156100935763ffffffff60e060020a60003504166306fdde0381146100955780631207f0c11461012557806318160ddd1461014657806326a90b7d14610168578063313ce5671461019657806364a80c0c146101bc57806370a08231146101dd5780637ae2b5c71461020b57806395d89b41146102335780639dad9382146102c3578063a9059cbb146102d5575bfe5b341561009d57fe5b6100a56102f6565b6040805160208082528351818301528351919283929083019185019080838382156100eb575b8051825260208311156100eb57601f1990920191602091820191016100cb565b505050905090810190601f1680156101175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561012d57fe5b610144600160a060020a0360043516602435610319565b005b341561014e57fe5b61015661035e565b60408051918252519081900360200190f35b341561017057fe5b610156600160a060020a0360043516610364565b60408051918252519081900360200190f35b341561019e57fe5b6101a66103fc565b6040805160ff9092168252519081900360200190f35b34156101c457fe5b610144600160a060020a0360043516602435610401565b005b34156101e557fe5b610156600160a060020a0360043516610512565b60408051918252519081900360200190f35b341561021357fe5b610156600435602435610696565b60408051918252519081900360200190f35b341561023b57fe5b6100a56106b0565b6040805160208082528351818301528351919283929083019185019080838382156100eb575b8051825260208311156100eb57601f1990920191602091820191016100cb565b505050905090810190601f1680156101175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156102cb57fe5b6101446106d0565b005b34156102dd57fe5b610144600160a060020a0360043516602435610848565b005b604080518082019091526006815260d060020a6553747265656d02602082015281565b60015433600160a060020a039081169116146103355760006000fd5b600160a060020a03821660009081526002602052604081208054830190558054820190555b5050565b60005481565b600160a060020a03811660009081526004602052604081206003810154829190829081901561039d578260020154836003015442030293505b5050600160a060020a0384166000908152600360208190526040822090810154156103d2578060020154816003015442030291505b600160a060020a038616600090815260026020526040902054840182900394505b50505050919050565b600081565b6104096109ff565b600061041433610512565b1161041b57fe5b5060408051608081018252600160a060020a0333811680835285821660208085018281528587018881524260608801908152600086815260038086528a82208a518154908b16600160a060020a0319918216178255865160018084018054928e1692841692909217909155865160028085019190915586519385019390935589855260048952938d90208c518154908d169083161781559651938701805494909b1693169290921790985591519183019190915551940193909355845186815294519394909391927f4baaa557c21346b70bdc9482890b5d7e315f6a2123611e74004857ebecde068692918290030190a35b505050565b600160a060020a038082166000908152600460209081526040808320815160808101835281548616815260018201549095169285019290925260028201549084015260038101546060840152909182908190819061056f906108e5565b61057a5760006105bd565b604080516080810182528554600160a060020a0390811682526001870154166020820152600286015491810191909152600385015460608201526105bd906108f2565b5b600160a060020a0380881660009081526003602081815260409283902083516080810185528a548616815260018b015490951691850191909152600289015492840192909252870154606083015291945090925061061b906108e5565b610626576000610669565b604080516080810182528354600160a060020a039081168252600185015416602082015260028401549181019190915260038301546060820152610669906108f2565b5b600160a060020a0387166000908152600260205260409020548401819003955090505b50505050919050565b60008183106106a557816106a7565b825b90505b92915050565b604080518082019091526003815260e960020a6229aa2902602082015281565b600160a060020a03331660009081526003602081905260408220908101549091908190819015156106fd57fe5b83600201548460030154420302925060009150600090506002600033600160a060020a0316600160a060020a03168152602001908152602001600020548311151561074a57829150610769565b5050600160a060020a0333166000908152600260205260409020548082035b600160a060020a0333811660008181526002602081815260408084208054899003905560018a810180548816865282862080548b019055805488168652600484528286208054600160a060020a031990811682558184018054821690558187018890556003918201889055888852818652848820805482168155938401805490911690558286018790559190910194909455925491890154835190815290810187905280830186905291519316927f96c5271ec05cb2683bdc50cf109341f5a4e45b02907df1c23a8855bddbe030a19181900360600190a35b50505050565b600160a060020a0333166000908152600260205260409020548190108015906108715750600081115b151561087957fe5b600160a060020a03338116600081815260026020908152604080832080548790039055938616808352918490208054860190558351858152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35b5050565b606081015115155b919050565b60006000600060006000610905866109ec565b8651600160a060020a03908116600090815260046020908152604091829020825160808101845281548516815260018201549094169184019190915260028101549183019190915260038101546060830152919550909350610966906108e5565b6109715760006109b4565b604080516080810182528454600160a060020a0390811682526001860154166020820152600285015491810191909152600384015460608201526109b4906108f2565b5b8651600160a060020a031660009081526002602052604090205490925090506109e084828401610696565b94505b50505050919050565b604081015160608201514203025b919050565b604080516080810182526000808252602082018190529181018290526060810191909152905600a165627a7a723058209925509c083502443d85e404030df8da00a0a32cdcfce52f3939f2dc3da262720029",
"networks": {
"1500075197859": {
"events": {
......@@ -655,8 +677,91 @@ const contract = {
"links": {},
"address": "0x4728135ad6078113537b473580b6d78448fe578d",
"updated_at": 1500332157181
},
"1501276036887": {
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
"0x4baaa557c21346b70bdc9482890b5d7e315f6a2123611e74004857ebecde0686": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_perSecond",
"type": "uint256"
}
],
"name": "StreamOpened",
"type": "event"
},
"0x96c5271ec05cb2683bdc50cf109341f5a4e45b02907df1c23a8855bddbe030a1": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_perSecond",
"type": "uint256"
},
{
"indexed": false,
"name": "_settledBalance",
"type": "uint256"
},
{
"indexed": false,
"name": "_outstandingBalance",
"type": "uint256"
}
],
"name": "StreamClosed",
"type": "event"
}
},
"links": {},
"address": "0x0a037b287e417f1bcf20a672c62a518406fdef65",
"updated_at": 1501276125961
}
},
"schema_version": "0.0.5",
"updated_at": 1500332893462
"updated_at": 1501285455112
}
\ 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