use miden_protocol::account::{AccountId, AccountStorage, StorageMapKey, StorageSlotDelta};
use miden_protocol::asset::{Asset, FungibleAsset};
use miden_protocol::testing::account_id::{
ACCOUNT_ID_NATIVE_ASSET_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
};
use miden_protocol::testing::constants::FUNGIBLE_ASSET_AMOUNT;
use miden_protocol::testing::storage::MOCK_MAP_SLOT;
use miden_standards::code_builder::CodeBuilder;
use miden_standards::testing::note::NoteBuilder;
use super::Word;
use crate::{Auth, MockChain, TransactionContextBuilder};
#[tokio::test]
async fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> {
let faucet_id1: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap();
let faucet_id2: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into().unwrap();
let fungible_asset1 =
FungibleAsset::new(faucet_id1, FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT)?;
let fungible_asset2 = FungibleAsset::new(faucet_id2, FUNGIBLE_ASSET_AMOUNT)?;
let asset_note = NoteBuilder::new(faucet_id1, rand::rng())
.add_assets([fungible_asset1, fungible_asset2].map(Asset::from))
.build()?;
let code = format!(
"
use mock::account
begin
push.{FUNGIBLE_ASSET_VALUE1}
push.{FUNGIBLE_ASSET_KEY1}
call.account::add_asset dropw dropw
push.{FUNGIBLE_ASSET_VALUE2}
push.{FUNGIBLE_ASSET_KEY2}
call.account::add_asset dropw dropw
end
",
FUNGIBLE_ASSET_KEY1 = fungible_asset1.to_key_word(),
FUNGIBLE_ASSET_VALUE1 = fungible_asset1.to_value_word(),
FUNGIBLE_ASSET_KEY2 = fungible_asset2.to_key_word(),
FUNGIBLE_ASSET_VALUE2 = fungible_asset2.to_value_word()
);
let builder = CodeBuilder::with_mock_libraries();
let source_manager = builder.source_manager();
let tx_script = builder.compile_tx_script(code)?;
let tx_context = TransactionContextBuilder::with_existing_mock_account()
.tx_script(tx_script)
.extend_input_notes(vec![asset_note])
.with_source_manager(source_manager)
.build()?;
let account = tx_context.account().clone();
let tx = tx_context.execute().await?;
let mut account_vault = account.vault().clone();
account_vault.add_asset(fungible_asset1.into())?;
account_vault.add_asset(fungible_asset2.into())?;
assert_eq!(tx.final_account().vault_root(), account_vault.root());
Ok(())
}
#[tokio::test]
async fn removing_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<()> {
let faucet_id1: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap();
let faucet_id2: AccountId = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into().unwrap();
let fungible_asset1 =
FungibleAsset::new(faucet_id1, FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT)?;
let fungible_asset2 = FungibleAsset::new(faucet_id2, FUNGIBLE_ASSET_AMOUNT)?;
let code = format!(
"
use mock::account
use mock::util
begin
push.{FUNGIBLE_ASSET1_VALUE}
push.{FUNGIBLE_ASSET1_KEY}
call.account::remove_asset
# drop the excess words from the call
dropw dropw
# => []
# move asset to note to adhere to asset preservation rules
push.{FUNGIBLE_ASSET1_VALUE}
push.{FUNGIBLE_ASSET1_KEY}
exec.util::create_default_note_with_asset
# => []
push.{FUNGIBLE_ASSET2_VALUE}
push.{FUNGIBLE_ASSET2_KEY}
call.account::remove_asset
# drop the excess words from the call
dropw dropw
# => []
# move asset to note to adhere to asset preservation rules
push.{FUNGIBLE_ASSET2_VALUE}
push.{FUNGIBLE_ASSET2_KEY}
exec.util::create_default_note_with_asset
# => []
end
",
FUNGIBLE_ASSET1_KEY = fungible_asset1.to_key_word(),
FUNGIBLE_ASSET1_VALUE = fungible_asset1.to_value_word(),
FUNGIBLE_ASSET2_KEY = fungible_asset2.to_key_word(),
FUNGIBLE_ASSET2_VALUE = fungible_asset2.to_value_word(),
);
let builder = CodeBuilder::with_mock_libraries();
let source_manager = builder.source_manager();
let tx_script = builder.compile_tx_script(code)?;
let mut builder = MockChain::builder();
let account = builder.add_existing_mock_account_with_assets(
crate::Auth::IncrNonce,
[fungible_asset1, fungible_asset2].map(Asset::from),
)?;
let tx_context = builder
.build()?
.build_tx_context(account, &[], &[])?
.tx_script(tx_script)
.with_source_manager(source_manager)
.build()?;
let account = tx_context.account().clone();
let tx = tx_context.execute().await?;
let mut account_vault = account.vault().clone();
account_vault.remove_asset(fungible_asset1.into())?;
account_vault.remove_asset(fungible_asset2.into())?;
assert_eq!(tx.final_account().vault_root(), account_vault.root());
Ok(())
}
#[tokio::test]
async fn loading_fee_asset_succeeds() -> anyhow::Result<()> {
let mut builder =
MockChain::builder().native_asset_id(ACCOUNT_ID_NATIVE_ASSET_FAUCET.try_into()?);
let account = builder.add_existing_mock_account_with_assets(
Auth::IncrNonce,
[
FungibleAsset::mock(23),
FungibleAsset::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2.try_into()?, 50)?.into(),
],
)?;
builder.build()?.build_tx_context(account, &[], &[])?.build()?.execute().await?;
Ok(())
}
#[tokio::test]
async fn setting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> {
let mock_map = AccountStorage::mock_map();
let existing_key = *mock_map.entries().next().unwrap().0;
let non_existent_key = StorageMapKey::from_array([5, 5, 5, 5u32]);
assert!(
mock_map.open(&non_existent_key).get(non_existent_key).unwrap() == Word::empty(),
"test setup requires that the non existent key does not exist"
);
let mock_map_slot = &*MOCK_MAP_SLOT;
let value0 = Word::from([3, 4, 5, 6u32]);
let value1 = Word::from([9, 8, 7, 6u32]);
let code = format!(
r#"
use mock::account
const MOCK_MAP_SLOT = word("{mock_map_slot}")
begin
# Update an existing key.
push.{value0}
push.{existing_key}
push.MOCK_MAP_SLOT[0..2]
# => [slot_id_suffix, slot_id_prefix, KEY, VALUE]
call.account::set_map_item
# Insert a non-existent key.
push.{value1}
push.{non_existent_key}
push.MOCK_MAP_SLOT[0..2]
# => [slot_id_suffix, slot_id_prefix, KEY, VALUE]
call.account::set_map_item
exec.::miden::core::sys::truncate_stack
end
"#
);
let builder = CodeBuilder::with_mock_libraries();
let source_manager = builder.source_manager();
let tx_script = builder.compile_tx_script(code)?;
let tx = TransactionContextBuilder::with_existing_mock_account()
.tx_script(tx_script)
.with_source_manager(source_manager)
.build()?
.execute()
.await?;
let map_delta = tx
.account_delta()
.storage()
.get(mock_map_slot)
.cloned()
.map(StorageSlotDelta::unwrap_map)
.unwrap();
assert_eq!(map_delta.entries().get(&existing_key).unwrap(), &value0);
assert_eq!(map_delta.entries().get(&non_existent_key).unwrap(), &value1);
Ok(())
}
#[tokio::test]
async fn getting_map_item_with_lazy_loading_succeeds() -> anyhow::Result<()> {
let mock_map = AccountStorage::mock_map();
let (existing_key, existing_value) = mock_map.entries().next().unwrap();
let non_existent_key = StorageMapKey::from_array([5, 5, 5, 5u32]);
assert!(
mock_map.open(&non_existent_key).get(non_existent_key).unwrap() == Word::empty(),
"test setup requires that the non existent key does not exist"
);
let mock_map_slot = &*MOCK_MAP_SLOT;
let code = format!(
r#"
use miden::core::word
use mock::account
const MOCK_MAP_SLOT = word("{mock_map_slot}")
begin
# Fetch value from existing key.
push.{existing_key}
push.MOCK_MAP_SLOT[0..2]
# => [slot_id_suffix, slot_id_prefix, KEY]
call.account::get_map_item
push.{existing_value}
assert_eqw.err="existing value does not match expected value"
# Fetch a non-existent key.
push.{non_existent_key}
push.MOCK_MAP_SLOT[0..2]
# => [slot_id_suffix, slot_id_prefix, KEY]
call.account::get_map_item
padw assert_eqw.err="non-existent value should be the empty word"
exec.::miden::core::sys::truncate_stack
end
"#
);
let builder = CodeBuilder::with_mock_libraries();
let source_manager = builder.source_manager();
let tx_script = builder.compile_tx_script(code)?;
TransactionContextBuilder::with_existing_mock_account()
.tx_script(tx_script)
.with_source_manager(source_manager)
.build()?
.execute()
.await?;
Ok(())
}