Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
80cd33a
feat: implement agglayer bridge GER update functionality
partylikeits1983 Dec 12, 2025
f0f8a91
refactor: reset to agglayer branch
partylikeits1983 Dec 12, 2025
2ff78b5
feat: make get_foreign_account_inputs public
partylikeits1983 Dec 12, 2025
5f6b6e4
Merge branch 'agglayer' into ajl-update-aggbridge-GER
partylikeits1983 Dec 15, 2025
ff1ec8a
wip: update GER
partylikeits1983 Dec 15, 2025
62acad5
wip: end to end flow
partylikeits1983 Dec 15, 2025
99bc000
wip: add monotonoic update_ger assertion
partylikeits1983 Dec 15, 2025
59a8344
wip: fix test
partylikeits1983 Dec 15, 2025
4ec9c2a
feat: add stubbed out bridge in flow
partylikeits1983 Dec 16, 2025
0e66481
refactor: clean up agglayer_faucet
partylikeits1983 Dec 16, 2025
dc2d124
feat: add create_claim_note helper function
partylikeits1983 Dec 16, 2025
46fa8dd
refactor: rm extraneous doc comment
partylikeits1983 Dec 16, 2025
613d37d
Update crates/miden-testing/tests/agglayer/bridge_in.rs
partylikeits1983 Dec 16, 2025
f903f02
refactor: cleanup comments & rename param
partylikeits1983 Dec 16, 2025
c7502f4
fix: rustfmt
partylikeits1983 Dec 16, 2025
f329e85
Update crates/miden-lib/asm/agglayer/account_components/local_exit_tr…
partylikeits1983 Dec 16, 2025
23c769f
refactor: cleanup comment
partylikeits1983 Dec 16, 2025
61c14e9
refactor: update agglayer faucet docs
partylikeits1983 Dec 16, 2025
ee40a65
refactor: cleanup doc comments
partylikeits1983 Dec 16, 2025
712fb9d
refactor: make target account consume created P2ID note
partylikeits1983 Dec 16, 2025
407341a
chore: merge agglayer
partylikeits1983 Dec 16, 2025
17b2684
chore: merge CLAIM note updates
partylikeits1983 Dec 16, 2025
5fdfe21
refactor: rm empty line
partylikeits1983 Dec 16, 2025
c232aa5
fix: clippy
partylikeits1983 Dec 16, 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
100 changes: 100 additions & 0 deletions crates/miden-lib/asm/agglayer/account_components/agglayer_faucet.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use.agglayer::bridge_in
use.miden::active_account
use.miden::contracts::faucets
use.miden::tx

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

# The slot in this component's storage layout where the bridge account ID is stored.
const.BRIDGE_ID_IDX=word("miden::agglayer::faucet")

# ERRORS
# =================================================================================================
const.ERR_INVALID_PROOF="invalid proof"

#! Validates a CLAIM note's rollup exit root Merkle Proof against the agglayer bridge via FPI.
#!
#! This procedure performs a foreign procedure invocation to the agglayer bridge to validate
#! that the CLAIM note's rollup exit root Merkle Proof is valid.
#!
#! Inputs: []
#! Outputs: []
#!
#! Panics if:
#! - the bridge account ID is not properly configured in storage.
#! - the foreign procedure invocation fails.
#! - the claim proof validation fails.
#!
#! Invocation: exec
proc validate_claim_via_fpi
# get check_claim_proof root
procref.bridge_in::check_claim_proof
# => [MAST_ROOT]

push.BRIDGE_ID_IDX[0..2]
# => [bridge_id_idx, MAST_ROOT]

# get bridge account id
exec.active_account::get_item
# => [bridge_account_id_prefix, bridge_account_id_suffix, 0, 0, MAST_ROOT]

movup.2 drop movup.2 drop
# => [bridge_account_id_prefix, bridge_account_id_suffix, MAST_ROOT]

# call check_claim_proof proc on bridge
exec.tx::execute_foreign_procedure
# => [validation_result]

assert.err=ERR_INVALID_PROOF drop
# => []
end

#! Distributes freshly minted fungible assets to the provided recipient by creating a note.
#!
#! This procedure validates the rollup exit root Merkle Proof via FPI against the agglayer bridge,
#! and if validation passes, mints the asset and creates an output note for the recipient.
#!
#! Inputs: [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(8)]
#! Outputs: [pad(16)]
#!
#! Where:
#! - amount is the amount to be minted and sent.
#! - tag is the tag to be included in the note.
#! - aux is the auxiliary data to be included in the note.
#! - note_type is the type of the note that holds the asset.
#! - execution_hint is the execution hint of the note that holds the asset.
#! - RECIPIENT is the recipient of the asset, i.e.,
#! hash(hash(hash(serial_num, [0; 4]), script_root), input_commitment).
#!
#! Panics if:
#! - the rollup exit root Merkle Proof validation via FPI fails.
#! - any of the validations in faucets::distribute fail.
#!
#! Invocation: call
export.claim
exec.faucets::distribute
# => [pad(16)]

# validate CLAIM note proof (stubbed out)
exec.validate_claim_via_fpi
# => [pad(16)]
end

#! Burns the fungible asset from the active note.
#!
#! This procedure retrieves the asset from the active note and burns it. The note must contain
#! exactly one asset, which must be a fungible asset issued by this faucet.
#!
#! Inputs: [pad(16)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the procedure is not called from a note context (active_note::get_assets will fail).
#! - the note does not contain exactly one asset.
#! - the transaction is executed against an account which is not a fungible asset faucet.
#! - the transaction is executed against a faucet which is not the origin of the specified asset.
#! - the amount about to be burned is greater than the outstanding supply of the asset.
#!
#! Invocation: call
export.faucets::burn
139 changes: 139 additions & 0 deletions crates/miden-lib/asm/agglayer/account_components/bridge_in.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use.miden::native_account
use.miden::active_account
use.miden::active_note
use.miden::account_id
use.agglayer::local_exit_tree

const.GLOBAL_EXIT_ROOT_MAP=word("miden::agglayer::GER")
const.BRIDGE_OPERATOR_SLOT=word("miden::agglayer::bridge_operator")

# 32 byte Global Exit Root stored across two Word values
const.GER_UPPER_KEY=[0,0,0,0]
const.GER_LOWER_KEY=[1,0,0,0]
const.GER_INDEX_KEY=[2,0,0,0]

# ERRORS
const.ERR_ONLY_BRIDGE_OPERATOR_CAN_UPDATE_GER="note sender is not the authorized bridge operator"

#! Checks if the note sender is the authorized bridge operator.
#!
#! Inputs: []
#! Outputs: [is_authorized]
#!
#! Where:
#! - is_authorized is 1 if the sender is the bridge operator, 0 otherwise.
proc.is_authorized_operator
push.BRIDGE_OPERATOR_SLOT[0..2] exec.active_account::get_item
# => [operator_prefix, operator_suffix, 0, 0]

exec.active_note::get_sender
# => [sender_prefix, sender_suffix, operator_prefix, operator_suffix, 0, 0]

exec.account_id::is_equal
# => [are_equal, 0, 0]

movdn.2 drop drop
# => [is_authorized]
end

proc.is_ger_update_monotonic
# Stack: [incoming_ger_index]
dup
# => [incoming_ger_index, incoming_ger_index]

# Get current ger_index from storage
push.GER_INDEX_KEY push.GLOBAL_EXIT_ROOT_MAP[0..2] exec.active_account::get_map_item
# => [current_ger_index_word, incoming_ger_index, incoming_ger_index]

# Extract the current ger_index (stored in the first element of the word)
drop drop drop
# => [current_ger_index, incoming_ger_index, incoming_ger_index]

# Check that incoming_index = current_index + 1
add.1
# => [current_index + 1, incoming_ger_index, incoming_ger_index]

dup.1 eq
# => [is_monotonic, incoming_ger_index, incoming_ger_index]

assert.err="ger index must increase monotonically by 1"
# => [incoming_ger_index, incoming_ger_index]

drop
# => [incoming_ger_index]

# Store the incoming ger_index at GER_INDEX_KEY
push.0.0.0
# => [0, 0, 0, incoming_ger_index]

push.GER_INDEX_KEY push.GLOBAL_EXIT_ROOT_MAP[0..2] exec.native_account::set_map_item
# => [old_map_root, old_map_value]

dropw dropw
# => []
end

#! Update Global Exit Root procedure
#! This procedure handles updating the Global Exit Root in the bridge
#! It validates that only the authorized bridge operator can update GER
#! and stores both the GER value and its index.
#!
#! Inputs: [GER_UPPER, GER_LOWER, ger_index]
#! Outputs: []
#!
#! Where:
#! - GER_UPPER is the upper 128-bit limb of the 256-bit Global Exit Root (4 field elements)
#! - GER_LOWER is the lower 128-bit limb of the 256-bit Global Exit Root (4 field elements)
#! - ger_index is the 32-bit L1InfoTree index (1 field element)
#!
#! Panics if:
#! - the note sender is not the authorized bridge operator
pub proc update_ger
# Check authorization first
exec.is_authorized_operator
# => [is_authorized, GER_UPPER, GER_LOWER, ger_index]

assert.err=ERR_ONLY_BRIDGE_OPERATOR_CAN_UPDATE_GER
# => [GER_UPPER, GER_LOWER, ger_index]

# Store GER_UPPER at key [0,0,0,0]
# Stack: [GER_UPPER, GER_LOWER, ger_index]
push.GER_UPPER_KEY push.GLOBAL_EXIT_ROOT_MAP[0..2]
# => [index, KEY, GER_UPPER, GER_LOWER, ger_index]

exec.native_account::set_map_item
# => [OLD_MAP_ROOT, OLD_MAP_VALUE, GER_LOWER, ger_index]

dropw dropw
# => [GER_LOWER, ger_index]

# Store GER_LOWER at key [0,0,0,1]
push.GER_LOWER_KEY push.GLOBAL_EXIT_ROOT_MAP[0..2]
# => [index, KEY, GER_LOWER, ger_index]

exec.native_account::set_map_item
# => [OLD_MAP_ROOT, OLD_MAP_VALUE, ger_index]

dropw dropw
# => [ger_index]

exec.is_ger_update_monotonic

dropw dropw dropw dropw
# => []
end

#! Checks the validity of the GET proof
#!
#! Inputs: []
#! Outputs: [is_valid_claim_proof]
#!
#! Invocation: exec
pub proc check_claim_proof
exec.local_exit_tree::verify_claim_proof
# => [is_valid_claim_proof]

# truncate stack
swap drop
# => [is_valid_claim_proof]
end
15 changes: 15 additions & 0 deletions crates/miden-lib/asm/agglayer/account_components/bridge_out.masm
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ const.AUX=0
const.NUM_BURN_NOTE_INPUTS=0
const.BURN_ASSET_MEM_PTR=24

#! Checks the validity of the GET proof
#!
#! Inputs: []
#! Outputs: [is_valid_claim_proof]
#!
#! Invocation: exec
pub proc check_claim_proof
exec.local_exit_tree::verify_claim_proof
# => [is_valid_claim_proof]

# truncate stack
swap drop
# => [is_valid_claim_proof]
end

#! Computes the SERIAL_NUM of the outputted BURN note.
#!
#! The serial number is computed as hash(B2AGG_SERIAL_NUM, ASSET).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,17 @@ pub proc add_asset_message

exec.write_mmr_frontier_root
# => []
end

#! Verifies the validity of the CLAIM note proof (stubbed implementation).
#!
#! This is a stubbed implementation that always returns 1 (true), indicating the
#! proof is valid regardless of the input.
#!
#! Inputs: [...]
#! Outputs: [1] (currently always returns 1)
#!
#! Invocation: exec
pub proc verify_claim_proof
push.1
end
82 changes: 82 additions & 0 deletions crates/miden-lib/asm/agglayer/note_scripts/CLAIM.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use.agglayer::agglayer_faucet -> agg_faucet
use.miden::note
use.miden::active_note

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

const.CLAIM_NOTE_MIN_NUM_INPUTS_PUBLIC=12
const.OUTPUT_NOTE_TYPE_PUBLIC=1
const.OUTPUT_PUBLIC_NOTE_INPUTS_ADDR=12
const.OUTPUT_PUBLIC_NOTE_INPUTS_LEN_MEM_ADDR=0

# ERRORS
# =================================================================================================
const.ERR_CLAIM_WRONG_NUMBER_OF_INPUTS="CLAIM script expects 12+ inputs for public output notes"

#! Agglayer Faucet CLAIM script: claims assets by calling the agglayer faucet's claim function.
#! This note is intended to be executed against an agglayer faucet account.
#!
#! Requires that the account exposes:
#! - agglayer::agglayer_faucet::claim procedure.
#!
#! Inputs: [ARGS, pad(12)]
#! Outputs: [pad(16)]
#!
#! The CLAIM note only supports public output notes and requires 12+ inputs:
#! - execution_hint: Execution hint for the output note
#! - aux: Auxiliary data for the output note
#! - tag: Note tag for the output note
#! - amount: The amount to claim
#! - SCRIPT_ROOT: Script root of the output note (4 elements)
#! - SERIAL_NUM: Serial number of the output note (4 elements)
#! - [INPUTS]: Variable-length inputs for the output note (Vec<Felt>)
#! The number of output note inputs = num_claim_note_inputs - 12
#!
#! Panics if:
#! - account does not expose claim procedure.
#! - the number of inputs is less than 12.
begin
dropw
# => [pad(16)]
# Load note inputs into memory starting at address 0
push.0 exec.active_note::get_inputs
# => [total_inputs, inputs_ptr, pad(16)]

u32assert2.err=ERR_CLAIM_WRONG_NUMBER_OF_INPUTS
# => [total_inputs, inputs_ptr, pad(16)]

movdn.9 drop
# => [EMPTY_WORD, EMPTY_WORD, total_inputs, pad(8)]

mem_loadw_be.4
# => [SCRIPT_ROOT, EMPTY_WORD, total_inputs, pad(8)]

swapw mem_loadw_be.8
# => [SERIAL_NUM, SCRIPT_ROOT, total_inputs, pad(8)]

# compute variable length note inputs for the output note
movup.8 sub.CLAIM_NOTE_MIN_NUM_INPUTS_PUBLIC
# => [num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT, pad(8)]

push.OUTPUT_PUBLIC_NOTE_INPUTS_ADDR
# => [inputs_ptr = 12, num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT, pad(8)]

exec.note::build_recipient
# => [RECIPIENT, pad(12)]

swapw mem_loadw_be.0
# => [amount, tag, aux, execution_hint, RECIPIENT, pad(8)]

push.OUTPUT_NOTE_TYPE_PUBLIC
# => [note_type, amount, tag, aux, execution_hint, RECIPIENT, pad(8)]

movdn.3
# => [amount, tag, aux, note_type, execution_hint, RECIPIENT, pad(8)]

call.agg_faucet::claim
# => [pad(17))]

drop
# => [pad(16)]
end
Loading