Skip to content
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
26 changes: 25 additions & 1 deletion rs/ethereum/cketh/minter/src/ledger_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,36 @@ impl LedgerClient {
from: Account,
amount: A,
memo: BurnMemo,
) -> Result<LedgerBurnIndex, LedgerBurnError> {
self.burn_from_with_spender(from, None, amount, memo).await
}

pub async fn burn_from_own_subaccount<A: Into<Nat>>(
&self,
subaccount: [u8; 32],
amount: A,
memo: BurnMemo,
) -> Result<LedgerBurnIndex, LedgerBurnError> {
let from = Account {
owner: ic_cdk::api::canister_self(),
subaccount: Some(subaccount),
};
self.burn_from_with_spender(from, Some(subaccount), amount, memo)
.await
}

async fn burn_from_with_spender<A: Into<Nat>>(
&self,
from: Account,
spender_subaccount: Option<[u8; 32]>,
amount: A,
memo: BurnMemo,
) -> Result<LedgerBurnIndex, LedgerBurnError> {
let amount = amount.into();
match self
.client
.transfer_from(TransferFromArgs {
spender_subaccount: None,
spender_subaccount,
from,
to: ic_cdk::api::canister_self().into(),
amount: amount.clone(),
Expand Down
7 changes: 7 additions & 0 deletions rs/ethereum/cketh/minter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,10 @@ pub const EVM_RPC_ID_PRODUCTION: Principal =
Principal::from_slice(&[0, 0, 0, 0, 2, 48, 0, 204, 1, 1]);
pub const EVM_RPC_ID_STAGING: Principal = Principal::from_slice(&[0, 0, 0, 0, 2, 48, 0, 161, 1, 1]);
pub const CKETH_LEDGER_MEMO_SIZE: u16 = 80;

pub const CKETH_FEE_SUBACCOUNT: [u8; 32] = {
let mut subaccount = [0_u8; 32];
subaccount[30] = 0x0f;
subaccount[31] = 0xee;
subaccount
};
42 changes: 42 additions & 0 deletions rs/ethereum/cketh/minter/tests/cketh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1357,3 +1357,45 @@ mod cketh_evm_rpc {
}
}
}

#[test]
fn should_burn_from_fee_account_without_an_allowance() {
use ic_cketh_minter::CKETH_FEE_SUBACCOUNT;
use icrc_ledger_types::icrc2::transfer_from::TransferFromArgs;

const FUNDING_AMOUNT: u64 = 1_000_000_000_000_000_000;

let cketh = CkEthSetup::default();
cketh
.call_ledger_mint(cketh.fee_account(), FUNDING_AMOUNT)
.expect("minting into the fee account must succeed");

let supply_before = cketh.total_supply();
let burn_amount = FUNDING_AMOUNT / 4;

let result = cketh.call_ledger_transfer_from(TransferFromArgs {
spender_subaccount: Some(CKETH_FEE_SUBACCOUNT),
from: cketh.fee_account(),
to: cketh.minting_account(),
amount: Nat::from(burn_amount),
fee: None,
memo: None,
created_at_time: None,
});

assert!(
result.is_ok(),
"burning from the fee account while naming it as the spender must need no allowance, \
got {result:?}"
);
assert_eq!(
cketh.balance_of(cketh.fee_account()),
Nat::from(FUNDING_AMOUNT - burn_amount),
"the fee account must be debited by exactly the burned amount (burns are fee-free)"
);
assert_eq!(
cketh.total_supply(),
supply_before - Nat::from(burn_amount),
"a transfer to the minting account must reduce total supply, i.e. be a real burn"
);
}
76 changes: 76 additions & 0 deletions rs/ethereum/cketh/test_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use ic_cketh_minter::endpoints::{
use ic_cketh_minter::lifecycle::upgrade::UpgradeArg;
use ic_cketh_minter::logs::Log;
use ic_cketh_minter::{
CKETH_FEE_SUBACCOUNT,
endpoints::{CandidBlockTag, Eip1559TransactionPrice},
lifecycle::{EthereumNetwork, MinterArg, init::InitArg as MinterInitArgs},
};
Expand All @@ -25,7 +26,9 @@ use ic_management_canister_types::{CanisterId, CanisterIdRecord, CanisterStatusT
use ic_metrics_assert::{MetricsAssert, PocketIcHttpQuery};
use ic_test_utilities_load_wasm::load_wasm;
use icrc_ledger_types::icrc1::account::Account;
use icrc_ledger_types::icrc1::transfer::{TransferArg, TransferError};
use icrc_ledger_types::icrc2::approve::{ApproveArgs, ApproveError};
use icrc_ledger_types::icrc2::transfer_from::{TransferFromArgs, TransferFromError};
use num_traits::cast::ToPrimitive;
use pocket_ic::common::rest::{
CanisterHttpReject, CanisterHttpReply, CanisterHttpRequest, CanisterHttpResponse, IcpConfig,
Expand Down Expand Up @@ -254,6 +257,35 @@ impl CkEthSetup {
.unwrap()
}

pub fn total_supply(&self) -> Nat {
Decode!(
&assert_reply(self.env.query_call(
self.ledger_id,
Principal::anonymous(),
"icrc1_total_supply",
Encode!().unwrap()
)),
Nat
)
.unwrap()
}

/// The ledger's minting account: the minter with no subaccount. Transferring there burns.
pub fn minting_account(&self) -> Account {
Account {
owner: self.minter_id,
subaccount: None,
}
}

/// The minter's fee subaccount, where the ckETH taken as withdrawal fees accumulates.
pub fn fee_account(&self) -> Account {
Account {
owner: self.minter_id,
subaccount: Some(CKETH_FEE_SUBACCOUNT),
}
}

pub fn eip_1559_transaction_price(
&self,
ledger_id: Option<Principal>,
Expand Down Expand Up @@ -323,6 +355,50 @@ impl CkEthSetup {
.unwrap()
}

/// Mints `amount` into `to` by transferring out of the ledger's minting account, which is the
/// minter itself. Lets a test start from a funded account without driving a whole deposit.
pub fn call_ledger_mint(
&self,
to: impl Into<Account>,
amount: u64,
) -> Result<Nat, TransferError> {
Decode!(
&assert_reply(self.env.update_call(
self.ledger_id,
self.minter_id,
"icrc1_transfer",
Encode!(&TransferArg {
from_subaccount: None,
to: to.into(),
fee: None,
created_at_time: None,
memo: None,
amount: Nat::from(amount),
})
.unwrap()
)),
Result<Nat, TransferError>
)
.unwrap()
}

/// Calls `icrc2_transfer_from` on the ckETH ledger as the minter.
pub fn call_ledger_transfer_from(
&self,
args: TransferFromArgs,
) -> Result<Nat, TransferFromError> {
Decode!(
&assert_reply(self.env.update_call(
self.ledger_id,
self.minter_id,
"icrc2_transfer_from",
Encode!(&args).unwrap()
)),
Result<Nat, TransferFromError>
)
.unwrap()
}

pub fn call_ledger_approve_minter(
self,
from: Principal,
Expand Down
Loading