diff --git a/contracts/vault/TokensPerSecond.sol b/contracts/vault/TokensPerSecond.sol index 174be78..0dc4a30 100644 --- a/contracts/vault/TokensPerSecond.sol +++ b/contracts/vault/TokensPerSecond.sol @@ -11,6 +11,7 @@ using {_tokensPerSecondPlus as +} for TokensPerSecond global; using {_tokensPerSecondEquals as ==} for TokensPerSecond global; using {_tokensPerSecondNotEqual as !=} for TokensPerSecond global; using {_tokensPerSecondAtLeast as >=} for TokensPerSecond global; +using {_tokensPerSecondLessThan as <} for TokensPerSecond global; function _tokensPerSecondNegate( TokensPerSecond rate @@ -54,3 +55,10 @@ function _tokensPerSecondAtLeast( ) pure returns (bool) { return TokensPerSecond.unwrap(a) >= TokensPerSecond.unwrap(b); } + +function _tokensPerSecondLessThan( + TokensPerSecond a, + TokensPerSecond b +) pure returns (bool) { + return TokensPerSecond.unwrap(a) < TokensPerSecond.unwrap(b); +} diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol index b788c6c..6513dc5 100644 --- a/contracts/vault/VaultBase.sol +++ b/contracts/vault/VaultBase.sol @@ -179,22 +179,36 @@ abstract contract VaultBase { Lock memory lock = _locks[controller][context]; require(lock.isLocked(), LockRequired()); - Timestamp start = Timestamps.currentTime(); + Balance memory senderBalance = _getBalance(controller, context, from); + Balance memory receiverBalance = _getBalance(controller, context, to); Flow memory senderFlow = _flows[controller][context][from]; + Flow memory receiverFlow = _flows[controller][context][to]; + + Timestamp start = Timestamps.currentTime(); senderFlow.start = start; senderFlow.rate = senderFlow.rate - rate; - Flow memory receiverFlow = _flows[controller][context][to]; receiverFlow.start = start; receiverFlow.rate = receiverFlow.rate + rate; - Balance memory senderBalance = _getBalance(controller, context, from); - uint128 flowMaximum = uint128(-senderFlow._totalAt(lock.maximum)); - require(flowMaximum <= senderBalance.available, InsufficientBalance()); + _checkFlowInvariant(senderBalance, lock, senderFlow); + _balances[controller][context][from] = senderBalance; + _balances[controller][context][to] = receiverBalance; _flows[controller][context][from] = senderFlow; _flows[controller][context][to] = receiverFlow; } + function _checkFlowInvariant( + Balance memory balance, + Lock memory lock, + Flow memory flow + ) private pure { + if (flow.rate < TokensPerSecond.wrap(0)) { + uint128 outgoing = uint128(-flow._totalAt(lock.maximum)); + require(outgoing <= balance.available, InsufficientBalance()); + } + } + error InsufficientBalance(); error Locked(); error AlreadyLocked(); diff --git a/test/Vault.tests.js b/test/Vault.tests.js index dd3a717..9eee3cb 100644 --- a/test/Vault.tests.js +++ b/test/Vault.tests.js @@ -476,6 +476,28 @@ describe("Vault", function () { expect(await getBalance(receiver2)).to.equal(8) }) + it("can change flows over time", async function () { + await setAutomine(false) + await vault.flow(context, sender, receiver, 1) + await vault.flow(context, sender, receiver2, 2) + await mine() + const start = await currentTime() + advanceTimeToForNextBlock(start + 4) + await vault.flow(context, receiver2, receiver, 1) + await mine() + expect(await getBalance(sender)).to.equal(deposit - 12) + expect(await getBalance(receiver)).to.equal(4) + expect(await getBalance(receiver2)).to.equal(8) + await advanceTimeTo(start + 8) + expect(await getBalance(sender)).to.equal(deposit - 24) + expect(await getBalance(receiver)).to.equal(12) + expect(await getBalance(receiver2)).to.equal(12) + await advanceTimeTo(start + 12) + expect(await getBalance(sender)).to.equal(deposit - 36) + expect(await getBalance(receiver)).to.equal(20) + expect(await getBalance(receiver2)).to.equal(16) + }) + it("designates tokens that flow for the recipient", async function () { await vault.flow(context, sender, receiver, 3) const start = await currentTime()