Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@
path = crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git
branch = release-v4.9
[submodule "crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts"]
path = crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts.git
branch = release-v5.0
[submodule "crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts-upgradeable5"]
path = crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts-upgradeable5
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git
branch = release-v5.0
Comment on lines +11 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it seems we have a v4 and v5 of the same submodule, is it possible to just have the latest version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like some of the contracts which we use have both v5 and v4 dependencies. I struggled a few hours to make it work, and AFAIU, it is the only configuration which makes it possible. Removing any of them results in a compilation error.

98 changes: 96 additions & 2 deletions crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use miden::protocol::output_note::ATTACHMENT_KIND_NONE
use miden::protocol::tx
use miden::standards::note_tag
use miden::standards::attachments::network_account_target
use miden::protocol::native_account
use miden::protocol::active_account
use miden::standards::note::execution_hint::ALWAYS

# TYPE ALIASES
Expand All @@ -35,6 +37,14 @@ const ERR_INVALID_CLAIM_PROOF = "invalid claim proof"
# CONSTANTS
# =================================================================================================

# Storage slots
# -------------------------------------------------------------------------------------------------

# Storage slot constants for the CGI (claimed global index) chain hash.
# It is stored in two separate value slots.
const CGI_CHAIN_HASH_LO_SLOT_NAME = word("miden::agglayer::bridge::cgi_chain_hash_lo")
const CGI_CHAIN_HASH_HI_SLOT_NAME = word("miden::agglayer::bridge::cgi_chain_hash_hi")

# Data sizes
# -------------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -167,6 +177,7 @@ const CREATE_MINT_FAUCET_SUFFIX_LOC = 1
#! - the Merkle proof for the provided leaf-index tuple against the computed GER is invalid.
#!
#! Invocation: call
@locals(8) # leaf value
pub proc verify_leaf_bridge
# get the leaf value. We have all the necessary leaf data in the advice map
exec.get_leaf_value
Expand All @@ -175,9 +186,23 @@ pub proc verify_leaf_bridge
movupw.3 dropw
# => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)]

# save the leaf value to the local memory to reuse it during the computation of the CGI chain
# hash
Comment on lines +189 to +190
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
# save the leaf value to the local memory to reuse it during the computation of the CGI chain
# hash
# save the leaf value to the local memory to reuse it during the computation
# of the CGI hash chain

loc_storew_le.0 swapw
loc_storew_le.4 swapw
# => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)]

# delegate proof verification
exec.verify_leaf
# => [pad(16)]

# load the pointer to the leaf value back to the stack
locaddr.0
# => [leaf_value_ptr, pad(16)]

# update the CGI chain hash
exec.update_cgi_chain_hash
# => [pad(16)]
end

#! Assert the global index is valid for a mainnet deposit.
Expand Down Expand Up @@ -468,8 +493,7 @@ proc verify_leaf
# => [LEAF_VALUE[8]]

# 3. load global index from memory
padw mem_loadw_le.GLOBAL_INDEX_PTR
padw push.GLOBAL_INDEX_PTR add.4 mem_loadw_le swapw
push.GLOBAL_INDEX_PTR exec.utils::mem_load_double_word
# => [GLOBAL_INDEX[8], LEAF_VALUE[8]]

# to see if we're dealing with a deposit from mainnet or from a rollup, process the global index
Expand Down Expand Up @@ -830,3 +854,73 @@ proc calculate_root(
padw loc_loadw_le.CUR_HASH_HI_LOCAL padw loc_loadw_le.CUR_HASH_LO_LOCAL
# => [ROOT_LO, ROOT_HI]
end

#! Updates the claimed global index (CGI) chain hash by recomputing it and storing into the
#! corresponding storage slot.
#!
#! The resulting hash is computed as a sequential hash of leaf value, global index, and previous
#! value of the CGI chain hash:
#! NEW_CGI_CHAIN_HASH[8] = Keccak256(OLD_CGI_CHAIN_HASH[8], Keccak256(GLOBAL_INDEX[8], LEAF_VALUE[8]))
#!
#! Inputs: [leaf_value_ptr]
#! Outputs: []
#!
#! Invocation: exec
proc update_cgi_chain_hash
# load the required CGI chain data values onto the stack
exec.load_cgi_chain_hash_data
# => [GLOBAL_INDEX[8], LEAF_VALUE[8], OLD_CGI_CHAIN_HASH[8]]

# compute the new CGI chain hash
exec.keccak256::merge swapdw
# => [OLD_CGI_CHAIN_HASH[8], Keccak256(GLOBAL_INDEX, LEAF_VALUE)]

exec.keccak256::merge
# => [NEW_CGI_CHAIN_HASH[8]]

# store the new CGI chain hash
exec.store_cgi_chain_hash
# => []
end

#! Loads the old CGI chain hash, the leaf value, and the global index onto the stack as a
#! preparation for the new CGI chain hash computation.
#!
#! Inputs: [leaf_value_ptr]
#! Outputs: [GLOBAL_INDEX[8], LEAF_VALUE[8], OLD_CGI_CHAIN_HASH[8]]
#!
#! Invocation: exec
proc load_cgi_chain_hash_data
# load the old CGI chain hash onto the stack
push.CGI_CHAIN_HASH_HI_SLOT_NAME[0..2]
exec.active_account::get_item
# => [OLD_CGI_CHAIN_HASH_HI, leaf_value_ptr]

push.CGI_CHAIN_HASH_LO_SLOT_NAME[0..2]
exec.active_account::get_item
# => [OLD_CGI_CHAIN_HASH[8], leaf_value_ptr]

# load the leaf value onto the stack
movup.8 exec.utils::mem_load_double_word
# => [LEAF_VALUE[8], OLD_CGI_CHAIN_HASH[8]]

# load the global index onto the stack
push.GLOBAL_INDEX_PTR exec.utils::mem_load_double_word
# => [GLOBAL_INDEX[8], LEAF_VALUE[8], OLD_CGI_CHAIN_HASH[8]]
end

#! Stores the computed global index (CGI) chain hash into the corresponding storage slots.
#!
#! Inputs: [NEW_CGI_CHAIN_HASH_LO, NEW_CGI_CHAIN_HASH_HI]
#! Outputs: []
#!
#! Invocation: exec
proc store_cgi_chain_hash
push.CGI_CHAIN_HASH_LO_SLOT_NAME[0..2]
exec.native_account::set_item dropw
# => [NEW_CGI_CHAIN_HASH_HI]

push.CGI_CHAIN_HASH_HI_SLOT_NAME[0..2]
exec.native_account::set_item dropw
# => []
end
12 changes: 12 additions & 0 deletions crates/miden-agglayer/solidity-compat/foundry.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@
"rev": "1801b0541f4fda118a10798fd3486bb7051c5dd6"
}
},
"lib/openzeppelin-contracts": {
"branch": {
"name": "release-v5.0",
"rev": "dbb6104ce834628e473d2173bbc9d47f81a9eec3"
}
},
"lib/openzeppelin-contracts-upgradeable": {
"branch": {
"name": "release-v4.9",
"rev": "2d081f24cac1a867f6f73d512f2022e1fa987854"
}
},
"lib/openzeppelin-contracts-upgradeable5": {
"branch": {
"name": "release-v5.0",
"rev": "723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1"
}
}
}
4 changes: 3 additions & 1 deletion crates/miden-agglayer/solidity-compat/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ libs = ["lib"]
optimizer = true
optimizer_runs = 200
out = "out"
solc = "0.8.20"
solc = "0.8.28"
src = "src"
via_ir = true

remappings = [
"@agglayer/=lib/agglayer-contracts/contracts/",
"@openzeppelin/contracts-upgradeable4/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/contracts-upgradeable5/=lib/openzeppelin-contracts-upgradeable5/contracts/",
"@openzeppelin/contracts5/=lib/openzeppelin-contracts/contracts/",
]

# Emit extra output for test vector generation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"amount": 100000000000000000000,
"amount": "100000000000000000000",
"claimed_global_index_hash_chain": "0xbce0afc98c69ea85e9cfbf98c87c58a77c12d857551f1858530341392f70c22d",
"deposit_count": 1,
"description": "L1 bridgeAsset transaction test vectors with valid Merkle proofs",
"destination_address": "0x00000000AA0000000000bb000000cc000000Dd00",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"amount": 100000000000000,
"claimed_global_index_hash_chain": "0xd2bb2f0231ee9ea0c88e89049bea6dbcf7dd96a1015ca9e66ab38ef3c8dc928e",
"destination_address": "0x00000000b0E79c68cafC54802726C6F102Cca300",
"destination_network": 20,
"global_exit_root": "0xe1cbfbde30bd598ee9aa2ac913b60d53e3297e51ed138bf86c500dd7d2391e7d",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,33 @@
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "@agglayer/v2/lib/DepositContractV2.sol";
import "@agglayer/v2/sovereignChains/BridgeL2SovereignChain.sol";
import "@agglayer/lib/GlobalExitRootLib.sol";
import "@agglayer/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol";
import "./DepositContractTestHelpers.sol";

contract MockGlobalExitRootManagerLocal is IBasePolygonZkEVMGlobalExitRoot {
mapping(bytes32 => uint256) public override globalExitRootMap;

function updateExitRoot(bytes32) external override {}

function setGlobalExitRoot(bytes32 globalExitRoot) external {
globalExitRootMap[globalExitRoot] = block.number;
}
}

/**
* @title ClaimAssetTestVectorsLocalTx
* @notice Test contract that generates test vectors for an L1 bridgeAsset transaction.
* This simulates calling bridgeAsset() on the PolygonZkEVMBridgeV2 contract
* and captures all relevant data including VALID Merkle proofs.
* Uses BridgeL2SovereignChain to get the authoritative claimedGlobalIndexHashChain.
*
* Run with: forge test -vv --match-contract ClaimAssetTestVectorsLocalTx
*
* The output can be used to verify Miden's ability to process L1 bridge transactions.
*/
contract ClaimAssetTestVectorsLocalTx is Test, DepositContractV2, DepositContractTestHelpers {
contract ClaimAssetTestVectorsLocalTx is Test, BridgeL2SovereignChain, DepositContractTestHelpers {
/**
* @notice Generates bridge asset test vectors with VALID Merkle proofs.
* Simulates a user calling bridgeAsset() to bridge tokens from L1 to Miden.
Expand Down Expand Up @@ -95,6 +107,36 @@ contract ClaimAssetTestVectorsLocalTx is Test, DepositContractV2, DepositContrac
// extracts it via uint32(globalIndex) in _verifyLeaf()
uint256 globalIndex = (uint256(1) << 64) | uint256(leafIndex);

// ====== COMPUTE CLAIMED GLOBAL INDEX HASH CHAIN ======
// Use the actual BridgeL2SovereignChain to compute the authoritative value

// Set up the global exit root manager
MockGlobalExitRootManagerLocal gerManager = new MockGlobalExitRootManagerLocal();
gerManager.setGlobalExitRoot(globalExitRoot);
globalExitRootManager = IBasePolygonZkEVMGlobalExitRoot(address(gerManager));

// Use a non-zero network ID to match sovereign-chain requirements
networkID = 10;

// Call _verifyLeafBridge to update claimedGlobalIndexHashChain
this.verifyLeafBridgeHarness(
smtProofLocal,
smtProofRollup,
globalIndex,
mainnetExitRoot,
rollupExitRoot,
leafType,
originNetwork,
originTokenAddress,
destinationNetwork,
destinationAddress,
amount,
metadataHash
);

// Read the updated claimedGlobalIndexHashChain
bytes32 claimedHashChain = claimedGlobalIndexHashChain;

// ====== SERIALIZE SMT PROOFS ======
_serializeProofs(obj, smtProofLocal, smtProofRollup);

Expand All @@ -115,6 +157,7 @@ contract ClaimAssetTestVectorsLocalTx is Test, DepositContractV2, DepositContrac
{
vm.serializeUint(obj, "deposit_count", depositCountValue);
vm.serializeBytes32(obj, "global_index", bytes32(globalIndex));
vm.serializeBytes32(obj, "claimed_global_index_hash_chain", claimedHashChain);
vm.serializeBytes32(obj, "local_exit_root", localExitRoot);
vm.serializeBytes32(obj, "mainnet_exit_root", mainnetExitRoot);
vm.serializeBytes32(obj, "rollup_exit_root", rollupExitRoot);
Expand All @@ -134,6 +177,39 @@ contract ClaimAssetTestVectorsLocalTx is Test, DepositContractV2, DepositContrac
}
}

/**
* @notice Harness function to call _verifyLeafBridge externally
*/
function verifyLeafBridgeHarness(
bytes32[32] calldata smtProofLocalExitRoot,
bytes32[32] calldata smtProofRollupExitRoot,
uint256 globalIndex,
bytes32 mainnetExitRoot,
bytes32 rollupExitRoot,
uint8 leafType,
uint32 originNetwork,
address originTokenAddress,
uint32 destinationNetwork,
address destinationAddress,
uint256 amount,
bytes32 metadataHash
) external {
_verifyLeafBridge(
smtProofLocalExitRoot,
smtProofRollupExitRoot,
globalIndex,
mainnetExitRoot,
rollupExitRoot,
leafType,
originNetwork,
originTokenAddress,
destinationNetwork,
destinationAddress,
amount,
metadataHash
);
}

/**
* @notice Helper function to serialize SMT proofs (avoids stack too deep)
* @param obj The JSON object key
Expand Down
Loading
Loading