From aba7f339243725bf0143751049156ec4f19d9ee5 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 27 Jan 2026 15:29:25 +0100 Subject: [PATCH 1/2] Added bridge-contract test --- packages/library/src/index.ts | 1 + .../protocol/src/prover/block/BlockProver.ts | 1 - .../settlement/contracts/BridgeContract.ts | 10 +- .../bridgeContract/bridge-contract.test.ts | 178 ++++++++++++++++++ 4 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 packages/sdk/test/bridgeContract/bridge-contract.test.ts diff --git a/packages/library/src/index.ts b/packages/library/src/index.ts index 98d833022..83ad42af0 100644 --- a/packages/library/src/index.ts +++ b/packages/library/src/index.ts @@ -4,6 +4,7 @@ export * from "./math/UInt64"; export * from "./math/UInt112"; export * from "./math/UInt224"; export * from "./protocol/VanillaProtocolModules"; +export * from "./protocol/WithdrawalMessageProcessor"; export * from "./runtime/Balances"; export * from "./runtime/VanillaRuntimeModules"; export * from "./runtime/Withdrawals"; diff --git a/packages/protocol/src/prover/block/BlockProver.ts b/packages/protocol/src/prover/block/BlockProver.ts index 4bb709c2b..38bc988e9 100644 --- a/packages/protocol/src/prover/block/BlockProver.ts +++ b/packages/protocol/src/prover/block/BlockProver.ts @@ -446,7 +446,6 @@ export class BlockProverProgrammable extends ZkProgrammable< const finalizeBlockProof = deferTransactionProof.or(deferSTProof).not(); // .or() // .or(state.bundleList.isEmpty().and(state.pendingSTBatches.isEmpty())); - // TODO This finalizes immediately if nothing happened - which we don't account for in tracer currently return this.computeOutput(publicInput, state, finalizeBlockProof); } diff --git a/packages/protocol/src/settlement/contracts/BridgeContract.ts b/packages/protocol/src/settlement/contracts/BridgeContract.ts index f56f8ec95..764a6f94c 100644 --- a/packages/protocol/src/settlement/contracts/BridgeContract.ts +++ b/packages/protocol/src/settlement/contracts/BridgeContract.ts @@ -70,7 +70,9 @@ export class BridgeContractContext { } export interface BridgeContractArgs { - SettlementContract: TypedClass & + SettlementContract: TypedClass< + Pick + > & typeof SmartContract; messageProcessors: OutgoingMessageProcessor[]; batchSize?: number; @@ -186,7 +188,9 @@ export abstract class BridgeContractBase messageType: args.messageType, value, }); + Provable.log("h", Poseidon.hash(MessageType.toFields(message))); return { + // TODO This doesn't make any sense tbh... messageType: args.messageType, result: processor.processMessage(value, { bridgeContract: { @@ -302,6 +306,10 @@ export abstract class BridgeContractBase } ); + Provable.log(message.hash); + Provable.log(path); + Provable.log(stateRoot); + args.witness .checkMembership(stateRoot, path, message.hash) .or(isDummy) diff --git a/packages/sdk/test/bridgeContract/bridge-contract.test.ts b/packages/sdk/test/bridgeContract/bridge-contract.test.ts new file mode 100644 index 000000000..505bfdfea --- /dev/null +++ b/packages/sdk/test/bridgeContract/bridge-contract.test.ts @@ -0,0 +1,178 @@ +import "reflect-metadata"; +import { + BridgeContract, + BridgeContractArgs, + BridgeContractContext, + BridgingSettlementContractType, + ContractArgsRegistry, + createMessageStruct, + OutgoingMessageArgument, + OutgoingMessageArgumentBatch, + OutgoingMessageKey, + Path, + PROTOKIT_FIELD_PREFIXES, +} from "@proto-kit/protocol"; +import { + AccountUpdate, + Mina, + PrivateKey, + SmartContract, + state, + State, + Permissions, + VerificationKey, + TokenId, + Field, + UInt64, + Poseidon, + Provable, +} from "o1js"; +import { container } from "tsyringe"; +import { Withdrawal, WithdrawalMessageProcessor } from "@proto-kit/library"; +import { + InMemoryLinkedLeafStore, + InMemoryMerkleTreeStorage, + LinkedMerkleTree, +} from "@proto-kit/common"; + +class MockSettlementContract + extends SmartContract + implements Pick +{ + @state(Field) root = State(Field(100)); + + public assertStateRoot(root: Field): AccountUpdate { + // this.root.requireNothing() + return this.self; + } +} + +const proofsEnabled = false; + +describe("bridging contract", () => { + it("setup", async () => { + container + .resolve(ContractArgsRegistry) + .addArgs("BridgeContract", { + SettlementContract: MockSettlementContract, + messageProcessors: [new WithdrawalMessageProcessor() as any], + }); + + const key1 = PrivateKey.random(); + const key2 = PrivateKey.random(); + + const settlement = new MockSettlementContract(key1.toPublicKey()); + const contract = new BridgeContract(key2.toPublicKey()); + + const chain = await Mina.LocalBlockchain({ proofsEnabled }); + Mina.setActiveInstance(chain); + const tx = await Mina.transaction(chain.testAccounts[0], async () => { + AccountUpdate.fundNewAccount(chain.testAccounts[0], 2); + + await settlement.deploy(); + + const accountUpdate = await contract.deployProvable( + VerificationKey.dummySync(), + false, + Permissions.default(), + key1.toPublicKey() + ); + accountUpdate.requireSignature(); + AccountUpdate.attachToTransaction(accountUpdate); + + AccountUpdate.createSigned(chain.testAccounts[0]).send({ + to: key1.toPublicKey(), + amount: 1e9, + }); + }); + + const proven = await tx + .sign([chain.testAccounts[0].key, key1, key2]) + .prove(); + const txId = await proven.send(); + await txId.wait(); + + const tree = new LinkedMerkleTree( + new InMemoryMerkleTreeStorage(), + new InMemoryLinkedLeafStore() + ); + const path = Path.fromKey( + PROTOKIT_FIELD_PREFIXES.OUTGOING_MESSAGE_BASE_PATH, + OutgoingMessageKey, + { + index: Field(0), + tokenId: contract.tokenId, + } + ); + const message = new Withdrawal({ + tokenId: TokenId.default, + amount: UInt64.from(1e9), + address: chain.testAccounts[1].key.toPublicKey(), + }); + const MessageType = createMessageStruct(Withdrawal); + tree.setLeaf( + path.toBigInt(), + Poseidon.hash( + MessageType.toFields({ messageType: Field(0), value: message }) + ).toBigInt() + ); + Provable.log( + "tree hash", + Poseidon.hash( + MessageType.toFields({ messageType: Field(0), value: message }) + ).toBigInt() + ); + + const tx2 = await Mina.transaction(chain.testAccounts[0], async () => { + await contract.updateStateRoot(tree.getRoot()); + }); + const proven2 = await tx2 + .sign([chain.testAccounts[0].key, key1, key2]) + .prove(); + const txId2 = await proven2.send(); + await txId2.wait(); + + container.resolve(BridgeContractContext).data = { + messageInputs: [[message]], + }; + + const treeWitness = tree.getReadWitness(path.toBigInt()); + // expect( + // treeWitness + // .checkMembership( + // tree.getRoot(), + // path, + // Poseidon.hash(Withdrawal.toFields(message)) + // ) + // .toBoolean() + // ).toBe(true); + + Provable.log(Poseidon.hash(Withdrawal.toFields(message))); + Provable.log(path); + Provable.log(tree.getRoot()); + + const tx3 = await Mina.transaction(chain.testAccounts[0], async () => { + const funded = await contract.rollupOutgoingMessages( + OutgoingMessageArgumentBatch.fromMessages([ + new OutgoingMessageArgument({ + messageType: Field(0), + witness: treeWitness, + }), + ]) + ); + + let numNewAccountsNumber = 0; + Provable.asProver(() => { + numNewAccountsNumber = parseInt(funded.toString(), 10); + }); + + // Pay account creation fees for internal token accounts + AccountUpdate.fundNewAccount(chain.testAccounts[0], numNewAccountsNumber); + }); + const proven3 = await tx3 + .sign([chain.testAccounts[0].key, key1, key2]) + .prove(); + const txId3 = await proven3.send(); + await txId3.wait(); + }); +}); From 7bdb601a07b91fc540d49512d2457462fae1778a Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 27 Jan 2026 18:21:06 +0100 Subject: [PATCH 2/2] Added compilation to bridge-contract test --- .../bridgeContract/bridge-contract.test.ts | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/sdk/test/bridgeContract/bridge-contract.test.ts b/packages/sdk/test/bridgeContract/bridge-contract.test.ts index 505bfdfea..4918b91f7 100644 --- a/packages/sdk/test/bridgeContract/bridge-contract.test.ts +++ b/packages/sdk/test/bridgeContract/bridge-contract.test.ts @@ -20,12 +20,12 @@ import { state, State, Permissions, - VerificationKey, TokenId, Field, UInt64, Poseidon, Provable, + method, } from "o1js"; import { container } from "tsyringe"; import { Withdrawal, WithdrawalMessageProcessor } from "@proto-kit/library"; @@ -33,6 +33,7 @@ import { InMemoryLinkedLeafStore, InMemoryMerkleTreeStorage, LinkedMerkleTree, + noop, } from "@proto-kit/common"; class MockSettlementContract @@ -42,9 +43,13 @@ class MockSettlementContract @state(Field) root = State(Field(100)); public assertStateRoot(root: Field): AccountUpdate { - // this.root.requireNothing() return this.self; } + + @method + public async test() { + noop(); + } } const proofsEnabled = false; @@ -61,18 +66,22 @@ describe("bridging contract", () => { const key1 = PrivateKey.random(); const key2 = PrivateKey.random(); + const chain = await Mina.LocalBlockchain({ proofsEnabled }); + Mina.setActiveInstance(chain); + + const vkSettlement = await MockSettlementContract.compile(); + const vk = await BridgeContract.compile(); + const settlement = new MockSettlementContract(key1.toPublicKey()); const contract = new BridgeContract(key2.toPublicKey()); - const chain = await Mina.LocalBlockchain({ proofsEnabled }); - Mina.setActiveInstance(chain); const tx = await Mina.transaction(chain.testAccounts[0], async () => { AccountUpdate.fundNewAccount(chain.testAccounts[0], 2); - await settlement.deploy(); + await settlement.deploy(vkSettlement); const accountUpdate = await contract.deployProvable( - VerificationKey.dummySync(), + vk.verificationKey, false, Permissions.default(), key1.toPublicKey() @@ -137,19 +146,6 @@ describe("bridging contract", () => { }; const treeWitness = tree.getReadWitness(path.toBigInt()); - // expect( - // treeWitness - // .checkMembership( - // tree.getRoot(), - // path, - // Poseidon.hash(Withdrawal.toFields(message)) - // ) - // .toBoolean() - // ).toBe(true); - - Provable.log(Poseidon.hash(Withdrawal.toFields(message))); - Provable.log(path); - Provable.log(tree.getRoot()); const tx3 = await Mina.transaction(chain.testAccounts[0], async () => { const funded = await contract.rollupOutgoingMessages( @@ -174,5 +170,7 @@ describe("bridging contract", () => { .prove(); const txId3 = await proven3.send(); await txId3.wait(); - }); + + console.log(proven3.proofs.map((p) => p?.toJSON())); + }, 300000); });