From 3864559d8538029fc42457dad78fc6bc3b0a111d Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Mon, 3 Feb 2025 16:13:46 +0100 Subject: [PATCH] vault: reorder tests --- test/Vault.tests.js | 1044 ++++++++++++++++++++++--------------------- 1 file changed, 546 insertions(+), 498 deletions(-) diff --git a/test/Vault.tests.js b/test/Vault.tests.js index 44c6f9e..7feb11c 100644 --- a/test/Vault.tests.js +++ b/test/Vault.tests.js @@ -12,6 +12,8 @@ const { } = require("./evm") describe("Vault", function () { + const context = randomBytes(32) + let token let vault let controller @@ -33,612 +35,658 @@ describe("Vault", function () { await revert() }) - describe("depositing", function () { - const context = randomBytes(32) - const amount = 42 - - it("accepts deposits of tokens", async function () { - await token.connect(account).approve(vault.address, amount) - await vault.deposit(context, account.address, amount) - expect(await vault.getBalance(context, account.address)).to.equal(amount) - }) - - it("keeps custody of tokens that are deposited", async function () { - await token.connect(account).approve(vault.address, amount) - await vault.deposit(context, account.address, amount) - expect(await token.balanceOf(vault.address)).to.equal(amount) - }) - - it("deposit fails when tokens cannot be transferred", async function () { - await token.connect(account).approve(vault.address, amount - 1) - const depositing = vault.deposit(context, account.address, amount) - await expect(depositing).to.be.revertedWith("insufficient allowance") - }) - - it("adds multiple deposits to the balance", async function () { - await token.connect(account).approve(vault.address, amount) - await vault.deposit(context, account.address, amount / 2) - await vault.deposit(context, account.address, amount / 2) - expect(await vault.getBalance(context, account.address)).to.equal(amount) - }) - - it("separates deposits from different contexts", async function () { - const context1 = randomBytes(32) - const context2 = randomBytes(32) - await token.connect(account).approve(vault.address, 3) - await vault.deposit(context1, account.address, 1) - await vault.deposit(context2, account.address, 2) - expect(await vault.getBalance(context1, account.address)).to.equal(1) - expect(await vault.getBalance(context2, account.address)).to.equal(2) - }) - - it("separates deposits from different controllers", async function () { - const [, , controller1, controller2] = await ethers.getSigners() - const vault1 = vault.connect(controller1) - const vault2 = vault.connect(controller2) - await token.connect(account).approve(vault.address, 3) - await vault1.deposit(context, account.address, 1) - await vault2.deposit(context, account.address, 2) - expect(await vault1.getBalance(context, account.address)).to.equal(1) - expect(await vault2.getBalance(context, account.address)).to.equal(2) - }) - }) - - describe("withdrawing", function () { - const context = randomBytes(32) - const amount = 42 - - beforeEach(async function () { - await token.connect(account).approve(vault.address, amount) - await vault.deposit(context, account.address, amount) - }) - - it("allows controller to withdraw for a recipient", async function () { - const before = await token.balanceOf(account.address) - await vault.withdraw(context, account.address) - const after = await token.balanceOf(account.address) - expect(after - before).to.equal(amount) - }) - - it("allows recipient to withdraw for itself", async function () { - const before = await token.balanceOf(account.address) - await vault - .connect(account) - .withdrawByRecipient(controller.address, context) - const after = await token.balanceOf(account.address) - expect(after - before).to.equal(amount) - }) - - it("empties the balance when withdrawing", async function () { - await vault.withdraw(context, account.address) - expect(await vault.getBalance(context, account.address)).to.equal(0) - }) - - it("does not withdraw more than once", async function () { - await vault.withdraw(context, account.address) - const before = await token.balanceOf(account.address) - await vault.withdraw(context, account.address) - const after = await token.balanceOf(account.address) - expect(after).to.equal(before) - }) - }) - - describe("burning", function () { - const context = randomBytes(32) - const amount = 42 - - beforeEach(async function () { - await token.connect(account).approve(vault.address, amount) - await vault.deposit(context, account.address, amount) - }) - - it("can burn a deposit", async function () { - await vault.burn(context, account.address) - expect(await vault.getBalance(context, account.address)).to.equal(0) - }) - - it("no longer allows withdrawal", async function () { - await vault.burn(context, account.address) - const before = await token.balanceOf(account.address) - await vault.withdraw(context, account.address) - const after = await token.balanceOf(account.address) - expect(after).to.equal(before) - }) - - it("moves the tokens to address 0xdead", async function () { - const dead = "0x000000000000000000000000000000000000dead" - const before = await token.balanceOf(dead) - await vault.burn(context, account.address) - const after = await token.balanceOf(dead) - expect(after - before).to.equal(amount) - }) - }) - - describe("transfering", function () { - const context = randomBytes(32) - const amount = 42 - - beforeEach(async function () { - await token.connect(account).approve(vault.address, amount) - await vault.deposit(context, account.address, amount) - }) - - it("can transfer tokens from one recipient to the other", async function () { - await vault.transfer(context, account.address, account2.address, amount) - expect(await vault.getBalance(context, account.address)).to.equal(0) - expect(await vault.getBalance(context, account2.address)).to.equal(amount) - }) - - it("can transfer part of a balance", async function () { - await vault.transfer(context, account.address, account2.address, 10) - expect(await vault.getBalance(context, account.address)).to.equal( - amount - 10 - ) - expect(await vault.getBalance(context, account2.address)).to.equal(10) - }) - - it("does not transfer more than the balance", async function () { - await expect( - vault.transfer(context, account.address, account2.address, amount + 1) - ).to.be.revertedWith("InsufficientBalance") - }) - - it("can withdraw funds that were transfered in", async function () { - await vault.transfer(context, account.address, account2.address, amount) - const before = await token.balanceOf(account2.address) - await vault.withdraw(context, account2.address) - const after = await token.balanceOf(account2.address) - expect(after - before).to.equal(amount) - }) - - it("cannot withdraw funds that were transfered out", async function () { - await vault.transfer(context, account.address, account2.address, amount) - const before = await token.balanceOf(account.address) - await vault.withdraw(context, account.address) - const after = await token.balanceOf(account.address) - expect(after).to.equal(before) - }) - - it("can transfer out funds that were transfered in", async function () { - await vault.transfer(context, account.address, account2.address, amount) - await vault.transfer(context, account2.address, account3.address, amount) - expect(await vault.getBalance(context, account2.address)).to.equal(0) - expect(await vault.getBalance(context, account3.address)).to.equal(amount) - }) - }) - - describe("designating", async function () { - const context = randomBytes(32) - const amount = 42 - - beforeEach(async function () { - await token.connect(account).approve(vault.address, amount) - await vault.deposit(context, account.address, amount) - }) - - it("can designate tokens for a single recipient", async function () { - await vault.designate(context, account.address, amount) - expect( - await vault.getDesignatedBalance(context, account.address) - ).to.equal(amount) - }) - - it("can designate part of the balance", async function () { - await vault.designate(context, account.address, 10) - expect( - await vault.getDesignatedBalance(context, account.address) - ).to.equal(10) - }) - - it("adds up designated tokens", async function () { - await vault.designate(context, account.address, 10) - await vault.designate(context, account.address, 10) - expect( - await vault.getDesignatedBalance(context, account.address) - ).to.equal(20) - }) - - it("cannot designate more than the undesignated balance", async function () { - await vault.designate(context, account.address, amount) - await expect( - vault.designate(context, account.address, 1) - ).to.be.revertedWith("InsufficientBalance") - }) - - it("does not change the balance", async function () { - await vault.designate(context, account.address, 10) - expect(await vault.getBalance(context, account.address)).to.equal(amount) - }) - - it("does not allow designated tokens to be transfered", async function () { - await vault.designate(context, account.address, 1) - await expect( - vault.transfer(context, account.address, account2.address, amount) - ).to.be.revertedWith("InsufficientBalance") - }) - - it("allows designated tokens to be withdrawn", async function () { - await vault.designate(context, account.address, 10) - const before = await token.balanceOf(account.address) - await vault.withdraw(context, account.address) - const after = await token.balanceOf(account.address) - expect(after - before).to.equal(amount) - }) - - it("does not withdraw designated tokens more than once", async function () { - await vault.designate(context, account.address, 10) - await vault.withdraw(context, account.address) - const before = await token.balanceOf(account.address) - await vault.withdraw(context, account.address) - const after = await token.balanceOf(account.address) - expect(after).to.equal(before) - }) - - it("allows designated tokens to be burned", async function () { - await vault.designate(context, account.address, 10) - await vault.burn(context, account.address) - expect(await vault.getBalance(context, account.address)).to.equal(0) - }) - - it("moves burned designated tokens to address 0xdead", async function () { - const dead = "0x000000000000000000000000000000000000dead" - await vault.designate(context, account.address, 10) - const before = await token.balanceOf(dead) - await vault.burn(context, account.address) - const after = await token.balanceOf(dead) - expect(after - before).to.equal(amount) - }) - }) - - describe("locking", async function () { - const context = randomBytes(32) - const amount = 42 - - let expiry - let maximum - - beforeEach(async function () { - await token.connect(account).approve(vault.address, amount) - await vault.deposit(context, account.address, amount) - let start = await currentTime() - expiry = start + 10 - maximum = start + 20 - }) - - it("can lock up all tokens in a context", async function () { + describe("when no lock is set", function () { + it("allows a lock to be set", async function () { + expiry = (await currentTime()) + 80 + maximum = (await currentTime()) + 100 await vault.lock(context, expiry, maximum) expect((await vault.getLock(context))[0]).to.equal(expiry) expect((await vault.getLock(context))[1]).to.equal(maximum) }) - it("cannot lock up when already locked", async function () { - await vault.lock(context, expiry, maximum) - const locking = vault.lock(context, expiry, maximum) - await expect(locking).to.be.revertedWith("AlreadyLocked") - }) - - it("cannot lock when expiry is past maximum", async function () { + it("does not allow a lock with expiry past maximum", async function () { + let maximum = (await currentTime()) + 100 const locking = vault.lock(context, maximum + 1, maximum) await expect(locking).to.be.revertedWith("ExpiryPastMaximum") }) - it("does not allow withdrawal before lock expires", async function () { - await vault.lock(context, expiry, expiry) - await advanceTimeTo(expiry - 1) - const withdrawing = vault.withdraw(context, account.address) - await expect(withdrawing).to.be.revertedWith("Locked") + it("does not allow extending of lock", async function () { + await expect( + vault.extendLock(context, (await currentTime()) + 1) + ).to.be.revertedWith("LockRequired") }) - it("locks withdrawal for all recipients in a context", async function () { - await vault.lock(context, expiry, expiry) - const address1 = account.address - const address2 = account2.address - await vault.transfer(context, address1, address2, amount / 2) - let withdrawing1 = vault.withdraw(context, address1) - let withdrawing2 = vault.withdraw(context, address2) - await expect(withdrawing1).to.be.revertedWith("Locked") - await expect(withdrawing2).to.be.revertedWith("Locked") + it("does not allow flowing of tokens", async function () { + await expect( + vault.flow(context, account.address, account2.address, 0) + ).to.be.revertedWith("LockRequired") }) - it("locks withdrawal for newly deposited tokens", async function () { - await vault.lock(context, expiry, expiry) - await token.connect(account2).approve(vault.address, amount) - await vault.deposit(context, account2.address, amount) - const withdrawing = vault.withdraw(context, account2.address) - await expect(withdrawing).to.be.revertedWith("Locked") - }) - - it("allows withdrawal after lock expires", async function () { - await vault.lock(context, expiry, expiry) - await advanceTimeTo(expiry) - const before = await token.balanceOf(account.address) - await vault.withdraw(context, account.address) - const after = await token.balanceOf(account.address) - expect(after - before).to.equal(amount) - }) - - it("can extend a lock expiry up to its maximum", async function () { - await vault.lock(context, expiry, maximum) - await vault.extendLock(context, expiry + 1) - expect((await vault.getLock(context))[0]).to.equal(expiry + 1) - await vault.extendLock(context, maximum) - expect((await vault.getLock(context))[0]).to.equal(maximum) - }) - - it("cannot extend a lock past its maximum", async function () { - await vault.lock(context, expiry, maximum) - const extending = vault.extendLock(context, maximum + 1) - await expect(extending).to.be.revertedWith("ExpiryPastMaximum") - }) - - it("cannot move expiry forward", async function () { - await vault.lock(context, expiry, maximum) - const extending = vault.extendLock(context, expiry - 1) - await expect(extending).to.be.revertedWith("InvalidExpiry") - }) - - it("cannot extend an expired lock", async function () { - await vault.lock(context, expiry, maximum) - await advanceTimeTo(expiry) - const extending = vault.extendLock(context, maximum) - await expect(extending).to.be.revertedWith("LockRequired") - }) - - it("allows locked tokens to be burned", async function () { - await vault.lock(context, expiry, expiry) - await vault.burn(context, account.address) - expect(await vault.getBalance(context, account.address)).to.equal(0) - }) - - it("deletes lock when all tokens are withdrawn/burned", async function () { - await vault.lock(context, expiry, expiry) - await vault.transfer(context, account.address, account2.address, 20) - await vault.transfer(context, account2.address, account3.address, 10) - - // part of the tokens are burned - await vault.burn(context, account2.address) - await advanceTimeTo(expiry) - expect((await vault.getLock(context))[0]).not.to.equal(0) - expect((await vault.getLock(context))[1]).not.to.equal(0) - - // part of the tokens are withdrawn - await vault.withdraw(context, account.address) - expect((await vault.getLock(context))[0]).not.to.equal(0) - expect((await vault.getLock(context))[1]).not.to.equal(0) - - // remainder of the tokens are withdrawn by recipient - await vault - .connect(account3) - .withdrawByRecipient(controller.address, context) - expect((await vault.getLock(context))[0]).to.equal(0) - expect((await vault.getLock(context))[1]).to.equal(0) - }) }) - describe("flow", function () { - const context = randomBytes(32) - const deposit = 1000 - - let sender - let receiver - let receiver2 + describe("when lock is locked", function () { + let expiry + let maximum beforeEach(async function () { - await token.connect(account).approve(vault.address, deposit) - await vault.deposit(context, account.address, deposit) - sender = account.address - receiver = account2.address - receiver2 = account3.address - }) - async function getBalance(recipient) { - return await vault.getBalance(context, recipient) - } - - it("requires that a lock is set", async function () { - await expect(vault.flow(context, sender, receiver, 2)).to.be.revertedWith( - "LockRequired" - ) + const beginning = (await currentTime()) + 10 + expiry = beginning + 80 + maximum = beginning + 100 + await setAutomine(false) + await setNextBlockTimestamp(beginning) + await vault.lock(context, expiry, maximum) }) - it("requires that the lock is not expired", async function () { - let expiry = (await currentTime()) + 20 - await vault.lock(context, expiry, expiry) - await advanceTimeTo(expiry) - await expect(vault.flow(context, sender, receiver, 2)).to.be.revertedWith( - "LockRequired" - ) - }) - - describe("when a lock is set", async function () { - let expiry - let maximum - + describe("locking", function () { beforeEach(async function () { - const beginning = (await currentTime()) + 10 - expiry = beginning + 80 - maximum = beginning + 100 - await setAutomine(false) - await setNextBlockTimestamp(beginning) - await vault.lock(context, expiry, maximum) + await setAutomine(true) }) + it("cannot set lock when already locked", async function () { + await expect(vault.lock(context, expiry, maximum)).to.be.revertedWith( + "AlreadyLocked" + ) + }) + + it("can extend a lock expiry up to its maximum", async function () { + await vault.extendLock(context, expiry + 1) + expect((await vault.getLock(context))[0]).to.equal(expiry + 1) + await vault.extendLock(context, maximum) + expect((await vault.getLock(context))[0]).to.equal(maximum) + }) + + it("cannot extend a lock past its maximum", async function () { + const extending = vault.extendLock(context, maximum + 1) + await expect(extending).to.be.revertedWith("ExpiryPastMaximum") + }) + + it("cannot move expiry forward", async function () { + const extending = vault.extendLock(context, expiry - 1) + await expect(extending).to.be.revertedWith("InvalidExpiry") + }) + }) + + describe("depositing", function () { + const amount = 1000 + + beforeEach(async function () { + await setAutomine(true) + }) + + it("accepts deposits of tokens", async function () { + await token.connect(account).approve(vault.address, amount) + await vault.deposit(context, account.address, amount) + const balance = await vault.getBalance(context, account.address) + expect(balance).to.equal(amount) + }) + + it("keeps custody of tokens that are deposited", async function () { + await token.connect(account).approve(vault.address, amount) + await vault.deposit(context, account.address, amount) + expect(await token.balanceOf(vault.address)).to.equal(amount) + }) + + it("deposit fails when tokens cannot be transferred", async function () { + await token.connect(account).approve(vault.address, amount - 1) + const depositing = vault.deposit(context, account.address, amount) + await expect(depositing).to.be.revertedWith("insufficient allowance") + }) + + it("adds multiple deposits to the balance", async function () { + await token.connect(account).approve(vault.address, amount) + await vault.deposit(context, account.address, amount / 2) + await vault.deposit(context, account.address, amount / 2) + const balance = await vault.getBalance(context, account.address) + expect(balance).to.equal(amount) + }) + + it("separates deposits from different contexts", async function () { + const context1 = randomBytes(32) + const context2 = randomBytes(32) + await token.connect(account).approve(vault.address, 3) + await vault.deposit(context1, account.address, 1) + await vault.deposit(context2, account.address, 2) + expect(await vault.getBalance(context1, account.address)).to.equal(1) + expect(await vault.getBalance(context2, account.address)).to.equal(2) + }) + + it("separates deposits from different controllers", async function () { + const [, , controller1, controller2] = await ethers.getSigners() + const vault1 = vault.connect(controller1) + const vault2 = vault.connect(controller2) + await token.connect(account).approve(vault.address, 3) + await vault1.deposit(context, account.address, 1) + await vault2.deposit(context, account.address, 2) + expect(await vault1.getBalance(context, account.address)).to.equal(1) + expect(await vault2.getBalance(context, account.address)).to.equal(2) + }) + }) + + describe("designating", function () { + const amount = 1000 + + beforeEach(async function () { + await token.connect(account).approve(vault.address, amount) + await vault.deposit(context, account.address, amount) + }) + + it("can designate tokens for a single recipient", async function () { + await setAutomine(true) + await vault.designate(context, account.address, amount) + expect( + await vault.getDesignatedBalance(context, account.address) + ).to.equal(amount) + }) + + it("can designate part of the balance", async function () { + await setAutomine(true) + await vault.designate(context, account.address, 10) + expect( + await vault.getDesignatedBalance(context, account.address) + ).to.equal(10) + }) + + it("adds up designated tokens", async function () { + await setAutomine(true) + await vault.designate(context, account.address, 10) + await vault.designate(context, account.address, 10) + expect( + await vault.getDesignatedBalance(context, account.address) + ).to.equal(20) + }) + + it("does not change the balance", async function () { + await setAutomine(true) + await vault.designate(context, account.address, 10) + expect(await vault.getBalance(context, account.address)).to.equal( + amount + ) + }) + + it("cannot designate more than the undesignated balance", async function () { + await setAutomine(true) + await vault.designate(context, account.address, amount) + await expect( + vault.designate(context, account.address, 1) + ).to.be.revertedWith("InsufficientBalance") + }) + + it("cannot designate tokens that are flowing", async function () { + await vault.flow(context, account.address, account2.address, 5) + setAutomine(true) + await vault.designate(context, account.address, 500) + const designating = vault.designate(context, account.address, 1) + await expect(designating).to.be.revertedWith("InsufficientBalance") + }) + }) + + describe("transfering", function () { + const amount = 1000 + + let address1 + let address2 + let address3 + + beforeEach(async function () { + await token.connect(account).approve(vault.address, amount) + await vault.deposit(context, account.address, amount) + address1 = account.address + address2 = account2.address + address3 = account3.address + }) + + it("can transfer tokens from one recipient to the other", async function () { + await setAutomine(true) + await vault.transfer(context, address1, address2, amount) + expect(await vault.getBalance(context, address1)).to.equal(0) + expect(await vault.getBalance(context, address2)).to.equal(amount) + }) + + it("can transfer part of a balance", async function () { + await setAutomine(true) + await vault.transfer(context, address1, address2, 10) + expect(await vault.getBalance(context, address1)).to.equal(amount - 10) + expect(await vault.getBalance(context, address2)).to.equal(10) + }) + + it("can transfer out funds that were transfered in", async function () { + await setAutomine(true) + await vault.transfer(context, address1, address2, amount) + await vault.transfer(context, address2, address3, amount) + expect(await vault.getBalance(context, address2)).to.equal(0) + expect(await vault.getBalance(context, address3)).to.equal(amount) + }) + + it("does not transfer more than the balance", async function () { + await setAutomine(true) + await expect( + vault.transfer(context, address1, address2, amount + 1) + ).to.be.revertedWith("InsufficientBalance") + }) + + it("does not transfer designated tokens", async function () { + await setAutomine(true) + await vault.designate(context, account.address, 1) + await expect( + vault.transfer(context, account.address, account2.address, amount) + ).to.be.revertedWith("InsufficientBalance") + }) + + it("does not transfer tokens that are flowing", async function () { + await vault.flow(context, address1, address2, 5) + setAutomine(true) + await vault.transfer(context, address1, address2, 500) + await expect( + vault.transfer(context, address1, address2, 1) + ).to.be.revertedWith("InsufficientBalance") + }) + }) + + describe("flowing", function () { + const deposit = 1000 + + let address1 + let address2 + let address3 + + beforeEach(async function () { + await token.connect(account).approve(vault.address, deposit) + await vault.deposit(context, account.address, deposit) + address1 = account.address + address2 = account2.address + address3 = account3.address + }) + + async function getBalance(recipient) { + return await vault.getBalance(context, recipient) + } + it("moves tokens over time", async function () { - await vault.flow(context, sender, receiver, 2) + await vault.flow(context, address1, address2, 2) mine() const start = await currentTime() await advanceTimeTo(start + 2) - expect(await getBalance(sender)).to.equal(deposit - 4) - expect(await getBalance(receiver)).to.equal(4) + expect(await getBalance(address1)).to.equal(deposit - 4) + expect(await getBalance(address2)).to.equal(4) await advanceTimeTo(start + 4) - expect(await getBalance(sender)).to.equal(deposit - 8) - expect(await getBalance(receiver)).to.equal(8) + expect(await getBalance(address1)).to.equal(deposit - 8) + expect(await getBalance(address2)).to.equal(8) }) it("can move tokens to several different recipients", async function () { - await vault.flow(context, sender, receiver, 1) - await vault.flow(context, sender, receiver2, 2) + await vault.flow(context, address1, address2, 1) + await vault.flow(context, address1, address3, 2) await mine() const start = await currentTime() await advanceTimeTo(start + 2) - expect(await getBalance(sender)).to.equal(deposit - 6) - expect(await getBalance(receiver)).to.equal(2) - expect(await getBalance(receiver2)).to.equal(4) + expect(await getBalance(address1)).to.equal(deposit - 6) + expect(await getBalance(address2)).to.equal(2) + expect(await getBalance(address3)).to.equal(4) await advanceTimeTo(start + 4) - expect(await getBalance(sender)).to.equal(deposit - 12) - expect(await getBalance(receiver)).to.equal(4) - expect(await getBalance(receiver2)).to.equal(8) + expect(await getBalance(address1)).to.equal(deposit - 12) + expect(await getBalance(address2)).to.equal(4) + expect(await getBalance(address3)).to.equal(8) }) it("allows flows to be diverted to other recipient", async function () { - await vault.flow(context, sender, receiver, 3) - await vault.flow(context, receiver, receiver2, 1) + await vault.flow(context, address1, address2, 3) + await vault.flow(context, address2, address3, 1) await mine() const start = await currentTime() await advanceTimeTo(start + 2) - expect(await getBalance(sender)).to.equal(deposit - 6) - expect(await getBalance(receiver)).to.equal(4) - expect(await getBalance(receiver2)).to.equal(2) + expect(await getBalance(address1)).to.equal(deposit - 6) + expect(await getBalance(address2)).to.equal(4) + expect(await getBalance(address3)).to.equal(2) await advanceTimeTo(start + 4) - expect(await getBalance(sender)).to.equal(deposit - 12) - expect(await getBalance(receiver)).to.equal(8) - expect(await getBalance(receiver2)).to.equal(4) + expect(await getBalance(address1)).to.equal(deposit - 12) + expect(await getBalance(address2)).to.equal(8) + expect(await getBalance(address3)).to.equal(4) }) it("allows flow to be reversed back to the sender", async function () { - await vault.flow(context, sender, receiver, 3) - await vault.flow(context, receiver, sender, 3) + await vault.flow(context, address1, address2, 3) + await vault.flow(context, address2, address1, 3) await mine() const start = await currentTime() await advanceTimeTo(start + 2) - expect(await getBalance(sender)).to.equal(deposit) - expect(await getBalance(receiver)).to.equal(0) + expect(await getBalance(address1)).to.equal(deposit) + expect(await getBalance(address2)).to.equal(0) await advanceTimeTo(start + 4) - expect(await getBalance(sender)).to.equal(deposit) - expect(await getBalance(receiver)).to.equal(0) + expect(await getBalance(address1)).to.equal(deposit) + expect(await getBalance(address2)).to.equal(0) }) it("can change flows over time", async function () { - await vault.flow(context, sender, receiver, 1) - await vault.flow(context, sender, receiver2, 2) + await vault.flow(context, address1, address2, 1) + await vault.flow(context, address1, address3, 2) await mine() const start = await currentTime() setNextBlockTimestamp(start + 4) - await vault.flow(context, receiver2, receiver, 1) + await vault.flow(context, address3, address2, 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) + expect(await getBalance(address1)).to.equal(deposit - 12) + expect(await getBalance(address2)).to.equal(4) + expect(await getBalance(address3)).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) + expect(await getBalance(address1)).to.equal(deposit - 24) + expect(await getBalance(address2)).to.equal(12) + expect(await getBalance(address3)).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) + expect(await getBalance(address1)).to.equal(deposit - 36) + expect(await getBalance(address2)).to.equal(20) + expect(await getBalance(address3)).to.equal(16) }) it("designates tokens that flow for the recipient", async function () { - await vault.flow(context, sender, receiver, 3) + await vault.flow(context, address1, address2, 3) await mine() const start = await currentTime() await advanceTimeTo(start + 7) - expect(await vault.getDesignatedBalance(context, receiver)).to.equal(21) - }) - - it("stops flowing when lock expires", async function () { - await vault.flow(context, sender, receiver, 2) - await mine() - const start = await currentTime() - await advanceTimeTo(expiry) - const total = (expiry - start) * 2 - expect(await getBalance(sender)).to.equal(deposit - total) - expect(await getBalance(receiver)).to.equal(total) - await advanceTimeTo(expiry + 10) - expect(await getBalance(sender)).to.equal(deposit - total) - expect(await getBalance(receiver)).to.equal(total) + expect(await vault.getDesignatedBalance(context, address2)).to.equal(21) }) it("flows longer when lock is extended", async function () { - await vault.flow(context, sender, receiver, 2) + await vault.flow(context, address1, address2, 2) await mine() const start = await currentTime() await vault.extendLock(context, maximum) await mine() await advanceTimeTo(maximum) const total = (maximum - start) * 2 - expect(await getBalance(sender)).to.equal(deposit - total) - expect(await getBalance(receiver)).to.equal(total) + expect(await getBalance(address1)).to.equal(deposit - total) + expect(await getBalance(address2)).to.equal(total) await advanceTimeTo(maximum + 10) - expect(await getBalance(sender)).to.equal(deposit - total) - expect(await getBalance(receiver)).to.equal(total) + expect(await getBalance(address1)).to.equal(deposit - total) + expect(await getBalance(address2)).to.equal(total) }) it("rejects negative flows", async function () { setAutomine(true) await expect( - vault.flow(context, sender, receiver, -1) + vault.flow(context, address1, address2, -1) ).to.be.revertedWith("NegativeFlow") }) it("rejects flow when insufficient available tokens", async function () { setAutomine(true) await expect( - vault.flow(context, sender, receiver, 11) + vault.flow(context, address1, address2, 11) ).to.be.revertedWith("InsufficientBalance") }) it("rejects total flows exceeding available tokens", async function () { - await vault.flow(context, sender, receiver, 10) + await vault.flow(context, address1, address2, 10) setAutomine(true) await expect( - vault.flow(context, sender, receiver, 1) + vault.flow(context, address1, address2, 1) ).to.be.revertedWith("InsufficientBalance") }) it("cannot flow designated tokens", async function () { - await vault.designate(context, sender, 500) - await vault.flow(context, sender, receiver, 5) + await vault.designate(context, address1, 500) + await vault.flow(context, address1, address2, 5) setAutomine(true) await expect( - vault.flow(context, sender, receiver, 1) + vault.flow(context, address1, address2, 1) ).to.be.revertedWith("InsufficientBalance") }) + }) - it("cannot transfer tokens that are flowing", async function () { - await vault.flow(context, sender, receiver, 5) - setAutomine(true) - await vault.transfer(context, sender, receiver, 500) - await expect( - vault.transfer(context, sender, receiver, 1) - ).to.be.revertedWith("InsufficientBalance") + describe("burning", function () { + const amount = 1000 + + beforeEach(async function () { + await setAutomine(true) + await token.connect(account).approve(vault.address, amount) + await vault.deposit(context, account.address, amount) }) - it("cannot designate tokens that are flowing", async function () { - await vault.flow(context, sender, receiver, 5) - setAutomine(true) - await vault.designate(context, sender, 500) - await expect(vault.designate(context, sender, 1)).to.be.revertedWith( - "InsufficientBalance" - ) + it("can burn a deposit", async function () { + await vault.burn(context, account.address) + expect(await vault.getBalance(context, account.address)).to.equal(0) + }) + + it("moves the tokens to address 0xdead", async function () { + const dead = "0x000000000000000000000000000000000000dead" + const before = await token.balanceOf(dead) + await vault.burn(context, account.address) + const after = await token.balanceOf(dead) + expect(after - before).to.equal(amount) + }) + + it("allows designated tokens to be burned", async function () { + await vault.designate(context, account.address, 10) + await vault.burn(context, account.address) + expect(await vault.getBalance(context, account.address)).to.equal(0) + }) + + it("moves burned designated tokens to address 0xdead", async function () { + const dead = "0x000000000000000000000000000000000000dead" + await vault.designate(context, account.address, 10) + const before = await token.balanceOf(dead) + await vault.burn(context, account.address) + const after = await token.balanceOf(dead) + expect(after - before).to.equal(amount) }) it("cannot burn tokens that are flowing", async function () { - await vault.flow(context, sender, receiver, 5) - setAutomine(true) - await expect(vault.burn(context, sender)).to.be.revertedWith( - "CannotBurnFlowingTokens" - ) - await expect(vault.burn(context, receiver)).to.be.revertedWith( - "CannotBurnFlowingTokens" - ) + await vault.flow(context, account.address, account2.address, 5) + const burning1 = vault.burn(context, account.address) + await expect(burning1).to.be.revertedWith("CannotBurnFlowingTokens") + const burning2 = vault.burn(context, account2.address) + await expect(burning2).to.be.revertedWith("CannotBurnFlowingTokens") }) it("can burn tokens that are no longer flowing", async function () { + await vault.flow(context, account.address, account2.address, 5) + await vault.flow(context, account2.address, account.address, 5) + await expect(vault.burn(context, account.address)).not.to.be.reverted + }) + }) + + describe("withdrawing", function () { + const amount = 1000 + + beforeEach(async function () { + await setAutomine(true) + await token.connect(account).approve(vault.address, amount) + await vault.deposit(context, account.address, amount) + }) + + it("does not allow withdrawal before lock expires", async function () { + await setNextBlockTimestamp(expiry - 1) + const withdrawing = vault.withdraw(context, account.address) + await expect(withdrawing).to.be.revertedWith("Locked") + }) + + it("disallows withdrawal for everyone in the context", async function () { + const address1 = account.address + const address2 = account2.address + await vault.transfer(context, address1, address2, amount / 2) + let withdrawing1 = vault.withdraw(context, address1) + let withdrawing2 = vault.withdraw(context, address2) + await expect(withdrawing1).to.be.revertedWith("Locked") + await expect(withdrawing2).to.be.revertedWith("Locked") + }) + }) + }) + + describe("when lock is expiring", function () { + let expiry + let maximum + + beforeEach(async function () { + const beginning = (await currentTime()) + 10 + expiry = beginning + 80 + maximum = beginning + 100 + await setAutomine(false) + await setNextBlockTimestamp(beginning) + await vault.lock(context, expiry, maximum) + }) + + async function expire() { + await setNextBlockTimestamp(expiry) + } + + describe("locking", function () { + beforeEach(async function () { + await setAutomine(true) + }) + + it("cannot set lock when lock expired", async function () { + await expire() + const locking = vault.lock(context, expiry, maximum) + await expect(locking).to.be.revertedWith("AlreadyLocked") + }) + + it("cannot extend an expired lock", async function () { + await expire() + const extending = vault.extendLock(context, maximum) + await expect(extending).to.be.revertedWith("LockRequired") + }) + + it("deletes lock when no tokens remain", async function () { + await token.connect(account).approve(vault.address, 30) + await vault.deposit(context, account.address, 30) + await vault.transfer(context, account.address, account2.address, 20) + await vault.transfer(context, account2.address, account3.address, 10) + // some tokens are burned + await vault.burn(context, account2.address) + await expire() + // some tokens are withdrawn + await vault.withdraw(context, account.address) + expect((await vault.getLock(context))[0]).not.to.equal(0) + expect((await vault.getLock(context))[1]).not.to.equal(0) + // remainder of the tokens are withdrawn by recipient + await vault + .connect(account3) + .withdrawByRecipient(controller.address, context) + expect((await vault.getLock(context))[0]).to.equal(0) + expect((await vault.getLock(context))[1]).to.equal(0) + }) + }) + + describe("flowing", function () { + const deposit = 1000 + + beforeEach(async function () { + await token.connect(account).approve(vault.address, deposit) + await vault.deposit(context, account.address, deposit) + }) + + it("stops flows when lock expires", async function () { + await vault.flow(context, account.address, account2.address, 2) + await mine() + const start = await currentTime() + const total = (expiry - start) * 2 + let balance1, balance2 + await advanceTimeTo(expiry) + balance1 = await vault.getBalance(context, account.address) + balance2 = await vault.getBalance(context, account2.address) + expect(balance1).to.equal(deposit - total) + expect(balance2).to.equal(total) + await advanceTimeTo(expiry + 10) + balance1 = await vault.getBalance(context, account.address) + balance2 = await vault.getBalance(context, account2.address) + expect(balance1).to.equal(deposit - total) + expect(balance2).to.equal(total) + }) + + it("does not allow new flows to start", async function () { + await setAutomine(true) + await expire() + await expect( + vault.flow(context, account.address, account2.address, 0) + ).to.be.revertedWith("LockRequired") + }) + }) + + describe("withdrawing", function () { + const amount = 1000 + + beforeEach(async function () { setAutomine(true) - await vault.flow(context, sender, receiver, 5) - await vault.flow(context, receiver, sender, 5) - await expect(vault.burn(context, sender)).not.to.be.reverted + await token.connect(account).approve(vault.address, amount) + await vault.deposit(context, account.address, amount) + }) + + it("allows controller to withdraw for a recipient", async function () { + await expire() + const before = await token.balanceOf(account.address) + await vault.withdraw(context, account.address) + const after = await token.balanceOf(account.address) + expect(after - before).to.equal(amount) + }) + + it("allows recipient to withdraw for itself", async function () { + await expire() + const before = await token.balanceOf(account.address) + await vault + .connect(account) + .withdrawByRecipient(controller.address, context) + const after = await token.balanceOf(account.address) + expect(after - before).to.equal(amount) + }) + + it("empties the balance when withdrawing", async function () { + await expire() + await vault.withdraw(context, account.address) + expect(await vault.getBalance(context, account.address)).to.equal(0) + }) + + it("allows designated tokens to be withdrawn", async function () { + await vault.designate(context, account.address, 10) + await expire() + const before = await token.balanceOf(account.address) + await vault.withdraw(context, account.address) + const after = await token.balanceOf(account.address) + expect(after - before).to.equal(amount) + }) + + it("does not withdraw designated tokens more than once", async function () { + await vault.designate(context, account.address, 10) + await expire() + await vault.withdraw(context, account.address) + const before = await token.balanceOf(account.address) + await vault.withdraw(context, account.address) + const after = await token.balanceOf(account.address) + expect(after).to.equal(before) + }) + + it("can withdraw funds that were transfered in", async function () { + await vault.transfer(context, account.address, account2.address, amount) + await expire() + const before = await token.balanceOf(account2.address) + await vault.withdraw(context, account2.address) + const after = await token.balanceOf(account2.address) + expect(after - before).to.equal(amount) + }) + + it("cannot withdraw funds that were transfered out", async function () { + await vault.transfer(context, account.address, account2.address, amount) + await expire() + const before = await token.balanceOf(account.address) + await vault.withdraw(context, account.address) + const after = await token.balanceOf(account.address) + expect(after).to.equal(before) + }) + + it("cannot withdraw more than once", async function () { + await expire() + await vault.withdraw(context, account.address) + const before = await token.balanceOf(account.address) + await vault.withdraw(context, account.address) + const after = await token.balanceOf(account.address) + expect(after).to.equal(before) + }) + + it("cannot withdraw burned tokens", async function () { + await vault.burn(context, account.address) + await expire() + const before = await token.balanceOf(account.address) + await vault.withdraw(context, account.address) + const after = await token.balanceOf(account.address) + expect(after).to.equal(before) }) }) })