From a38fd1281be9e62a09fa5942605f9d268a2e375e Mon Sep 17 00:00:00 2001 From: Bartlomiej Tarczynski Date: Wed, 17 Nov 2021 11:46:11 +0100 Subject: [PATCH 1/4] Add unshift function to SubtreeQueue --- contracts/DepositManager.sol | 20 ++++++++++++-------- contracts/test/TestRollup.sol | 4 +--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/contracts/DepositManager.sol b/contracts/DepositManager.sol index c631e55a..deac05f7 100644 --- a/contracts/DepositManager.sol +++ b/contracts/DepositManager.sol @@ -69,17 +69,21 @@ contract SubtreeQueue { event DepositSubTreeReady(uint256 subtreeID, bytes32 subtreeRoot); - function enqueue(bytes32 subtreeRoot) internal returns (uint256 subtreeID) { + function unshift(bytes32 subtreeRoot) internal { + uint256 subtreeID = front - 1; + require(subtreeID > 0, "Deposit Core: No subtrees to unshift"); + front = subtreeID; + queue[subtreeID] = subtreeRoot; + } + + function push(bytes32 subtreeRoot) internal returns (uint256 subtreeID) { subtreeID = back + 1; back = subtreeID; queue[subtreeID] = subtreeRoot; emit DepositSubTreeReady(subtreeID, subtreeRoot); } - function dequeue() - internal - returns (uint256 subtreeID, bytes32 subtreeRoot) - { + function shift() internal returns (uint256 subtreeID, bytes32 subtreeRoot) { subtreeID = front; require(back >= subtreeID, "Deposit Core: Queue should be non-empty"); subtreeRoot = queue[subtreeID]; @@ -132,7 +136,7 @@ contract DepositCore is SubtreeQueue { // Subtree is ready, send to SubtreeQueue if (numDeposits == paramMaxSubtreeSize) { - subtreeID = enqueue(babyTrees[0]); + subtreeID = push(babyTrees[0]); // reset babyTreesLength = 0; depositCount = 0; @@ -234,10 +238,10 @@ contract DepositManager is onlyRollup returns (uint256 subtreeID, bytes32 subtreeRoot) { - return dequeue(); + return shift(); } function reenqueue(bytes32 subtreeRoot) external override onlyRollup { - enqueue(subtreeRoot); + unshift(subtreeRoot); } } diff --git a/contracts/test/TestRollup.sol b/contracts/test/TestRollup.sol index 9be3c408..df9e4a06 100644 --- a/contracts/test/TestRollup.sol +++ b/contracts/test/TestRollup.sol @@ -19,9 +19,7 @@ contract MockDepositManager is IDepositManager { return (0, bytes32(0)); } - function reenqueue(bytes32 subtreeRoot) external override { - emit DepositSubTreeReady(0, subtreeRoot); - } + function reenqueue(bytes32 subtreeRoot) external override {} } contract TestRollup is BatchManager { From 193d643bbd81aa3b85d5f8277a174c93fefc8b51 Mon Sep 17 00:00:00 2001 From: Bartlomiej Tarczynski Date: Thu, 18 Nov 2021 10:31:31 +0100 Subject: [PATCH 2/4] Add test for deposit rollback --- test/fast/rollback.test.ts | 24 ----- test/slow/rollup.deposit.test.ts | 153 +++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 test/slow/rollup.deposit.test.ts diff --git a/test/fast/rollback.test.ts b/test/fast/rollback.test.ts index d55f54a6..20036206 100644 --- a/test/fast/rollback.test.ts +++ b/test/fast/rollback.test.ts @@ -123,28 +123,4 @@ describe("Rollback", function() { assert.equal(Number(await rollup.invalidBatchMarker()), 0); assert.equal(await getTipBatchID(), goodBatchID); }); - it("Test rollback with deposits", async function() { - const badBatchID = await getTipBatchID(); - const [subtree1, subtree2, subtree3] = [ - randHex(32), - randHex(32), - randHex(32) - ]; - await rollup.submitDeposits(subtree1, { value: param.STAKE_AMOUNT }); - await rollup.submitDummyBatch({ value: param.STAKE_AMOUNT }); - await rollup.submitDeposits(subtree2, { value: param.STAKE_AMOUNT }); - await rollup.submitDummyBatch({ value: param.STAKE_AMOUNT }); - await rollup.submitDeposits(subtree3, { value: param.STAKE_AMOUNT }); - const tx = await rollup.testRollback(badBatchID, { gasLimit: 1000000 }); - const events = await depositManager.queryFilter( - depositManager.filters.DepositSubTreeReady(), - tx.blockHash - ); - assert.equal(events.length, 3); - const [event1, event2, event3] = events; - // Since we are rolling "back", the events are emitted in reverse order - assert.equal(event1.args?.subtreeRoot, subtree3); - assert.equal(event2.args?.subtreeRoot, subtree2); - assert.equal(event3.args?.subtreeRoot, subtree1); - }); }); diff --git a/test/slow/rollup.deposit.test.ts b/test/slow/rollup.deposit.test.ts new file mode 100644 index 00000000..e4a6ca70 --- /dev/null +++ b/test/slow/rollup.deposit.test.ts @@ -0,0 +1,153 @@ +import { deployAll } from "../../ts/deploy"; +import { TESTING_PARAMS } from "../../ts/constants"; +import { ethers } from "hardhat"; +import { StateTree } from "../../ts/stateTree"; +import { AccountRegistry } from "../../ts/accountTree"; +import { serialize } from "../../ts/tx"; +import * as mcl from "../../ts/mcl"; +import { allContracts } from "../../ts/allContractsInterfaces"; +import chai, { assert } from "chai"; +import chaiAsPromised from "chai-as-promised"; +import { + getGenesisProof, + TransferBatch, + TransferCommitment +} from "../../ts/commitments"; +import { ERC20ValueFactory, USDT } from "../../ts/decimal"; +import { hexToUint8Array } from "../../ts/utils"; +import { Group, txTransferFactory } from "../../ts/factory"; +import { deployKeyless } from "../../ts/deployment/deploy"; + +chai.use(chaiAsPromised); + +const DOMAIN = hexToUint8Array( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +); + +describe("Rollup Deposit", async function() { + const tokenID = 0; + let contracts: allContracts; + let stateTree: StateTree; + let registry: AccountRegistry; + let users: Group; + let genesisRoot: string; + let erc20: ERC20ValueFactory; + + before(async function() { + await mcl.init(); + }); + + beforeEach(async function() { + const [signer] = await ethers.getSigners(); + + users = Group.new({ + n: 32, + initialStateID: 0, + initialPubkeyID: 0, + domain: DOMAIN + }); + + stateTree = new StateTree(TESTING_PARAMS.MAX_DEPTH); + + const initialBalance = USDT.fromHumanValue("55.6").l2Value; + users + .connect(stateTree) + .createStates({ initialBalance, tokenID, zeroNonce: true }); + + genesisRoot = stateTree.root; + + await deployKeyless(signer, false); + contracts = await deployAll(signer, { + ...TESTING_PARAMS, + GENESIS_STATE_ROOT: genesisRoot + }); + + registry = await AccountRegistry.new(contracts.blsAccountRegistry); + + for (const user of users.userIterator()) { + const pubkeyID = await registry.register(user.pubkey); + assert.equal(pubkeyID, user.pubkeyID); + } + + const { exampleToken, depositManager } = contracts; + erc20 = new ERC20ValueFactory(await exampleToken.decimals()); + await exampleToken.approve( + depositManager.address, + erc20.fromHumanValue("1000000").l1Value + ); + }); + + it("reenqueue deposit subtree on rollback", async function() { + const feeReceiver = users.getUser(0).stateID; + const { rollup, depositManager } = contracts; + const { txs, signature } = txTransferFactory( + users, + TESTING_PARAMS.MAX_TXS_PER_COMMIT + ); + + const postBatchStateTree = new StateTree(TESTING_PARAMS.MAX_DEPTH); + const commitment = TransferCommitment.new( + postBatchStateTree.root, + registry.root(), + signature, + feeReceiver, + serialize(txs) + ); + + const targetBatch = commitment.toBatch(); + const transferBatchID = 1; + const _txSubmit = await targetBatch.submit( + rollup, + transferBatchID, + TESTING_PARAMS.STAKE_AMOUNT + ); + await _txSubmit.wait(); + + const depositSubtreeRoot = await submitDepositBatch( + targetBatch, + postBatchStateTree + ); + + const { proofs } = stateTree.processTransferCommit(txs, feeReceiver); + const _tx = await rollup.disputeTransitionTransfer( + transferBatchID, + getGenesisProof(genesisRoot), + targetBatch.proof(0), + proofs, + { gasLimit: 10000000 } + ); + const receipt = await _tx.wait(); + console.log("disputeBatch execution cost", receipt.gasUsed.toNumber()); + + const root = await depositManager.queue(1); + assert.equal(root, depositSubtreeRoot); + }).timeout(120000); + + async function submitDepositBatch( + previousBatch: TransferBatch, + stateTree: StateTree + ): Promise { + const { depositManager, rollup } = contracts; + + const vacancyProof = stateTree.getVacancyProof( + 0, + TESTING_PARAMS.MAX_DEPOSIT_SUBTREE_DEPTH + ); + + const amount = erc20.fromHumanValue("10"); + + const nDeposits = 2 ** TESTING_PARAMS.MAX_DEPOSIT_SUBTREE_DEPTH; + + for (let i = 0; i < nDeposits; i++) { + await depositManager.depositFor(i, amount.l1Value, tokenID); + } + const batchID = 2; + await rollup.submitDeposits( + batchID, + previousBatch.proofCompressed(0), + vacancyProof, + { value: TESTING_PARAMS.STAKE_AMOUNT } + ); + return rollup.deposits(2); + } +}); From 3a5311c7ebbacd8a77e72729bdf151ee04afab6c Mon Sep 17 00:00:00 2001 From: Bartlomiej Tarczynski Date: Thu, 18 Nov 2021 13:57:08 +0100 Subject: [PATCH 3/4] Submit two deposit batches in test --- test/slow/rollup.deposit.test.ts | 82 ++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/test/slow/rollup.deposit.test.ts b/test/slow/rollup.deposit.test.ts index e4a6ca70..55f42616 100644 --- a/test/slow/rollup.deposit.test.ts +++ b/test/slow/rollup.deposit.test.ts @@ -8,15 +8,15 @@ import * as mcl from "../../ts/mcl"; import { allContracts } from "../../ts/allContractsInterfaces"; import chai, { assert } from "chai"; import chaiAsPromised from "chai-as-promised"; -import { - getGenesisProof, - TransferBatch, - TransferCommitment -} from "../../ts/commitments"; +import { getGenesisProof, TransferCommitment } from "../../ts/commitments"; import { ERC20ValueFactory, USDT } from "../../ts/decimal"; import { hexToUint8Array } from "../../ts/utils"; import { Group, txTransferFactory } from "../../ts/factory"; import { deployKeyless } from "../../ts/deployment/deploy"; +import { handleNewBatch } from "../../ts/client/features/deposit"; +import { Batch } from "../../ts/client/features/interface"; +import { BigNumberish } from "ethers"; +import { State } from "../../ts/state"; chai.use(chaiAsPromised); @@ -59,6 +59,7 @@ describe("Rollup Deposit", async function() { await deployKeyless(signer, false); contracts = await deployAll(signer, { ...TESTING_PARAMS, + BLOCKS_TO_FINALISE: 10, GENESIS_STATE_ROOT: genesisRoot }); @@ -77,7 +78,7 @@ describe("Rollup Deposit", async function() { ); }); - it("reenqueue deposit subtree on rollback", async function() { + it.only("reenqueue deposit subtree on rollback", async function() { const feeReceiver = users.getUser(0).stateID; const { rollup, depositManager } = contracts; const { txs, signature } = txTransferFactory( @@ -103,9 +104,11 @@ describe("Rollup Deposit", async function() { ); await _txSubmit.wait(); - const depositSubtreeRoot = await submitDepositBatch( + const subtreeRoots = await submitTwoDepositBatches( targetBatch, - postBatchStateTree + 2, + postBatchStateTree, + 0 ); const { proofs } = stateTree.processTransferCommit(txs, feeReceiver); @@ -119,35 +122,74 @@ describe("Rollup Deposit", async function() { const receipt = await _tx.wait(); console.log("disputeBatch execution cost", receipt.gasUsed.toNumber()); - const root = await depositManager.queue(1); - assert.equal(root, depositSubtreeRoot); + for (let i = 0; i < subtreeRoots.length; i++) { + const root = await depositManager.queue(i + 1); + assert.equal(root, subtreeRoots[i]); + } }).timeout(120000); + async function submitTwoDepositBatches( + previousBatch: Batch, + batchID: number, + stateTree: StateTree, + startStateID: number + ): Promise { + const submitResult1 = await submitDepositBatch( + previousBatch, + batchID, + stateTree, + startStateID + ); + + const submitResult2 = await submitDepositBatch( + submitResult1.batch, + batchID + 1, + stateTree, + startStateID + 4 + ); + + return [submitResult1.subtreeRoot, submitResult2.subtreeRoot]; + } + async function submitDepositBatch( - previousBatch: TransferBatch, - stateTree: StateTree - ): Promise { + previousBatch: Batch, + batchID: BigNumberish, + stateTree: StateTree, + startStateID: number + ): Promise<{ + subtreeRoot: string; + batch: Batch; + }> { const { depositManager, rollup } = contracts; + const amount = erc20.fromHumanValue("10"); + const vacancyProof = stateTree.getVacancyProof( - 0, + startStateID, TESTING_PARAMS.MAX_DEPOSIT_SUBTREE_DEPTH ); - const amount = erc20.fromHumanValue("10"); - const nDeposits = 2 ** TESTING_PARAMS.MAX_DEPOSIT_SUBTREE_DEPTH; - for (let i = 0; i < nDeposits; i++) { await depositManager.depositFor(i, amount.l1Value, tokenID); + const state = State.new(i, tokenID, amount.l2Value, 0); + stateTree.createState(startStateID + i, state); } - const batchID = 2; - await rollup.submitDeposits( + const _txSubmit = await rollup.submitDeposits( batchID, previousBatch.proofCompressed(0), vacancyProof, { value: TESTING_PARAMS.STAKE_AMOUNT } ); - return rollup.deposits(2); + await _txSubmit.wait(); + + const [event] = await rollup.queryFilter( + rollup.filters.NewBatch(null, null, null), + _txSubmit.blockHash + ); + return { + subtreeRoot: await rollup.deposits(2), + batch: await handleNewBatch(event, rollup) + }; } }); From a93de9a1c60146180a93fede2919bde2ca06c78a Mon Sep 17 00:00:00 2001 From: Bartlomiej Tarczynski Date: Mon, 22 Nov 2021 13:54:44 +0100 Subject: [PATCH 4/4] Remove it.only from test case --- test/slow/rollup.deposit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/slow/rollup.deposit.test.ts b/test/slow/rollup.deposit.test.ts index 55f42616..1de1e17d 100644 --- a/test/slow/rollup.deposit.test.ts +++ b/test/slow/rollup.deposit.test.ts @@ -78,7 +78,7 @@ describe("Rollup Deposit", async function() { ); }); - it.only("reenqueue deposit subtree on rollback", async function() { + it("reenqueue deposit subtree on rollback", async function() { const feeReceiver = users.getUser(0).stateID; const { rollup, depositManager } = contracts; const { txs, signature } = txTransferFactory(