Skip to content

Add the test that executes counter contract and note script on the testnet #555

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: next
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion tests/integration/src/compiler_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ impl CompilerTestBuilder {
"--remap-path-prefix".into(),
format!("{workspace_dir}=../../").into(),
]);
let mut midenc_flags = vec!["--debug".into(), "--verbose".into()];
let mut midenc_flags = vec!["--verbose".into()];
if let Some(entrypoint) = entrypoint {
midenc_flags.extend(["--entrypoint".into(), format!("{}", entrypoint.display())]);
}
Expand Down
1 change: 1 addition & 0 deletions tests/integration/src/rust_masm_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod examples;
mod instructions;
mod intrinsics;
mod rust_sdk;
mod testnet;
mod types;

pub fn run_masm_vs_rust<T>(
Expand Down
16 changes: 3 additions & 13 deletions tests/integration/src/rust_masm_tests/rust_sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ fn pure_rust_hir2() {

/// This test demonstrates the use of the testnet integration test infrastructure
#[test]
#[ignore = "this test needs refinement before it can be run by default"]
fn rust_sdk_counter_testnet_example() {
use cargo_miden::BuildOutput;

Expand All @@ -121,8 +120,6 @@ fn rust_sdk_counter_testnet_example() {
"build",
"--manifest-path",
"../../examples/counter-contract/Cargo.toml",
"--lib",
"--release",
]
.iter()
.map(|s| s.to_string())
Expand All @@ -146,24 +143,17 @@ fn rust_sdk_counter_testnet_example() {

let config = WasmTranslationConfig::default();

let mut builder = CompilerTestBuilder::rust_source_cargo_miden(
"../../examples/counter-note",
config,
["-l".into(), masp_path.clone().into_os_string().into_string().unwrap().into()],
);
let mut builder =
CompilerTestBuilder::rust_source_cargo_miden("../../examples/counter-note", config, []);
builder.with_target_dir(&target_dir);
builder.with_entrypoint(FunctionIdent {
module: Ident::new(Symbol::intern("miden:base/[email protected]"), SourceSpan::default()),
function: Ident::new(Symbol::intern("note-script"), SourceSpan::default()),
});
let mut test = builder.build();
let note_package = test.compiled_package();

let account_package =
Arc::new(Package::read_from_bytes(&std::fs::read(masp_path).unwrap()).unwrap());

let key = [Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(0)];
let expected = [Felt::new(1), Felt::new(0), Felt::new(0), Felt::new(0)];
let expected = [Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(1)];
scenario
.create_account("example", account_package)
.then()
Expand Down
323 changes: 323 additions & 0 deletions tests/integration/src/rust_masm_tests/testnet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
//! This module provides accommodation for the integration tests that execute against the Miden
//! testnet

use std::{env, sync::Arc, time::Duration};

use miden_client::{
account::{
component::{BasicWallet, RpoFalcon512},
Account, AccountBuilder, AccountId, AccountStorage, AccountStorageMode, AccountType,
StorageMap, StorageSlot,
},
auth::AuthSecretKey,
builder::ClientBuilder,
crypto::{FeltRng, SecretKey},
keystore::FilesystemKeyStore,
note::{
Note, NoteExecutionHint, NoteExecutionMode, NoteInputs, NoteMetadata, NoteRecipient,
NoteScript, NoteTag, NoteType,
},
rpc::{Endpoint, TonicRpcClient},
store::InputNoteRecord,
transaction::{OutputNote, TransactionKernel, TransactionRequestBuilder},
Client, ClientError, Felt, Word,
};
use miden_core::{
utils::{Deserializable, Serializable},
FieldElement,
};
use miden_objects::{
account::{
AccountComponent, AccountComponentMetadata, AccountComponentTemplate, InitStorageData,
StorageValueName,
},
Hasher,
};
use midenc_frontend_wasm::WasmTranslationConfig;
use rand::{rngs::StdRng, RngCore};
use tokio::time::sleep;

use crate::CompilerTestBuilder;

/// Helper to create a basic account
#[allow(dead_code)]
async fn create_basic_account(
client: &mut Client,
keystore: Arc<FilesystemKeyStore<StdRng>>,
) -> Result<Account, ClientError> {
let mut init_seed = [0_u8; 32];
client.rng().fill_bytes(&mut init_seed);

let key_pair = SecretKey::with_rng(client.rng());
let anchor_block = client.get_latest_epoch_block().await.unwrap();
let builder = AccountBuilder::new(init_seed)
.anchor((&anchor_block).try_into().unwrap())
.account_type(AccountType::RegularAccountUpdatableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(RpoFalcon512::new(key_pair.public_key()))
.with_component(BasicWallet);
let (account, seed) = builder.build().unwrap();
client.add_account(&account, Some(seed), false).await?;
keystore.add_key(&AuthSecretKey::RpoFalcon512(key_pair)).unwrap();

Ok(account)
}

/// Helper to create a basic account with the counter contract
async fn create_counter_account(
client: &mut Client,
keystore: Arc<FilesystemKeyStore<StdRng>>,
account_package: Arc<miden_mast_package::Package>,
) -> Result<Account, ClientError> {
// TODO: WTF no error?
// let init_storage =
// InitStorageData::new([(StorageValueName::new("count_map").unwrap(), "0.0.0.0".into())]);
let account_component = match account_package.account_component_metadata_bytes.as_deref() {
None => panic!("no account component metadata present"),
Some(bytes) => {
let metadata = AccountComponentMetadata::read_from_bytes(bytes).unwrap();
let template = AccountComponentTemplate::new(
metadata,
account_package.unwrap_library().as_ref().clone(),
);

let word_zero = Word::from([Felt::ZERO; 4]);
AccountComponent::new(
template.library().clone(),
vec![StorageSlot::Map(
StorageMap::with_entries([(word_zero.into(), word_zero)]).unwrap(),
)],
)
.unwrap()
.with_supported_types([AccountType::RegularAccountUpdatableCode].into())
}
};

let mut init_seed = [0_u8; 32];
client.rng().fill_bytes(&mut init_seed);

let key_pair = SecretKey::with_rng(client.rng());
let anchor_block = client.get_latest_epoch_block().await.unwrap();
let builder = AccountBuilder::new(init_seed)
.anchor((&anchor_block).try_into().unwrap())
.account_type(AccountType::RegularAccountUpdatableCode)
.storage_mode(AccountStorageMode::Public)
.with_component(RpoFalcon512::new(key_pair.public_key()))
.with_component(BasicWallet)
.with_component(account_component);
let (account, seed) = builder.build().unwrap();
client.add_account(&account, Some(seed), false).await?;
keystore.add_key(&AuthSecretKey::RpoFalcon512(key_pair)).unwrap();

Ok(account)
}

// Helper to wait until an account has the expected number of consumable notes
async fn wait_for_notes(
client: &mut Client,
account_id: &miden_client::account::Account,
expected: usize,
) -> Result<(), ClientError> {
let mut try_num = 0;
loop {
client.sync_state().await?;
let notes = client.get_consumable_notes(None).await?;
if notes.len() >= expected {
break;
}
eprintln!(
"{} consumable notes found for account {}. Waiting...",
notes.len(),
account_id.id().to_hex()
);
if try_num > 10 {
panic!("waiting for too long");
} else {
try_num += 1;
}
sleep(Duration::from_secs(3)).await;
}
Ok(())
}

fn assert_counter_storage(counter_account_storage: &AccountStorage, expected: u64) {
// dbg!(counter_account_storage);
// according to `examples/counter-contract` for inner (slot, key) values
let counter_contract_storage_key = Word::from([Felt::ZERO; 4]);
// The storage slot is 1 since the RpoFalcon512 account component sits in 0 slot
let counter_val_word =
counter_account_storage.get_map_item(1, counter_contract_storage_key).unwrap();
// Felt is stored in the last word item. See sdk/stdlib-sys/src/intrinsics/word.rs
let counter_val = counter_val_word.last().unwrap();
// dbg!(&counter_val_word);
assert_eq!(counter_val.as_int(), expected);
}

/// Tests the counter contract deployment and note consumption workflow on testnet.
#[test]
pub fn test_counter_contract_testnet() {
// Compile the contracts first (before creating any runtime)
let config = WasmTranslationConfig::default();
let mut contract_builder = CompilerTestBuilder::rust_source_cargo_miden(
"../../examples/counter-contract",
config.clone(),
["--debug=none".into()], // don't include any debug info in the compiled MAST
);
contract_builder.with_release(true);
let mut contract_test = contract_builder.build();
let contract_package = contract_test.compiled_package();

let bytes = <miden_mast_package::Package as Clone>::clone(&contract_package)
.into_mast_artifact()
.unwrap_library()
.to_bytes();
// dbg!(bytes.len());
assert!(bytes.len() < 32767, "expected to fit in 32 KB account update size limit");

// Compile the counter note
let mut note_builder = CompilerTestBuilder::rust_source_cargo_miden(
"../../examples/counter-note",
config,
["--debug=none".into()], // don't include any debug info in the compiled MAST
);
note_builder.with_release(true);
let mut note_test = note_builder.build();
let note_package = note_test.compiled_package();
dbg!(note_package.unwrap_program().mast_forest().advice_map());

let restore_dir = env::current_dir().unwrap();
// switch cwd to temp_dir to have a fresh client store
let temp_dir = temp_dir::TempDir::new().unwrap();
env::set_current_dir(temp_dir.path()).unwrap();

let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
// Initialize client & keystore
let endpoint = Endpoint::testnet();
let timeout_ms = 10_000;
let rpc_api = Arc::new(TonicRpcClient::new(&endpoint, timeout_ms));

let keystore_path = temp_dir.path().join("keystore");
let keystore = Arc::new(FilesystemKeyStore::new(keystore_path.clone()).unwrap());

let mut client = ClientBuilder::new()
.with_rpc(rpc_api)
.with_filesystem_keystore(keystore_path.to_str().unwrap())
.in_debug_mode(true)
.build()
.await
.unwrap();

let sync_summary = client.sync_state().await.unwrap();
eprintln!("Latest block: {}", sync_summary.block_num);

// Create the counter account
let counter_account =
create_counter_account(&mut client, keystore.clone(), contract_package)
.await
.unwrap();
eprintln!("Counter account ID: {:?}", counter_account.id().to_hex());

// client.sync_state().await.unwrap();

// The counter contract storage value should be zero after the account creation
assert_counter_storage(
client
.get_account(counter_account.id())
.await
.unwrap()
.unwrap()
.account()
.storage(),
0,
);

// Create the counter note from sender to counter
let note_program = note_package.unwrap_program();
let note_script =
NoteScript::from_parts(note_program.mast_forest().clone(), note_program.entrypoint());

let serial_num = client.rng().draw_word();
let note_inputs = NoteInputs::new(vec![]).unwrap();
let recipient = NoteRecipient::new(serial_num, note_script, note_inputs);

let tag = NoteTag::for_local_use_case(0, 0).unwrap();
let metadata = NoteMetadata::new(
counter_account.id(), // The sender is who creates the note
NoteType::Public,
tag,
NoteExecutionHint::always(),
Felt::ZERO,
)
.unwrap();

let vault = miden_client::note::NoteAssets::new(vec![]).unwrap();
let counter_note = Note::new(vault, metadata, recipient);
eprintln!("Counter note hash: {:?}", counter_note.id().to_hex());

// Submit transaction to create the note
let note_request = TransactionRequestBuilder::new()
.with_own_output_notes(vec![OutputNote::Full(counter_note.clone())])
.build()
.unwrap();

let tx_result = client.new_transaction(counter_account.id(), note_request).await.unwrap();
let executed_transaction = tx_result.executed_transaction();
// dbg!(executed_transaction.output_notes());

assert_eq!(executed_transaction.output_notes().num_notes(), 1);

let executed_tx_output_note = executed_transaction.output_notes().get_note(0);
assert_eq!(executed_tx_output_note.id(), counter_note.id());
let create_note_tx_id = executed_transaction.id();
// client
// .submit_transaction(tx_result)
// .await
// .expect("failed to submit the tx creating the note");
eprintln!(
"Created counter note tx: https://testnet.midenscan.com/tx/{:?}",
create_note_tx_id
);

// TODO: do we need it?
// client.sync_state().await.unwrap();

// wait_for_notes(&mut client, &counter_account, 1).await.unwrap();

// Consume the note to increment the counter
let consume_request = TransactionRequestBuilder::new()
// .with_authenticated_input_notes([(counter_note.id(), None)])
.with_unauthenticated_input_notes([(counter_note, None)])
.build()
.unwrap();

let tx_result =
client.new_transaction(counter_account.id(), consume_request).await.unwrap();

eprintln!(
"Consumed counter note tx: https://testnet.midenscan.com/tx/{:?}",
tx_result.executed_transaction().id()
);

// client
// .submit_transaction(tx_result)
// .await
// .expect("failed to submit the tx consuming the note");

// client.sync_state().await.unwrap();

// The counter contract storage value should be 1 (incremented) after the note is consumed
assert_counter_storage(
client
.get_account(counter_account.id())
.await
.unwrap()
.unwrap()
.account()
.storage(),
1,
);
});

env::set_current_dir(restore_dir).unwrap();
}
Loading
Loading