Skip to content

Commit

Permalink
test(ICRC_ledger): FI-1397: Add transaction generation to ICRC golden…
Browse files Browse the repository at this point in the history
… state tests (#1478)

Add generation of transactions to the ICRC tests with golden ledger
state. Also only run the extensive testing (verifying the ledger
internal state wrt. allowances and balances, and generating
transactions), for only one ledger per test.
  • Loading branch information
mbjorkqvist authored and frankdavid committed Sep 25, 2024
1 parent 18a0397 commit 6410717
Show file tree
Hide file tree
Showing 4 changed files with 473 additions and 155 deletions.
11 changes: 8 additions & 3 deletions rs/rosetta-api/icrc1/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,17 @@ rust_test(
# bazel \
# test \
# --test_env=SSH_AUTH_SOCK \
# --test_timeout=43200 \
# //rs/rosetta-api/icrc1:icrc_ledger_suite_integration_golden_state_upgrade_downgrade_test
#
# To run the U256 token version of the test (for ckETH and ckERC20 tokens), use:
#
# //rs/rosetta-api/icrc1:icrc_ledger_suite_integration_golden_state_upgrade_downgrade_test_u256
#
# The only unusual thing in this command is `--test_env=SSH_AUTH_SOCK`. That causes the
# SSH_AUTH_SOCK environment variable to be "forwarded" from your shell to the sandbox where the test
# is run. This authorizes the test to download the test data.
# The unusual things in this command are:
# - `--test_env=SSH_AUTH_SOCK`: This causes the SSH_AUTH_SOCK environment variable to be "forwarded" from
# your shell to the sandbox where the test is run. This authorizes the test to download the test data.
# - `--test_timeout=43200`: This sets the test timeout to 12 hours (more than currently required).
#
# Additionally, the following flags are recommended (but not required):
#
Expand Down Expand Up @@ -141,6 +143,9 @@ rust_test(
"//rs/rosetta-api/icrc1/ledger",
"//rs/rosetta-api/icrc1/ledger/sm-tests:sm-tests" + name_suffix,
"//rs/rosetta-api/icrc1/test_utils",
"//rs/rosetta-api/icrc1/tokens_u256",
"//rs/rosetta-api/icrc1/tokens_u64",
"//rs/rosetta-api/ledger_core",
"//rs/rust_canisters/canister_test",
"//rs/state_machine_tests",
"//rs/test_utilities/load_wasm",
Expand Down
62 changes: 39 additions & 23 deletions rs/rosetta-api/icrc1/ledger/sm-tests/src/in_memory_ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ impl From<ApprovalKey> for (Account, Account) {
}
}

trait InMemoryLedgerState {
pub trait InMemoryLedgerState {
type AccountId;
type Tokens;

fn get_allowance(
&self,
from: &Self::AccountId,
spender: &Self::AccountId,
) -> Option<Allowance<Self::Tokens>>;

fn process_approve(
&mut self,
from: &Self::AccountId,
Expand Down Expand Up @@ -85,6 +91,15 @@ where
type AccountId = AccountId;
type Tokens = Tokens;

fn get_allowance(
&self,
from: &Self::AccountId,
spender: &Self::AccountId,
) -> Option<Allowance<Self::Tokens>> {
let key = K::from((from, spender));
self.allowances.get(&key).cloned()
}

fn process_approve(
&mut self,
from: &Self::AccountId,
Expand Down Expand Up @@ -257,12 +272,16 @@ where
) {
let key = K::from((from, spender));
if let Some(expected_allowance) = expected_allowance {
let current_allowance = self
let current_allowance_amount = self
.allowances
.get(&key)
.unwrap_or_else(|| panic!("No current allowance but expected allowance set"));
if current_allowance.amount != *expected_allowance {
panic!("Expected allowance does not match current allowance");
.map(|allowance| allowance.amount.clone())
.unwrap_or(Tokens::zero());
if current_allowance_amount != *expected_allowance {
panic!(
"Expected allowance ({:?}) does not match current allowance ({:?})",
expected_allowance, current_allowance_amount
);
}
}
if amount == &Tokens::zero() {
Expand Down Expand Up @@ -336,42 +355,40 @@ where
}

impl InMemoryLedger<ApprovalKey, Account, Tokens> {
fn new_from_icrc1_ledger_blocks(
blocks: &[ic_icrc1::Block<Tokens>],
burns_without_spender: Option<BurnsWithoutSpender<Account>>,
) -> InMemoryLedger<ApprovalKey, Account, Tokens> {
let mut state = InMemoryLedger {
pub fn new(burns_without_spender: Option<BurnsWithoutSpender<Account>>) -> Self {
InMemoryLedger {
burns_without_spender,
..Default::default()
};
}
}

pub fn ingest_icrc1_ledger_blocks(&mut self, blocks: &[ic_icrc1::Block<Tokens>]) {
for (index, block) in blocks.iter().enumerate() {
if let Some(fee_collector) = block.fee_collector {
state.fee_collector = Some(fee_collector);
self.fee_collector = Some(fee_collector);
}
match &block.transaction.operation {
Operation::Mint { to, amount } => state.process_mint(to, amount),
Operation::Mint { to, amount } => self.process_mint(to, amount),
Operation::Transfer {
from,
to,
spender,
amount,
fee,
} => {
state.process_transfer(from, to, spender, amount, &fee.or(block.effective_fee))
}
} => self.process_transfer(from, to, spender, amount, &fee.or(block.effective_fee)),
Operation::Burn {
from,
spender,
amount,
} => state.process_burn(from, spender, amount, index),
} => self.process_burn(from, spender, amount, index),
Operation::Approve {
from,
spender,
amount,
expected_allowance,
expires_at,
fee,
} => state.process_approve(
} => self.process_approve(
from,
spender,
amount,
Expand All @@ -381,12 +398,11 @@ impl InMemoryLedger<ApprovalKey, Account, Tokens> {
TimeStamp::from_nanos_since_unix_epoch(block.timestamp),
),
}
state.validate_invariants();
self.validate_invariants();
}
state.prune_expired_allowances(TimeStamp::from_nanos_since_unix_epoch(
self.prune_expired_allowances(TimeStamp::from_nanos_since_unix_epoch(
blocks.last().unwrap().timestamp,
));
state
}

pub fn apply_arg_with_caller(
Expand Down Expand Up @@ -522,8 +538,8 @@ pub fn verify_ledger_state(
println!("verifying state of ledger {}", ledger_id);
let blocks = get_all_ledger_and_archive_blocks(env, ledger_id);
println!("retrieved all ledger and archive blocks");
let expected_ledger_state =
InMemoryLedger::new_from_icrc1_ledger_blocks(&blocks, burns_without_spender);
let mut expected_ledger_state = InMemoryLedger::new(burns_without_spender);
expected_ledger_state.ingest_icrc1_ledger_blocks(&blocks);
println!("recreated expected ledger state");
expected_ledger_state.verify_balances_and_allowances(env, ledger_id);
println!("ledger state verified successfully");
Expand Down
26 changes: 15 additions & 11 deletions rs/rosetta-api/icrc1/ledger/sm-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ fn model_transfer(
((from_balance, to_balance), None)
}

fn send_transfer(
pub fn send_transfer(
env: &StateMachine,
ledger: CanisterId,
from: Principal,
Expand Down Expand Up @@ -539,6 +539,19 @@ pub fn balance_of(env: &StateMachine, ledger: CanisterId, acc: impl Into<Account
.unwrap()
}

pub fn fee(env: &StateMachine, ledger: CanisterId) -> u64 {
Decode!(
&env.query(ledger, "icrc1_fee", Encode!().unwrap())
.expect("failed to query fee")
.bytes(),
Nat
)
.expect("failed to decode icrc1_fee response")
.0
.to_u64()
.unwrap()
}

pub fn metadata(env: &StateMachine, ledger: CanisterId) -> BTreeMap<String, Value> {
Decode!(
&env.query(ledger, "icrc1_metadata", Encode!().unwrap())
Expand Down Expand Up @@ -1948,16 +1961,7 @@ where
.expect("failed to decode balance_of response");
assert_eq!(token_name_after_upgrade, OTHER_TOKEN_NAME);

let token_fee_after_upgrade: u64 = Decode!(
&env.query(canister_id, "icrc1_fee", Encode!().unwrap())
.expect("failed to query fee")
.bytes(),
Nat
)
.expect("failed to decode balance_of response")
.0
.to_u64()
.unwrap();
let token_fee_after_upgrade = fee(&env, canister_id);
assert_eq!(token_fee_after_upgrade, NEW_FEE);
}

Expand Down
Loading

0 comments on commit 6410717

Please sign in to comment.