extern crate alloc;
use alloc::collections::BTreeSet;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use miden_protocol::account::{Account, AccountId};
use miden_protocol::asset::AssetAmount;
use miden_protocol::note::NoteScriptRoot;
use miden_protocol::testing::account_id::{
ACCOUNT_ID_FEE_FAUCET,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
};
use miden_protocol::utils::hex_to_bytes;
use miden_protocol::utils::sync::LazyLock;
use miden_protocol::{Felt, Word};
use miden_standards::account::fees::{BasicConstantFeePolicy, FeePolicyManager};
use miden_standards::interop::eth::{EthAddress, EthAmount};
use serde::Deserialize;
use crate::claim_note::{ProofData, SmtNode};
use crate::{
AggLayerBridge,
AggLayerFaucet,
BridgeRoles,
CgiChainHash,
ExitRoot,
GlobalIndex,
LeafData,
MetadataHash,
};
pub fn zero_fee_policy_manager(allowed_notes: BTreeSet<NoteScriptRoot>) -> FeePolicyManager {
let fee_faucet_id =
AccountId::try_from(ACCOUNT_ID_FEE_FAUCET).expect("mock-chain fee faucet id is valid");
let mut basic_constant_fee_policy = BasicConstantFeePolicy::new();
for note_script in allowed_notes {
basic_constant_fee_policy =
basic_constant_fee_policy.with_fee(note_script, AssetAmount::ZERO);
}
FeePolicyManager::builder()
.active_fee_policy(basic_constant_fee_policy.into())
.fee_faucet_id(fee_faucet_id)
.build()
}
pub fn bridge_admin_account_id() -> AccountId {
AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap()
}
pub fn create_existing_bridge_account_with_roles(
seed: Word,
bridge_admin: AccountId,
faucet_manager: AccountId,
ger_injector: AccountId,
ger_remover: AccountId,
network_id: u32,
) -> Account {
let fee_policy_manager = zero_fee_policy_manager(AggLayerBridge::allowed_notes());
let roles = BridgeRoles::new(
BTreeSet::from([faucet_manager]),
BTreeSet::from([ger_injector]),
BTreeSet::from([ger_remover]),
)
.expect("single-holder role sets are non-empty");
AggLayerBridge::account_builder(seed, bridge_admin, roles, network_id, fee_policy_manager)
.build_existing()
.expect("bridge account should be valid")
}
pub fn create_existing_agglayer_faucet(
seed: Word,
token_symbol: &str,
decimals: u8,
max_supply: Felt,
initial_supply: Felt,
bridge_account_id: AccountId,
) -> Account {
let faucet_admin = bridge_admin_account_id();
AggLayerFaucet::account_builder(
seed,
token_symbol,
decimals,
max_supply,
initial_supply,
faucet_admin,
bridge_account_id,
zero_fee_policy_manager(AggLayerFaucet::allowed_notes()),
)
.build_existing()
.expect("agglayer faucet account should be valid")
}
pub const BRIDGE_ASSET_VECTORS_JSON: &str =
include_str!("../../solidity-compat/test-vectors/claim_asset_vectors_l1_tx.json");
pub const ROLLUP_ASSET_VECTORS_JSON: &str =
include_str!("../../solidity-compat/test-vectors/claim_asset_vectors_l2_tx.json");
pub const LEAF_VALUE_VECTORS_JSON: &str =
include_str!("../../solidity-compat/test-vectors/leaf_value_vectors.json");
pub const MERKLE_PROOF_VECTORS_JSON: &str =
include_str!("../../solidity-compat/test-vectors/merkle_proof_vectors.json");
pub const CANONICAL_ZEROS_JSON: &str =
include_str!("../../solidity-compat/test-vectors/canonical_zeros.json");
pub const MTF_VECTORS_JSON: &str =
include_str!("../../solidity-compat/test-vectors/merkle_tree_frontier_vectors.json");
pub 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")),
}
}
pub fn deserialize_uint_vec_to_strings<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let values = Vec::<serde_json::Value>::deserialize(deserializer)?;
values
.into_iter()
.map(|v| match v {
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")),
})
.collect()
}
#[derive(Debug, Deserialize)]
pub struct LeafValueVector {
pub origin_network: u32,
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)]
#[serde(default)]
pub leaf_value: String,
}
impl LeafValueVector {
pub fn to_leaf_data(&self) -> LeafData {
LeafData {
origin_network: self.origin_network,
origin_token_address: EthAddress::from_hex(&self.origin_token_address)
.expect("valid origin token address hex"),
destination_network: self.destination_network,
destination_address: EthAddress::from_hex(&self.destination_address)
.expect("valid destination address 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"),
),
}
}
}
#[derive(Debug, Deserialize)]
pub struct ProofValueVector {
pub smt_proof_local_exit_root: Vec<String>,
pub smt_proof_rollup_exit_root: Vec<String>,
pub global_index: String,
pub mainnet_exit_root: String,
pub rollup_exit_root: String,
#[allow(dead_code)]
pub global_exit_root: String,
pub claimed_global_index_hash_chain: String,
}
impl ProofValueVector {
pub fn to_proof_data(&self) -> ProofData {
let smt_proof_local: [SmtNode; 32] = self
.smt_proof_local_exit_root
.iter()
.map(|s| SmtNode::new(hex_to_bytes(s).expect("valid smt proof hex")))
.collect::<Vec<_>>()
.try_into()
.expect("expected 32 SMT proof nodes for local exit root");
let smt_proof_rollup: [SmtNode; 32] = self
.smt_proof_rollup_exit_root
.iter()
.map(|s| SmtNode::new(hex_to_bytes(s).expect("valid smt proof hex")))
.collect::<Vec<_>>()
.try_into()
.expect("expected 32 SMT proof nodes for rollup exit root");
ProofData {
smt_proof_local_exit_root: smt_proof_local,
smt_proof_rollup_exit_root: smt_proof_rollup,
global_index: GlobalIndex::from_hex(&self.global_index)
.expect("valid global index hex"),
mainnet_exit_root: ExitRoot::new(
hex_to_bytes(&self.mainnet_exit_root).expect("valid mainnet exit root hex"),
),
rollup_exit_root: ExitRoot::new(
hex_to_bytes(&self.rollup_exit_root).expect("valid rollup exit root hex"),
),
}
}
}
#[derive(Debug, Deserialize)]
pub struct ClaimAssetVector {
#[serde(flatten)]
pub proof: ProofValueVector,
#[serde(flatten)]
pub leaf: LeafValueVector,
}
#[derive(Debug, Deserialize)]
pub struct MerkleProofVerificationFile {
pub leaves: Vec<String>,
pub roots: Vec<String>,
pub merkle_paths: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct CanonicalZerosFile {
pub canonical_zeros: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct MtfVectorsFile {
pub leaves: Vec<String>,
pub roots: Vec<String>,
pub counts: Vec<u32>,
#[serde(deserialize_with = "deserialize_uint_vec_to_strings")]
pub amounts: Vec<String>,
pub origin_token_address: String,
pub destination_networks: Vec<u32>,
pub destination_addresses: Vec<String>,
pub token_name: String,
pub token_symbol: String,
pub token_decimals: u8,
}
pub static CLAIM_ASSET_VECTOR_L1: LazyLock<ClaimAssetVector> = LazyLock::new(|| {
serde_json::from_str(BRIDGE_ASSET_VECTORS_JSON)
.expect("failed to parse bridge asset vectors JSON")
});
pub static CLAIM_ASSET_VECTOR_L2: LazyLock<ClaimAssetVector> = LazyLock::new(|| {
serde_json::from_str(ROLLUP_ASSET_VECTORS_JSON)
.expect("failed to parse rollup asset vectors JSON")
});
pub static SOLIDITY_MERKLE_PROOF_VECTORS: LazyLock<MerkleProofVerificationFile> =
LazyLock::new(|| {
serde_json::from_str(MERKLE_PROOF_VECTORS_JSON)
.expect("failed to parse Merkle proof vectors JSON")
});
pub static SOLIDITY_CANONICAL_ZEROS: LazyLock<CanonicalZerosFile> = LazyLock::new(|| {
serde_json::from_str(CANONICAL_ZEROS_JSON).expect("failed to parse canonical zeros JSON")
});
pub static SOLIDITY_MTF_VECTORS: LazyLock<MtfVectorsFile> = LazyLock::new(|| {
serde_json::from_str(MTF_VECTORS_JSON).expect("failed to parse MTF vectors JSON")
});
#[derive(Debug, Clone, Copy)]
pub enum ClaimDataSource {
L1ToMiden,
L2ToMiden,
}
impl ClaimDataSource {
pub fn get_data(self) -> (ProofData, LeafData, ExitRoot, CgiChainHash) {
let vector = match self {
ClaimDataSource::L1ToMiden => &*CLAIM_ASSET_VECTOR_L1,
ClaimDataSource::L2ToMiden => &*CLAIM_ASSET_VECTOR_L2,
};
let ger = ExitRoot::new(
hex_to_bytes(&vector.proof.global_exit_root).expect("valid global exit root hex"),
);
let cgi_chain_hash = CgiChainHash::new(
hex_to_bytes(&vector.proof.claimed_global_index_hash_chain)
.expect("invalid CGI chain hash"),
);
(vector.proof.to_proof_data(), vector.leaf.to_leaf_data(), ger, cgi_chain_hash)
}
}