diff --git a/contracts/RollupNC.sol b/contracts/RollupNC.sol index b0b1f11..0b91d34 100644 --- a/contracts/RollupNC.sol +++ b/contracts/RollupNC.sol @@ -36,6 +36,7 @@ contract RollupNC { event RegisteredToken(uint256 tokenType, address tokenContract); event RequestDeposit(uint256[2] pubkey, uint256 amount, uint256 tokenType); + event ConfirmDeposit(uint256 oldRoot, uint256 newRoot, uint8 numAdded); event UpdatedState(uint256 currentRoot, uint256 oldRoot, uint256 txRoot); event Withdraw(uint256[9] accountInfo, address recipient); @@ -60,7 +61,7 @@ contract RollupNC { uint256 _zero, uint256[] memory _zeroCache ) { - require(_depth[0] == _zeroCache.length, "Param size mismatch"); + require(_depth[0] + 1 == _zeroCache.length, "Param size mismatch"); // assign contract references usv = IVerifier(_addresses[0]); wsv = IVerifier(_addresses[1]); @@ -71,7 +72,7 @@ contract RollupNC { txDepth = _depth[1]; ZERO = _zero; zeroCache = _zeroCache; - currentRoot = zeroCache[balDepth - 1]; + currentRoot = zeroCache[balDepth]; coordinator = msg.sender; } @@ -103,7 +104,6 @@ contract RollupNC { uint256 depositHash = PoseidonT6.poseidon( [pubkey[0], pubkey[1], amount, uint256(0), tokenType] ); - pendingDeposits[depositQueueEnd] = depositHash; depositQueueEnd++; depositQueueSize++; @@ -144,13 +144,16 @@ contract RollupNC { "specified subtree is not empty" ); // insert multiple leafs (insert subtree) by computing new root + uint256 oldRoot = currentRoot; currentRoot = getRootFromProof( pendingDeposits[depositQueueStart], subtreePosition, subtreeProof ); removeDeposit(true); - depositQueueSize -= uint8(2**depositSubtreeHeight); + uint8 numAdded = uint8(2**depositSubtreeHeight); + depositQueueSize -= numAdded; + emit ConfirmDeposit(oldRoot, currentRoot, numAdded); return currentRoot; } @@ -291,8 +294,7 @@ contract RollupNC { // compute height uint8 _i = 0; // track insert index, should always be safe for (uint256 i = 1; i <= depositSubtreeHeight; i++) { - if ((depositQueueSize & (uint256(1) << i)) > 0) - _heights[_i++] = i; + if ((depositQueueSize & (uint256(1) << i)) > 0) _heights[_i++] = i; } // store leaves for (uint256 i = 0; i < num; i++) @@ -312,7 +314,7 @@ contract RollupNC { uint256 _leaf, uint256[] memory _position, uint256[] memory _proof - ) public pure returns (uint256) { + ) public view returns (uint256) { uint256 hash = _leaf; for (uint8 i = 0; i < _proof.length; i++) { if (_position[i] == 0) diff --git a/test/0.js b/test/0.js new file mode 100644 index 0000000..7363cbf --- /dev/null +++ b/test/0.js @@ -0,0 +1,142 @@ +const { deployments, ethers } = require('hardhat') +const { solidity } = require("ethereum-waffle"); +const { buildEddsa, buildPoseidon } = require('circomlibjs') +const chai = require("chai").use(solidity) +const { initializeContracts, generateAccounts, L2Account } = require('./utils') +const { IncrementalMerkleTree } = require('@zk-kit/incremental-merkle-tree'); +const { expect } = require('chai'); + +describe("Test rollup deposits", async () => { + let eddsa, poseidon, F; + let signers, accounts; + let rollup; + let zeroCache; + before(async () => { + + // initial + signers = await ethers.getSigners(); + poseidon = await buildPoseidon(); + eddsa = await buildEddsa(); + F = poseidon.F; + + // generate zero cache + const depths = [4, 2]; + zeroCache = [BigInt(0)]; + for (let i = 1; i <= depths[0]; i++) { + const root = zeroCache[i - 1]; + const internalNode = poseidon([root, root]) + zeroCache.push(F.toObject(internalNode)); + } + rollup = await initializeContracts(zeroCache); + // set accounts + accounts = await generateAccounts(poseidon, eddsa); + }) + describe('Deposits', async () => { + describe('Batch #1', async () => { + it('Deposit #0 (0 ADDRESS)', async () => { + // check deposit fn execution logic + const tx = rollup.deposit([0, 0], 0, 0, { from: accounts.coordinator.L1.address }); + await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs([0, 0], 0, 0); + // check deposit queue + const expectedRoot = L2Account.emptyRoot(poseidon); + const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[0]); + expect(expectedRoot).to.be.equal(depositRoot); + }) + it('Deposit #1 (COORDINATOR ADDRESS)', async () => { + // check deposit fn execution logic + const l2Pubkey = accounts.coordinator.L2.pubkey.map(point => F.toObject(point)); + const tx = rollup.deposit(l2Pubkey, 0, 0, { from: accounts.coordinator.L1.address }); + await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs(l2Pubkey, 0, 0); + // check deposit queue + const data = [...l2Pubkey, 0, 0, 0]; + const leafRoot = F.toObject(poseidon(data)); + const sibling = L2Account.emptyRoot(poseidon); + const expectedRoot = F.toObject(poseidon([sibling, leafRoot])); + const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[0]); + expect(expectedRoot).to.be.equal(depositRoot); + }) + it('Deposit #2 (Alice)', async () => { + // check deposit fn execution logic + const l2Pubkey = accounts.alice.L2.pubkey.map(point => F.toObject(point)); + const tx = rollup.connect(accounts.alice.L1).deposit(l2Pubkey, 20, 1, { value: 20 }); + await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs(l2Pubkey, 20, 1); + accounts.alice.L2.credit(BigInt(20)); + // check deposit queue + const expectedRoot = accounts.alice.L2.root; + const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[1]); + expect(expectedRoot).to.be.equal(depositRoot); + }) + it('Deposit #3 (Bob)', async () => { + // check deposit fn execution logic + const l2Pubkey = accounts.bob.L2.pubkey.map(point => F.toObject(point)); + const tx = rollup.connect(accounts.bob.L1).deposit(l2Pubkey, 15, 1, { value: 15 }); + await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs(l2Pubkey, 15, 1); + accounts.bob.L2.credit(BigInt(15)); + // check deposit queue + const coordinatorPubkey = accounts.coordinator.L2.pubkey.map(point => F.toObject(point)); + const coordinatorLeaf = F.toObject(poseidon([...coordinatorPubkey, 0, 0, 0])); + const sibling = F.toObject(poseidon([L2Account.emptyRoot(poseidon), coordinatorLeaf])) + const current = F.toObject(poseidon([accounts.alice.L2.root, accounts.bob.L2.root])); + const expectedRoot = F.toObject(poseidon([sibling, current])); + const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[0]); + expect(expectedRoot).to.be.equal(depositRoot); + }) + it('Process Batch #1 (4 new balance leaves)', async () => { + // construct expected values + const emptyLeaf = L2Account.emptyRoot(poseidon) + const coordinatorPubkey = accounts.coordinator.L2.pubkey.map(point => F.toObject(point)); + const coordinatorLeaf = F.toObject(poseidon([...coordinatorPubkey, 0, 0, 0])); + const expectTree = new IncrementalMerkleTree(poseidon, 4, 0); + expectTree.insert(emptyLeaf); + expectTree.insert(coordinatorLeaf); + expectTree.insert(accounts.alice.L2.root); + expectTree.insert(accounts.bob.L2.root); + const expected = { + oldRoot: zeroCache[zeroCache.length - 1], + newRoot: F.toObject(expectTree.root) + } + // construct transaction + const position = [0, 0]; + const proof = [zeroCache[2], zeroCache[3]]; + const tx = rollup.connect(accounts.coordinator.L1).processDeposits(2, position, proof); + // verify execution integrity + await expect(tx).to.emit(rollup, "ConfirmDeposit").withArgs( + expected.oldRoot, + expected.newRoot, + 4 + ); + }) + + }) + describe('Batch #2', async () => { + xit('Deposit #4 (Charlie)', async () => { + // check deposit fn execution logic + const l2Pubkey = accounts.charlie.L2.pubkey.map(point => F.toObject(point)); + const tx = rollup.connect(accounts.charlie.L1).deposit(l2Pubkey, 500, 1, { value: 500 }); + await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs(l2Pubkey, 500, 1); + accounts.charlie.L2.credit(BigInt(500)); + // check deposit queue + const expectedRoot = accounts.charlie.L2.root; + const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[0]); + expect(expectedRoot).to.be.equal(depositRoot); + }) + xit('Deposit #5 (David)', async () => { + // check deposit fn execution logic + const l2Pubkey = accounts.david.L2.pubkey.map(point => F.toObject(point)); + const tx = rollup.connect(accounts.david.L1).deposit(l2Pubkey, 499, 1, { value: 500 }); + await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs(l2Pubkey, 499, 1); + accounts.david.L2.credit(BigInt(499)); + // check deposit queue + const expectedRoot = F.toObject(poseidon[ + accounts.charlie.L2.root, + accounts.david.L2.root + ]) + const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[0]); + expect(expectedRoot).to.be.equal(depositRoot); + }) + xit('Process Batch #2 (2 new balance leaves', async () => { + + }) + }) + }) +}) \ No newline at end of file diff --git a/test/deposits.js b/test/deposits.js deleted file mode 100644 index 6c5498a..0000000 --- a/test/deposits.js +++ /dev/null @@ -1,213 +0,0 @@ -const { deployments, ethers } = require('hardhat') -const { solidity } = require("ethereum-waffle"); -const { buildEddsa, buildPoseidon } = require('circomlibjs') -const chai = require("chai").use(solidity) -const { initializeContracts, generateAccounts, L2Account } = require('./utils') -const { IncrementalMerkleTree } = require('@zk-kit/incremental-merkle-tree'); -const { expect } = require('chai'); - -describe("Test rollup deposits", async () => { - let eddsa, poseidon, F; - let signers, accounts; - let rollup; - before(async () => { - - // initial - signers = await ethers.getSigners(); - poseidon = await buildPoseidon(); - eddsa = await buildEddsa(); - F = poseidon.F; - - // generate zero cache - const depths = [4, 2]; - const zeroCache = [BigInt(0)]; - for (let i = 1; i < depths[0]; i++) { - const root = zeroCache[i - 1]; - const internalNode = poseidon([root, root]) - zeroCache.push(F.toObject(internalNode)); - } - rollup = await initializeContracts(zeroCache); - // set accounts - accounts = await generateAccounts(poseidon, eddsa); - }) - describe('Deposits', async () => { - describe('Batch #1', async () => { - it('Deposit #0 (0 ADDRESS)', async () => { - // check deposit fn execution logic - const tx = rollup.deposit([0, 0], 0, 0, { from: accounts.coordinator.L1.address }); - await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs([0, 0], 0, 0); - // check deposit queue - const expectedRoot = L2Account.emptyRoot(poseidon); - const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[0]); - expect(expectedRoot).to.be.equal(depositRoot); - }) - it('Deposit #1 (COORDINATOR ADDRESS)', async () => { - // check deposit fn execution logic - const l2Pubkey = accounts.coordinator.L2.pubkey.map(point => F.toObject(point)); - const tx = rollup.deposit(l2Pubkey, 0, 0, { from: accounts.coordinator.L1.address }); - await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs(l2Pubkey, 0, 0); - // check deposit queue - const data = [...l2Pubkey, 0, 0, 0]; - const leafRoot = F.toObject(poseidon(data)); - const sibling = L2Account.emptyRoot(poseidon); - const expectedRoot = F.toObject(poseidon([sibling, leafRoot])); - const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[0]); - expect(expectedRoot).to.be.equal(depositRoot); - }) - it('Deposit #2 (Alice)', async () => { - // check deposit fn execution logic - const l2Pubkey = accounts.alice.L2.pubkey.map(point => F.toObject(point)); - const tx = rollup.connect(accounts.alice.L1).deposit(l2Pubkey, 20, 1, { value: 20 }); - await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs(l2Pubkey, 20, 1); - accounts.alice.L2.credit(BigInt(20)); - // check deposit queue - const expectedRoot = accounts.alice.L2.root; - const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[1]); - expect(expectedRoot).to.be.equal(depositRoot); - }) - it('Deposit #3 (Bob)', async () => { - // check deposit fn execution logic - const l2Pubkey = accounts.bob.L2.pubkey.map(point => F.toObject(point)); - const tx = rollup.connect(accounts.bob.L1).deposit(l2Pubkey, 15, 1, { value: 15 }); - await expect(tx).to.emit(rollup, 'RequestDeposit').withArgs(l2Pubkey, 15, 1); - accounts.bob.L2.credit(BigInt(15)); - // check deposit queue - const coordinatorPubkey = accounts.coordinator.L2.pubkey.map(point => F.toObject(point)); - const coordinatorLeaf = F.toObject(poseidon([...coordinatorPubkey, 0, 0, 0])); - const sibling = F.toObject(poseidon([L2Account.emptyRoot(poseidon), coordinatorLeaf])) - const current = F.toObject(poseidon([accounts.alice.L2.root, accounts.bob.L2.root])); - const expectedRoot = F.toObject(poseidon([sibling, current])); - const depositRoot = F.toObject((await rollup.describeDeposits())._leaves[0]); - expect(expectedRoot).to.be.equal(depositRoot); - }) - it('Process Deposit Batch #1', async () => { - - }) - }) - - }) - xit('Make first 4 deposits', async () => { - // 1st deposit (0 address) - await (await rollup.deposit([0, 0], 0, 0, { from: accounts.coordinator.L1.address })).wait(); - const description = await rollup.describeDeposits(); - const q = F.toObject(poseidon([0, 0])); - console.log('expected: ', F.toObject(L2Account.emptyRoot(poseidon))); - // await (await rollup.deposit( - // accounts.coordinator.L2.getPubkey(), - // 0, - // 0, - // { from: accounts.coordinator.L1.address } - // )).wait() // coordinator address - - // await (await rollup.deposit( - // accounts.alice.L2.getPubkey(), - // 10, // num tokens in wei - // 1, // ether - // { value: 10, from: accounts.alice.L1.signer } - // )).wait() - // accounts.alice.L2.credit(BigInt(10)) - // await (await rollup.deposit( - // accounts.bob.L2.getPubkey(), - // 20, // num tokens in wei - // 1, // ether - // { value: 20, from: accounts.bob.L1.signer } - // )).wait() - // accounts.bob.L2.credit(BigInt(20)) - // // get leaves - // const leaves = [ - // L2Account.emptyRoot(poseidon), - // accounts.coordinator.L2.root, - // accounts.alice.L2.root, - // accounts.bob.L2.root - // ] - // // ensure deposit tree correctly reflects onchain/ offchain - // const description = await rollup.describeDeposits() - // const height = description._heights[0].toNumber(); - // const root = description._leaves[0]; - // const tree = new IncrementalMerkleTree( - // poseidon, - // description._heights[0].toNumber(), - // BigInt(0), - // height - // ) - // for (leaf of leaves) { - // tree.insert(leaf) - // } - // const expectedRoot = BigInt(`0x${Buffer.from(tree.root).toString('hex')}`) - // console.log('expected root: ', expectedRoot) - // console.log('on-chain root: ', BigInt(root.toString())) - - - }) - it('this is a test', async () => { - console.log('true') - }) -}) - - -// const rollupFixture = deployments.createFixture( -// async ({ deployments, ethers }, _) => { -// console.log('q') -// console.log('r') -// // get circomlibjs objects -// console.log('s') -// console.log('a') -// // generate zero cache -// const depths = [4, 2]; -// const zeroCache = [BigInt(0)]; -// for (let i = 1; i < depths[0]; i++) { -// const root = zeroCache[i - 1]; -// const internalNode = circomlibjs.poseidon([root, root]) -// zeroCache.push(BigInt(`0x${Buffer.from(internalNode).toString('hex')}`)); -// } -// console.log('b') -// // deploy poseidon contracts -// const poseidonT3ABI = poseidonContract.generateABI(2); -// const poseidonT3Bytecode = poseidonContract.createCode(2); -// const poseidonT3Factory = new ethers.ContractFactory(poseidonT3ABI, poseidonT3Bytecode, operator); -// const poseidonT3 = await poseidonT3Factory.deploy(); -// await poseidonT3.deployed(); -// const poseidonT6ABI = poseidonContract.generateABI(5); -// const poseidonT6Bytecode = poseidonContract.createCode(5); -// const poseidonT6Factory = new ethers.ContractFactory(poseidonT6ABI, poseidonT6Bytecode, operator); -// const poseidonT6 = await poseidonT6Factory.deploy(); -// await poseidonT6.deployed(); -// // deploy verifiers -// console.log('c') -// const { address: usvAddress } = await deployments.deploy('UpdateStateVerifier', { -// from: operator.address, -// log: true -// }) -// const { address: wsvAddress } = await deployments.deploy('WithdrawSignatureVerifier', { -// from: operator.address, -// log: true -// }) - -// // deploy token registry -// const { address: registryAddress } = await deployments.deploy('TokenRegistry', { -// from: operator.address, -// log: true -// }) - -// // deploy rollup contract -// const { address: rollupAddress } = await deployments.deploy('RollupNC', { -// from: operator.address, -// args: [ -// [usvAddress, wsvAddress, registryAddress], -// depths, -// 0, -// zeroCache -// ], -// libraries: { -// PoseidonT3: poseidonT3.address, -// PoseidonT6: poseidonT6.address -// } -// }) -// return { -// poseidon, -// eddsa, -// rollupAddress, -// registryAddress -// } -// } -// ) \ No newline at end of file diff --git a/test/utils/accounts.js b/test/utils/accounts.js index ab9594e..2b98fde 100644 --- a/test/utils/accounts.js +++ b/test/utils/accounts.js @@ -1,5 +1,4 @@ const crypto = require('crypto'); -const ZERO = BigInt(0); /** * @title Offchain (EdDSA) account state @@ -119,6 +118,6 @@ module.exports = class L2Account { * @return {bigint[2]} - the account pubkey */ getPubkey() { - return this.pubkey.map(point => BigInt(`0x${Buffer.from(point).toString('hex')}`)); + return this.pubkey.map(point => this.F.toObject(point)); } } \ No newline at end of file diff --git a/test/utils/coordinator.js b/test/utils/coordinator.js new file mode 100644 index 0000000..0c277aa --- /dev/null +++ b/test/utils/coordinator.js @@ -0,0 +1,35 @@ +const { IncrementalMerkleTree } = require('@zk-kit/incremental-merkle-tree'); + +module.exports = class L2Coordinator { + + /// CIRCOMLIBJS HELPERS /// + poseidon; // Poseidon Hasher Object + // eddsa; // EdDSA Signing/ Verification w. stored private key + F; // Curve object from ffjavascript + + /// ON-CHAIN STATE /// + signer; // signer that is RollupNC coordinator + contract; // RollupNC.sol on-chain + + /// ACCOUNT STATE /// + balanceTree; // Incremental Merkle Tree (used during updates) + + /// ACCOUNT CRYPTO /// + // root; // bigint + + /** + * Create new L2 Coordinator Object + * + * @param _poseidon - the circomlibjs poseidon hasher object + * @param _contract - the RollupNC contract deployed on-chain + * @param _signer - the ethers wallet capable of acting as coordinator in _contract + */ + constructor(_poseidon, _contract, _signer) { + // assign helpers + this.poseidon = _poseidon; + this.contract = _contract; + this.signer = _signer; + } + + +} \ No newline at end of file