pragma solidity ^0.4.0; contract Asset { string public description; // e.g.: this Asset representes EUR string public id; // e.g.: EUR uint public decimalUnits; // for making this a Token Interface later(?) mapping (address => bool) accepted; function Asset(string _description, string _id, uint _decimalUnits) { description = _description; id = _id; decimalUnits = _decimalUnits; } // agree on this asset function accept() { accepted[msg.sender] = true; } function reject() { delete accepted[msg.sender]; } function isAcceptedBy(address a) public constant returns (bool) { return accepted[a]; } function () { throw; } } contract Ripple { // IOU - I owe you struct IOU { uint amountOwed; // current amount owed by the counterparty uint maxAllowed; // max. amount that I trust the counterparty will pay back } // XCHG - exchange offer struct XCHG { uint validUntil; uint exchangeRateInMillionth; // exchangeRate = exchangeRateInMillionth / 1000000 } // a generic asset, could be a currency, but it could be also worktime or anything else struct ASSET { // exchange offers (asset addr of Asset -> XCHG) mapping (address => XCHG) xchgs; // debitors mapping (address => IOU) ious; } // ACCOUNT struct RippleAccount { // addr of an Asset contract to ASSET struct mapping (address => ASSET) assets; } mapping (address => RippleAccount) accounts; event EventUpdateIOU(address lender, address debitor, address asset, uint newCurrent, uint newMax); event EventDeleteIOU(address lender, address debitor, address asset); event EventUpdateXCHG(address xchgAddr, Asset fromAsset, Asset toAsset, uint exchangeRateInMillionth, uint validUntil); event EventDeleteXCHG(address xchgAddr, Asset fromAsset, Asset toAsset); event EventExecuteXCHG(address xchgAddr, Asset fromAsset, Asset toAsset, uint exchangeRateInMillionth); // used to process offchain payments or add new limits function modifyIOU(address debitor, Asset asset, uint newAmountOwed, uint newMaxAllowed) { //assert(asset.isAcceptedBy(msg.sender) && asset.isAcceptedBy(debitor)); RippleAccount a = accounts[msg.sender]; IOU iou = a.assets[asset].ious[debitor]; // we can only reduce the amount owed (if someone pays back or if we feel too generous) if(newAmountOwed < iou.amountOwed) { iou.amountOwed = newAmountOwed; } // limits can be changed anytime iou.maxAllowed = newMaxAllowed; EventUpdateIOU(msg.sender, debitor, asset, iou.amountOwed, iou.maxAllowed); } function deleteIOU(address debitor, Asset asset) { assert(accounts[msg.sender].assets[asset].ious[debitor].amountOwed == 0); delete accounts[msg.sender].assets[asset].ious[debitor]; EventDeleteIOU(msg.sender, debitor, asset); } function modifyXCHG(Asset fromAsset, Asset toAsset, uint exchangeRateInMillionth, uint validUntil) { //assert(fromAsset.isAcceptedBy(msg.sender) && toAsset.isAcceptedBy(msg.sender)); RippleAccount a = accounts[msg.sender]; XCHG xchg = a.assets[fromAsset].xchgs[toAsset]; xchg.validUntil = validUntil; xchg.exchangeRateInMillionth = exchangeRateInMillionth; EventUpdateXCHG(msg.sender, fromAsset, toAsset, exchangeRateInMillionth, validUntil); } function deleteXCHG(Asset fromAsset, Asset toAsset) { delete accounts[msg.sender].assets[fromAsset].xchgs[toAsset]; EventDeleteXCHG(msg.sender, fromAsset, toAsset); } // @param chain - the chain depends on the web of trust and is calculated off-chain [this(sender), ..., receiver] // if correctly calculated the ious will be recalculated accordingly // @param assetFlow - the assets transfered between nodes in the chain // @param expectedExchangeRateInMillionth - expected exchange rates every time assets are exchanged function ripple(address[] chain, Asset[] assetFlow, uint[] expectedExchangeRateInMillionth, uint amount) { assert( chain.length >= 2 && chain[0] == msg.sender && amount > 0 && chain.length == assetFlow.length + 1); uint expectedExchangeRateInMillionthCurrentIdx = 0; for(uint i=1; i Asset transfered from prev to current // assetFlow[i] -> Asset transfered from current to next // money owed by current to previous IOU iouCurrentToPrev = prev.assets[assetFlow[i-1]].ious[chain[i]]; // money owed by previous to current IOU iouPrevToCurrent = current.assets[assetFlow[i-1]].ious[chain[i-1]]; // current owes money to previous -> settle if(iouCurrentToPrev.amountOwed > 0) { if(iouCurrentToPrev.amountOwed >= a) { iouCurrentToPrev.amountOwed -= a; a = 0; } else { // the amount larger than current owes to previous a -= iouCurrentToPrev.amountOwed; iouCurrentToPrev.amountOwed = 0; } EventUpdateIOU(chain[i-1], chain[i], assetFlow[i-1], iouCurrentToPrev.amountOwed, iouCurrentToPrev.maxAllowed); } // if not settled above, prev increases his debit at current if(a > 0) { uint newSum = safeAdd(iouPrevToCurrent.amountOwed, a); if(newSum <= iouPrevToCurrent.maxAllowed) { iouPrevToCurrent.amountOwed = newSum; EventUpdateIOU(chain[i], chain[i-1], assetFlow[i-1], iouPrevToCurrent.amountOwed, iouPrevToCurrent.maxAllowed); } else { throw; } } // handle FX, when there is a switch in the asset flow if(i < assetFlow.length && assetFlow[i-1] != assetFlow[i]) { // handle FX XCHG xchg = current.assets[assetFlow[i-1]].xchgs[assetFlow[i]]; // check fxRate offer is still valid //assert(xchg.validUntil > now); // check exchangeRate didn't change meanwhile assert( expectedExchangeRateInMillionthCurrentIdx < expectedExchangeRateInMillionth.length && xchg.exchangeRateInMillionth == expectedExchangeRateInMillionth[expectedExchangeRateInMillionthCurrentIdx]); expectedExchangeRateInMillionthCurrentIdx++; amount = safeMul(amount, xchg.exchangeRateInMillionth) / 1000000; EventExecuteXCHG(chain[i], assetFlow[i-1], assetFlow[i], xchg.exchangeRateInMillionth); } } } function queryIOU(address lender, Asset asset, address debitor) public constant returns (uint, uint) { IOU iou = accounts[lender].assets[asset].ious[debitor]; return (iou.amountOwed, iou.maxAllowed); } function queryXCHG(address fxAddr, Asset fromAsset, Asset toAsset) public constant returns (uint, uint) { XCHG xchg = accounts[fxAddr].assets[fromAsset].xchgs[toAsset]; return (xchg.validUntil, xchg.exchangeRateInMillionth); } /* * HELPER */ function safeMul(uint a, uint b) internal returns (uint) { uint c = a * b; assert(a == 0 || c / a == b); return c; } function safeSub(uint a, uint b) internal returns (uint) { assert(b <= a); return a - b; } function safeAdd(uint a, uint b) internal returns (uint) { uint c = a + b; assert(c>=a && c>=b); return c; } function assert(bool cond) internal { if(!cond) throw; } function () { throw; } }