Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
53c326f
feat: implement u256 agglayer asset scaling procedure
partylikeits1983 Dec 5, 2025
44ef81c
refactor: change doc comment example
partylikeits1983 Dec 5, 2025
c917836
fix: cargo.toml fmt
partylikeits1983 Dec 5, 2025
f325b69
Update crates/miden-lib/asm/agglayer/account_components/asset_convers…
partylikeits1983 Dec 5, 2025
7f07013
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Dec 5, 2025
5f34f6b
Update crates/miden-lib/src/agglayer/utils.rs
partylikeits1983 Dec 5, 2025
75a8870
Update crates/miden-lib/asm/agglayer/account_components/asset_convers…
partylikeits1983 Dec 5, 2025
363af83
Update crates/miden-lib/asm/agglayer/account_components/asset_convers…
partylikeits1983 Dec 5, 2025
2607a68
refactor: fix felts_to_u256_bytes & refactor masm comments
partylikeits1983 Dec 5, 2025
b1d9ac3
refactor: clean up masm doc comment
partylikeits1983 Dec 5, 2025
ffa0b7e
Merge branch 'agglayer' into ajl-asset-conversion-agglayer
partylikeits1983 Dec 8, 2025
cb5af54
Update crates/miden-lib/asm/agglayer/account_components/asset_convers…
partylikeits1983 Dec 8, 2025
17870c2
refactor: add more asset conversion tests
partylikeits1983 Dec 8, 2025
02ca823
refactor: use u32assert, add masm comments, & improve tests
partylikeits1983 Dec 9, 2025
f7eb5db
refactor: add basic examples test
partylikeits1983 Dec 9, 2025
642f7da
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Dec 9, 2025
d41e591
Update crates/miden-lib/asm/agglayer/account_components/asset_convers…
partylikeits1983 Dec 11, 2025
3167d82
refactor: address test name & fix tests to call new proc name
partylikeits1983 Dec 11, 2025
c4fef2a
chore: merge agglayer into ajl-asset-conversion-agglayer
partylikeits1983 Dec 11, 2025
e7bb55f
fix: taplo fmt
partylikeits1983 Dec 11, 2025
38d4032
chore: merge agglayer into ajl-asset-conversion-agglayer
partylikeits1983 Dec 12, 2025
79129a5
refactor: rename procedure & add comments
partylikeits1983 Dec 14, 2025
a2ccbed
refactor: use u64::overflowing_mul
partylikeits1983 Dec 14, 2025
4acf1a8
refactor: use asset_conversion_library fn
partylikeits1983 Dec 14, 2025
ce42f7f
Merge branch 'agglayer' into ajl-asset-conversion-agglayer
partylikeits1983 Dec 14, 2025
7c0fd06
refactor: update comment in scale_native_amount_to_u256
partylikeits1983 Dec 14, 2025
301124c
refactor: update to return little-endian u256
partylikeits1983 Dec 15, 2025
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
50 changes: 50 additions & 0 deletions Cargo.lock

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

15 changes: 8 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ miden-utils-sync = { default-features = false, version = "0.19" }
miden-verifier = { default-features = false, version = "0.19" }

# External dependencies
anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" }
assert_matches = { default-features = false, version = "1.5" }
rand = { default-features = false, version = "0.9" }
rand_chacha = { default-features = false, version = "0.9" }
rstest = { version = "0.26" }
thiserror = { default-features = false, version = "2.0" }
tokio = { default-features = false, features = ["sync"], version = "1" }
anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" }
assert_matches = { default-features = false, version = "1.5" }
primitive-types = { default-features = false, version = "0.14" }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Importing U256 type.

rand = { default-features = false, version = "0.9" }
rand_chacha = { default-features = false, version = "0.9" }
rstest = { version = "0.26" }
thiserror = { default-features = false, version = "2.0" }
tokio = { default-features = false, features = ["sync"], version = "1" }
106 changes: 106 additions & 0 deletions crates/miden-lib/asm/agglayer/account_components/asset_conversion.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use.std::math::u64
use.std::word

# CONSTANTS
# =================================================================================================

const.MAX_SCALING_FACTOR=18

# ERRORS
# =================================================================================================
const.ERR_SCALE_AMOUNT_EXCEEDED_LIMIT="maximum scaling factor is 18"

#! Calculate 10^scale where scale is a u8 exponent.
#!
#! Inputs: [scale]
#! Outputs: [10^scale]
#!
#! Where:
#! - scale is expected to be a small integer (0-18 typical for crypto decimals)
#!
#! Panics if:
#! - scale > 18 (overflow protection)
proc pow10
u32assert.err=ERR_SCALE_AMOUNT_EXCEEDED_LIMIT
# => [scale]

dup u32lte.MAX_SCALING_FACTOR assert.err=ERR_SCALE_AMOUNT_EXCEEDED_LIMIT
# => [scale]

push.1 swap
# => [scale, result]

dup neq.0
# => [is_not_zero, scale, result]

# Loop to calculate 10^scale
while.true
# => [scale, result]

# result *= 10
swap mul.10 swap
# => [scale, result*10]

# scale -= 1
sub.1
# => [scale-1, result*10]

dup neq.0
# => [is_not_zero, scale-1, result*10]
end
# => [0, result]

drop
# => [result]
end

#! Convert an asset amount to a scaled U256 representation for bridging to Agglayer.
#!
#! This procedure is used to convert Miden asset amounts to EVM asset amounts.
#! It multiplies the input amount by 10^target_scale to adjust for decimal differences
#! between the current representation and the target chain's native decimals.
#!
#! The procedure first calculates 10^target_scale using the pow10 helper, then converts
#! both the amount and scale factor to U64 format, performs U64 multiplication, and
#! returns the result as 8 u32 limbs in little-endian order (U256 format).
#!
#! Inputs: [amount, target_scale]
#! Outputs: [[RESULT_U256[0], RESULT_U256[1]]]
#!
#! Where:
#! - amount: The asset amount to be converted (range: 0 to 2^63 - 2^31)
#! - target_scale: Exponent for scaling factor (10^target_scale)
#! - [RESULT_U256[0], RESULT_U256[1]]: U256 value as 8 u32 limbs in little-endian order
#! (least significant limb at the top of the stack, each limb stored in little-endian format)
#!
#! Examples:
#! - USDC: amount=1000000000, target_scale=0 → 1000000000 (no scaling)
#! - ETH: amount=1e10, target_scale=8 → 1e18
#!
#! Invocation: exec
pub proc scale_native_amount_to_u256
swap
# => [target_scale, amount]

exec.pow10
# => [scale, amount]

u32split
# => [scale_hi, scale_lo, amount]

movup.2 u32split
# => [amount_hi, amount_lo, scale_hi, scale_lo]

# Perform U64 multiplication: amount * scale
# This is safe because both the scaling factor and amount are guaranteed to be smaller
# than 2^64, so we will never overflow a 256-bit value.
exec.u64::overflowing_mul
# => [res_hi, res_mid_hi, res_mid_lo, res_lo]

exec.word::reverse
# => [res_lo, res_mid_lo, res_mid_hi, res_hi]

# convert to U256 & little endian
padw swapw
# => [RESULT_U256[0], RESULT_U256[1]]
end
26 changes: 26 additions & 0 deletions crates/miden-lib/src/agglayer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,29 @@ pub fn bridge_out_with_local_exit_tree_component(
local_exit_tree_component(vec![]), // local_exit_tree typically doesn't need storage slots
]
}

// Initialize the Asset Conversion library only once
static ASSET_CONVERSION_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
let bytes = include_bytes!(concat!(
env!("OUT_DIR"),
"/assets/agglayer/account_components/asset_conversion.masl"
));
Library::read_from_bytes(bytes).expect("Shipped Asset Conversion library is well-formed")
});

/// Returns the Asset Conversion Library.
pub fn asset_conversion_library() -> Library {
ASSET_CONVERSION_LIBRARY.clone()
}

/// Creates an Asset Conversion component with the specified storage slots.
///
/// This component uses the asset_conversion library and can be added to accounts
/// that need to convert assets between Miden and Ethereum formats.
pub fn asset_conversion_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = asset_conversion_library();

AccountComponent::new(library, storage_slots)
.expect("asset_conversion component should satisfy the requirements of a valid account component")
.with_supports_all_types()
}
17 changes: 17 additions & 0 deletions crates/miden-lib/src/agglayer/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@ use alloc::vec::Vec;

use miden_objects::Felt;

/// Convert 8 Felt values (u32 limbs in little-endian order) to U256 bytes in little-endian format.
///
/// The input limbs are expected to be in little-endian order (least significant limb first).
/// This function converts them to a 32-byte array in little-endian format for compatibility
/// with Ethereum/EVM which expects U256 values as 32 bytes in little-endian format.
/// This ensures compatibility when bridging assets between Miden and Ethereum-based chains.
pub fn felts_to_u256_bytes(limbs: [Felt; 8]) -> [u8; 32] {
let mut bytes = [0u8; 32];

for (i, limb) in limbs.iter().enumerate() {
let u32_value = limb.as_int() as u32;
let limb_bytes = u32_value.to_le_bytes();
bytes[i * 4..(i + 1) * 4].copy_from_slice(&limb_bytes);
}

bytes
}
/// Converts an Ethereum address (20 bytes) into a vector of 5 Felt values.
///
/// An Ethereum address is 20 bytes, which we split into 5 u32 values (4 bytes each).
Expand Down
3 changes: 3 additions & 0 deletions crates/miden-lib/src/errors/note_script_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub const ERR_P2ID_TARGET_ACCT_MISMATCH: MasmError = MasmError::from_static_str(
/// Error Message: "P2ID note expects exactly 2 note inputs"
pub const ERR_P2ID_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("P2ID note expects exactly 2 note inputs");

/// Error Message: "maximum scaling factor is 18"
pub const ERR_SCALE_AMOUNT_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("maximum scaling factor is 18");

/// Error Message: "SWAP script requires exactly 1 note asset"
pub const ERR_SWAP_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_str("SWAP script requires exactly 1 note asset");
/// Error Message: "SWAP script expects exactly 12 note inputs"
Expand Down
1 change: 1 addition & 0 deletions crates/miden-testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ winterfell = { version = "0.13" }
anyhow = { features = ["backtrace", "std"], workspace = true }
assert_matches = { workspace = true }
miden-objects = { features = ["std"], workspace = true }
primitive-types = { workspace = true }
rstest = { workspace = true }
tokio = { features = ["macros", "rt"], workspace = true }
winter-rand-utils = { version = "0.13" }
Loading