Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/miden-agglayer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ miden-protocol = { workspace = true }
miden-standards = { workspace = true }
miden-utils-sync = { workspace = true }

# Third-party dependencies
primitive-types = { workspace = true }

[dev-dependencies]
miden-agglayer = { features = ["testing"], path = "." }

Expand Down
53 changes: 39 additions & 14 deletions crates/miden-agglayer/asm/bridge/agglayer_faucet.masm
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const LEAF_DATA_KEY_MEM_ADDR = 704
const OUTPUT_NOTE_DATA_MEM_ADDR = 708
const CLAIM_NOTE_DATA_MEM_ADDR = 712

const OUTPUT_NOTE_INPUTS_MEM_ADDR = 0
const OUTPUT_NOTE_STORAGE_MEM_ADDR = 0
const OUTPUT_NOTE_TAG_MEM_ADDR = 574
const OUTPUT_NOTE_SERIAL_NUM_MEM_ADDR = 568
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 = 549
Expand All @@ -48,6 +48,16 @@ const DESTINATION_ADDRESS_2 = 546
const DESTINATION_ADDRESS_3 = 547
const DESTINATION_ADDRESS_4 = 548

# Memory locals in claim
const CLAIM_PREFIX_MEM_LOC = 5
const CLAIM_SUFFIX_MEM_LOC = 6
const CLAIM_AMOUNT_MEM_LOC_0 = 0
const CLAIM_AMOUNT_MEM_LOC_1 = 4

# Memory locals in build_p2id_output_note
const BUILD_P2ID_AMOUNT_MEM_LOC_0 = 0
const BUILD_P2ID_AMOUNT_MEM_LOC_1 = 4

# P2ID output note constants
const P2ID_NOTE_NUM_STORAGE_ITEMS = 2
const OUTPUT_NOTE_TYPE_PUBLIC = 1
Expand Down Expand Up @@ -163,11 +173,19 @@ end
#! It reads the destination account ID, amount, and other note parameters from memory to construct
#! the output note.
#!
#! Inputs: []
#! Inputs: [prefix, suffix, AMOUNT[0], AMOUNT[1]]
#! Outputs: []
#!
#! Note: This procedure will be refactored in a follow-up to use leaf data to build the output note.
@locals(8)
proc build_p2id_output_note
# write destination account id into memory for use in note::build_recipient
push.OUTPUT_NOTE_STORAGE_MEM_ADDR add.1 mem_store mem_store.OUTPUT_NOTE_STORAGE_MEM_ADDR

# store amount in memory locals for use in faucets::distribute
loc_storew_be.BUILD_P2ID_AMOUNT_MEM_LOC_0 dropw loc_storew_be.BUILD_P2ID_AMOUNT_MEM_LOC_1 dropw
# => [pad(16)]

# Build P2ID output note
procref.::miden::standards::notes::p2id::main
# => [SCRIPT_ROOT]
Expand All @@ -178,15 +196,8 @@ proc build_p2id_output_note
push.P2ID_NOTE_NUM_STORAGE_ITEMS
# => [note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT]

push.OUTPUT_NOTE_INPUTS_MEM_ADDR
# => [storage_ptr = 0, note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT]

exec.get_destination_account_id_data
# => [prefix, suffix]

# Write destination account id into memory
mem_store.1 mem_store.0
# => []
push.OUTPUT_NOTE_STORAGE_MEM_ADDR
# => [storage_ptr, note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT]

exec.note::build_recipient
# => [RECIPIENT]
Expand All @@ -197,10 +208,9 @@ proc build_p2id_output_note
mem_load.OUTPUT_NOTE_TAG_MEM_ADDR
# => [tag, RECIPIENT]

exec.get_raw_claim_amount
# => [AMOUNT[1], AMOUNT[0], tag, note_type, RECIPIENT]

# TODO: implement scale down logic; stubbed out for now
padw loc_loadw_be.BUILD_P2ID_AMOUNT_MEM_LOC_1 padw loc_loadw_be.BUILD_P2ID_AMOUNT_MEM_LOC_0
# => [AMOUNT[0], AMOUNT[1], tag, note_type, RECIPIENT]
exec.asset_conversion::scale_u256_to_native_amount
# => [amount, tag, note_type, RECIPIENT]

Expand Down Expand Up @@ -252,11 +262,22 @@ end
#! - any of the validations in faucets::distribute fail.
#!
#! Invocation: call
@locals(10) # 2 for prefix and suffix, 8 for amount
pub proc claim
# Check AdviceMap values hash to keys & write CLAIM inputs & DATA_KEYs to global memory
exec.batch_pipe_double_words
# => [pad(16)]

# validate_claim will overwrite memory in-place, so we need to load the account and amount
# before calling validate_claim and store it in memory locals
exec.get_destination_account_id_data
loc_store.CLAIM_PREFIX_MEM_LOC loc_store.CLAIM_SUFFIX_MEM_LOC
# => [pad(16)]

exec.get_raw_claim_amount
loc_storew_be.CLAIM_AMOUNT_MEM_LOC_0 dropw loc_storew_be.CLAIM_AMOUNT_MEM_LOC_1 dropw
# => [pad(16)]

# VALIDATE CLAIM
mem_loadw_be.PROOF_DATA_KEY_MEM_ADDR
# => [PROOF_DATA_KEY, pad(12)]
Expand All @@ -269,6 +290,10 @@ pub proc claim
# => [pad(16)]

# Create P2ID output note
loc_loadw_be.CLAIM_AMOUNT_MEM_LOC_1 swapw loc_loadw_be.CLAIM_AMOUNT_MEM_LOC_0
# => [AMOUNT[0], AMOUNT[1], pad(8)]
loc_load.CLAIM_SUFFIX_MEM_LOC loc_load.CLAIM_PREFIX_MEM_LOC
# => [prefix, suffix, AMOUNT[0], AMOUNT[1], pad(8)]
exec.build_p2id_output_note
# => [pad(16)]
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"amount": "0x00000000000000000000000000000000000000000000000000005af3107a4000",
"amount": 100000000000000,
"destination_address": "0x00000000b0E79c68cafC54802726C6F102Cca300",
"destination_network": 20,
"global_exit_root": "0xe1cbfbde30bd598ee9aa2ac913b60d53e3297e51ed138bf86c500dd7d2391e7d",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"amount": "0x0000000000000000000000000000000000000000000000001bc16d674ec80000",
"amount": 2000000000000000000,
"destination_address": "0xD9b20Fe633b609B01081aD0428e81f8Dd604F5C5",
"destination_network": 7,
"leaf_type": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ contract ClaimAssetTestVectors is Test, DepositContractV2 {
vm.serializeAddress(obj, "origin_token_address", originTokenAddress);
vm.serializeUint(obj, "destination_network", destinationNetwork);
vm.serializeAddress(obj, "destination_address", destinationAddress);
vm.serializeBytes32(obj, "amount", bytes32(amount));
vm.serializeUint(obj, "amount", amount);
vm.serializeBytes32(obj, "metadata_hash", metadataHash);
string memory json = vm.serializeBytes32(obj, "leaf_value", leafValue);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,13 @@ contract LeafValueTestVectors is Test, DepositContractV2 {
);

// Serialize to JSON
// Note: amount is serialized as bytes32 (hex string) to properly handle u256 values
string memory obj = "root";
vm.serializeUint(obj, "leaf_type", leafType);
vm.serializeUint(obj, "origin_network", originNetwork);
vm.serializeAddress(obj, "origin_token_address", originTokenAddress);
vm.serializeUint(obj, "destination_network", destinationNetwork);
vm.serializeAddress(obj, "destination_address", destinationAddress);
vm.serializeBytes32(obj, "amount", bytes32(amount));
vm.serializeUint(obj, "amount", amount);
vm.serializeBytes32(obj, "metadata_hash", metadataHash);
string memory json = vm.serializeBytes32(obj, "leaf_value", leafValue);

Expand Down
20 changes: 20 additions & 0 deletions crates/miden-agglayer/src/eth_types/amount.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;

use miden_core_lib::handlers::bytes_to_packed_u32_felts;
use miden_protocol::Felt;
use primitive_types::U256;

// ================================================================================================
// ETHEREUM AMOUNT
Expand All @@ -14,12 +16,30 @@ use miden_protocol::Felt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EthAmount([u8; 32]);

/// Error type for parsing an [`EthAmount`] from a decimal string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EthAmountError(String);

impl EthAmount {
/// Creates an [`EthAmount`] from a 32-byte array.
pub fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}

/// Creates an [`EthAmount`] from a decimal (uint) string.
///
/// The string should contain only ASCII decimal digits (e.g. `"2000000000000000000"`).
/// The value is stored as a 32-byte big-endian array, matching the Solidity uint256 layout.
///
/// # Errors
///
/// Returns [`EthAmountError`] if the string is empty, contains non-digit characters,
/// or represents a value that overflows uint256.
pub fn from_uint_str(s: &str) -> Result<Self, EthAmountError> {
let value = U256::from_dec_str(s).map_err(|e| EthAmountError(e.to_string()))?;
Ok(Self(value.to_big_endian()))
}

/// Converts the amount to a vector of field elements for note storage.
///
/// Each u32 value in the amount array is converted to a [`Felt`].
Expand Down
2 changes: 1 addition & 1 deletion crates/miden-agglayer/src/eth_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ pub mod global_index;
pub mod metadata_hash;

pub use address::EthAddressFormat;
pub use amount::EthAmount;
pub use amount::{EthAmount, EthAmountError};
pub use global_index::{GlobalIndex, GlobalIndexError};
pub use metadata_hash::MetadataHash;
9 changes: 8 additions & 1 deletion crates/miden-agglayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,14 @@ pub use claim_note::{
SmtNode,
create_claim_note,
};
pub use eth_types::{EthAddressFormat, EthAmount, GlobalIndex, GlobalIndexError, MetadataHash};
pub use eth_types::{
EthAddressFormat,
EthAmount,
EthAmountError,
GlobalIndex,
GlobalIndexError,
MetadataHash,
};
pub use update_ger_note::UpdateGerNote;

// AGGLAYER NOTE SCRIPTS
Expand Down
22 changes: 21 additions & 1 deletion crates/miden-testing/tests/agglayer/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ use miden_protocol::utils::sync::LazyLock;
use miden_tx::utils::hex_to_bytes;
use serde::Deserialize;

// SERDE HELPERS
// ================================================================================================

/// Deserializes a JSON value that may be either a number or a string into a `String`.
///
/// Foundry's `vm.serializeUint` outputs JSON numbers for uint256 values.
/// This deserializer accepts both `"100"` (string) and `100` (number) forms.
fn deserialize_uint_to_string<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::String(s) => Ok(s),
serde_json::Value::Number(n) => Ok(n.to_string()),
_ => Err(serde::de::Error::custom("expected a number or string for amount")),
}
}

// TEST VECTOR STRUCTURES
// ================================================================================================

Expand Down Expand Up @@ -61,6 +80,7 @@ pub struct LeafValueVector {
pub origin_token_address: String,
pub destination_network: u32,
pub destination_address: String,
#[serde(deserialize_with = "deserialize_uint_to_string")]
pub amount: String,
pub metadata_hash: String,
#[allow(dead_code)]
Expand All @@ -77,7 +97,7 @@ impl LeafValueVector {
destination_network: self.destination_network,
destination_address: EthAddressFormat::from_hex(&self.destination_address)
.expect("valid destination address hex"),
amount: EthAmount::new(hex_to_bytes(&self.amount).expect("valid amount hex")),
amount: EthAmount::from_uint_str(&self.amount).expect("valid amount uint string"),
metadata_hash: MetadataHash::new(
hex_to_bytes(&self.metadata_hash).expect("valid metadata hash hex"),
),
Expand Down
Loading