diff --git a/packages/library/src/index.ts b/packages/library/src/index.ts index 98d83302..83ad42af 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 4bb709c2..38bc988e 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 f56f8ec9..764a6f94 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 00000000..4918b91f --- /dev/null +++ b/packages/sdk/test/bridgeContract/bridge-contract.test.ts @@ -0,0 +1,176 @@ +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, + TokenId, + Field, + UInt64, + Poseidon, + Provable, + method, +} from "o1js"; +import { container } from "tsyringe"; +import { Withdrawal, WithdrawalMessageProcessor } from "@proto-kit/library"; +import { + InMemoryLinkedLeafStore, + InMemoryMerkleTreeStorage, + LinkedMerkleTree, + noop, +} from "@proto-kit/common"; + +class MockSettlementContract + extends SmartContract + implements Pick +{ + @state(Field) root = State(Field(100)); + + public assertStateRoot(root: Field): AccountUpdate { + return this.self; + } + + @method + public async test() { + noop(); + } +} + +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 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 tx = await Mina.transaction(chain.testAccounts[0], async () => { + AccountUpdate.fundNewAccount(chain.testAccounts[0], 2); + + await settlement.deploy(vkSettlement); + + const accountUpdate = await contract.deployProvable( + vk.verificationKey, + 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()); + + 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(); + + console.log(proven3.proofs.map((p) => p?.toJSON())); + }, 300000); +});