Skip to content

Commit

Permalink
feat(ledger-core): FI-1437: Implement stable structures storable inte…
Browse files Browse the repository at this point in the history
…rface for Allowance (#2000)

Co-authored-by: IDX GitHub Automation <[email protected]>
Co-authored-by: Mathias Björkqvist <[email protected]>
  • Loading branch information
3 people authored Oct 18, 2024
1 parent d346b93 commit 944b8d0
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions rs/ledger_suite/common/ledger_core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ rust_library(
# Keep sorted.
"//packages/ic-ledger-hash-of:ic_ledger_hash_of",
"@crate_index//:candid",
"@crate_index//:ic-stable-structures",
"@crate_index//:num-traits",
"@crate_index//:serde",
"@crate_index//:serde_bytes",
Expand All @@ -21,6 +22,7 @@ rust_test(
name = "ledger_core_test",
crate = ":ledger_core",
deps = [
"@crate_index//:proptest",
],
)

Expand Down
2 changes: 2 additions & 0 deletions rs/ledger_suite/common/ledger_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ documentation.workspace = true
[dependencies]
candid = { workspace = true }
ic-ledger-hash-of = { path = "../../../../packages/ic-ledger-hash-of" }
ic-stable-structures = { workspace = true }
num-traits = { workspace = true }
serde = { workspace = true }
serde_bytes = { workspace = true }

[dev-dependencies]
proptest = { workspace = true }
43 changes: 43 additions & 0 deletions rs/ledger_suite/common/ledger_core/src/approvals.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use crate::timestamp::TimeStamp;
use crate::tokens::{CheckedSub, TokensType, Zero};
use candid::Nat;
use ic_stable_structures::{storable::Bound, Storable};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
use std::{
borrow::Cow,
io::{Cursor, Read},
};

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -474,3 +480,40 @@ where
fn remote_future() -> TimeStamp {
TimeStamp::from_nanos_since_unix_epoch(u64::MAX)
}

impl<Tokens: Clone + Into<Nat> + TryFrom<Nat, Error = String>> Storable for Allowance<Tokens> {
fn to_bytes(&self) -> Cow<[u8]> {
let mut buffer = vec![];
let amount: Nat = self.amount.clone().into();
amount
.encode(&mut buffer)
.expect("Unable to serialize amount");
if let Some(expires_at) = self.expires_at {
buffer.extend(expires_at.as_nanos_since_unix_epoch().to_le_bytes());
}
// We don't serialize arrived_at - it is not used after stable structures migration.
Cow::Owned(buffer)
}

fn from_bytes(bytes: Cow<[u8]>) -> Self {
let mut cursor = Cursor::new(bytes.into_owned());
let amount = Nat::decode(&mut cursor).expect("Unable to deserialize amount");
let amount = Tokens::try_from(amount).expect("Unable to convert Nat to Tokens");
// arrived_at was not serialized, use a default value.
let arrived_at = TimeStamp::from_nanos_since_unix_epoch(0);
let mut expires_at_bytes = [0u8; 8];
let expires_at = match cursor.read_exact(&mut expires_at_bytes) {
Ok(()) => Some(TimeStamp::from_nanos_since_unix_epoch(u64::from_le_bytes(
expires_at_bytes,
))),
_ => None,
};
Self {
amount,
arrived_at,
expires_at,
}
}

const BOUND: Bound = Bound::Unbounded;
}
35 changes: 35 additions & 0 deletions rs/ledger_suite/common/ledger_core/src/approvals/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::HashSet;
use super::*;
use crate::timestamp::TimeStamp;
use crate::tokens::Tokens;
use ic_stable_structures::Storable;
use std::cmp;

fn ts(n: u64) -> TimeStamp {
Expand Down Expand Up @@ -619,3 +620,37 @@ fn expected_allowance_if_zero_no_approval() {
}
);
}

use proptest::prelude::{any, prop_assert_eq, proptest};
use proptest::strategy::Strategy;

#[test]
fn allowance_serialization() {
fn arb_token() -> impl Strategy<Value = Tokens> {
any::<u64>().prop_map(Tokens::from_e8s)
}
fn arb_timestamp() -> impl Strategy<Value = TimeStamp> {
any::<u64>().prop_map(TimeStamp::from_nanos_since_unix_epoch)
}
fn arb_opt_expiration() -> impl Strategy<Value = Option<TimeStamp>> {
proptest::option::of(any::<u64>().prop_map(TimeStamp::from_nanos_since_unix_epoch))
}
fn arb_allowance() -> impl Strategy<Value = Allowance<Tokens>> {
(arb_token(), arb_opt_expiration(), arb_timestamp()).prop_map(
|(amount, expires_at, arrived_at)| Allowance {
amount,
expires_at,
arrived_at,
},
)
}
proptest!(|(allowance in arb_allowance())| {
let new_allowance: Allowance<Tokens> = Allowance::from_bytes(allowance.to_bytes());
prop_assert_eq!(new_allowance.amount, allowance.amount);
prop_assert_eq!(new_allowance.expires_at, allowance.expires_at);
prop_assert_eq!(
new_allowance.arrived_at,
TimeStamp::from_nanos_since_unix_epoch(0)
);
})
}

0 comments on commit 944b8d0

Please sign in to comment.