From b54653baef1fa9eba908cee4e32575c57deac0b9 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 13 May 2024 21:00:34 +1200 Subject: [PATCH 01/87] update localsecret and secret-contract-optimizer versions --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index eee79095..48cb32ad 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ compile-optimized-reproducible: docker run --rm -v "$$(pwd)":/contract \ --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - enigmampc/secret-contract-optimizer:1.0.7 + enigmampc/secret-contract-optimizer:1.0.10 contract.wasm.gz: contract.wasm cat ./contract.wasm | gzip -9 > ./contract.wasm.gz @@ -76,7 +76,7 @@ start-server: # CTRL+C to stop docker run -it --rm \ -p 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ -v $$(pwd):/root/code \ - --name secretdev ghcr.io/scrtlabs/localsecret:v1.6.0-alpha.4 + --name secretdev docker pull ghcr.io/scrtlabs/localsecret:v1.13.0-rc.2 .PHONY: schema schema: From 5b80e600d1a476535314c7946c5d963105434b4f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 14 May 2024 20:03:26 +1200 Subject: [PATCH 02/87] remove decoy code --- src/batch.rs | 28 +- src/contract.rs | 967 +------------------------------------ src/msg.rs | 101 ---- src/state.rs | 80 +-- src/transaction_history.rs | 169 +------ 5 files changed, 51 insertions(+), 1294 deletions(-) diff --git a/src/batch.rs b/src/batch.rs index dbe47fb1..5c175cbe 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -3,11 +3,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, Binary, Uint128}; - -pub trait HasDecoy { - fn decoys(&self) -> &Option>; -} +use cosmwasm_std::{Binary, Uint128}; #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] #[serde(rename_all = "snake_case")] @@ -15,7 +11,6 @@ pub struct TransferAction { pub recipient: String, pub amount: Uint128, pub memo: Option, - pub decoys: Option>, } #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] @@ -26,7 +21,6 @@ pub struct SendAction { pub amount: Uint128, pub msg: Option, pub memo: Option, - pub decoys: Option>, } #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] @@ -36,7 +30,6 @@ pub struct TransferFromAction { pub recipient: String, pub amount: Uint128, pub memo: Option, - pub decoys: Option>, } #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] @@ -48,7 +41,6 @@ pub struct SendFromAction { pub amount: Uint128, pub msg: Option, pub memo: Option, - pub decoys: Option>, } #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] @@ -57,7 +49,6 @@ pub struct MintAction { pub recipient: String, pub amount: Uint128, pub memo: Option, - pub decoys: Option>, } #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] @@ -66,22 +57,5 @@ pub struct BurnFromAction { pub owner: String, pub amount: Uint128, pub memo: Option, - pub decoys: Option>, -} - -macro_rules! impl_decoyable { - ($struct:ty) => { - impl HasDecoy for $struct { - fn decoys(&self) -> &Option> { - &self.decoys - } - } - }; } -impl_decoyable!(BurnFromAction); -impl_decoyable!(MintAction); -impl_decoyable!(SendFromAction); -impl_decoyable!(TransferFromAction); -impl_decoyable!(TransferAction); -impl_decoyable!(SendAction); diff --git a/src/contract.rs b/src/contract.rs index f5f89789..c6dcf368 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -4,15 +4,14 @@ use cosmwasm_std::{ entry_point, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128, }; -use rand::RngCore; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; -use secret_toolkit_crypto::{sha_256, Prng, SHA256_HASH_SIZE}; +use secret_toolkit_crypto::sha_256; use crate::batch; use crate::msg::{ - AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, Decoyable, ExecuteAnswer, + AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, }; use crate::receiver::Snip20ReceiveMsg; @@ -75,8 +74,6 @@ pub fn instantiate( amount, true, "", - &None, - &None, )?; if let Some(new_total_supply) = total_supply.checked_add(amount) { @@ -95,8 +92,6 @@ pub fn instantiate( msg.symbol.clone(), Some("Initial Balance".to_string()), &env.block, - &None, - &None, )?; } } @@ -137,39 +132,10 @@ pub fn instantiate( Ok(Response::default()) } -fn get_address_position( - store: &mut dyn Storage, - decoys_size: usize, - entropy: &[u8; SHA256_HASH_SIZE], -) -> StdResult { - let mut rng = Prng::new(&PrngStore::load(store)?, entropy); - - let mut new_contract_entropy = [0u8; 20]; - rng.rng.fill_bytes(&mut new_contract_entropy); - - let new_prng_seed = sha_256(&new_contract_entropy); - PrngStore::save(store, new_prng_seed)?; - - // decoys_size is also an accepted output which means: set the account balance after you've set decoys' balanace - Ok(rng.rng.next_u64() as usize % (decoys_size + 1)) -} - #[entry_point] pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { let contract_status = CONTRACT_STATUS.load(deps.storage)?; - let mut account_random_pos: Option = None; - - let entropy = match msg.clone().get_entropy() { - None => [0u8; SHA256_HASH_SIZE], - Some(e) => sha_256(&e.0), - }; - - let decoys_size = msg.get_minimal_decoys_size(); - if decoys_size != 0 { - account_random_pos = Some(get_address_position(deps.storage, decoys_size, &entropy)?); - } - match contract_status { ContractStatusLevel::StopAll | ContractStatusLevel::StopAllButRedeems => { let response = match msg { @@ -179,10 +145,9 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::Redeem { amount, denom, - decoys, .. } if contract_status == ContractStatusLevel::StopAllButRedeems => { - try_redeem(deps, env, info, amount, denom, decoys, account_random_pos) + try_redeem(deps, env, info, amount, denom) } _ => Err(StdError::generic_err( "This contract is stopped and this action is not allowed", @@ -195,22 +160,20 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S let response = match msg.clone() { // Native - ExecuteMsg::Deposit { decoys, .. } => { - try_deposit(deps, env, info, decoys, account_random_pos) + ExecuteMsg::Deposit { .. } => { + try_deposit(deps, env, info) } ExecuteMsg::Redeem { amount, denom, - decoys, .. - } => try_redeem(deps, env, info, amount, denom, decoys, account_random_pos), + } => try_redeem(deps, env, info, amount, denom), // Base ExecuteMsg::Transfer { recipient, amount, memo, - decoys, .. } => try_transfer( deps, @@ -219,8 +182,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S recipient, amount, memo, - decoys, - account_random_pos, ), ExecuteMsg::Send { recipient, @@ -228,7 +189,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, msg, memo, - decoys, .. } => try_send( deps, @@ -239,21 +199,18 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, msg, - decoys, - account_random_pos, ), ExecuteMsg::BatchTransfer { actions, .. } => { - try_batch_transfer(deps, env, info, actions, account_random_pos) + try_batch_transfer(deps, env, info, actions) } ExecuteMsg::BatchSend { actions, .. } => { - try_batch_send(deps, env, info, actions, account_random_pos) + try_batch_send(deps, env, info, actions) } ExecuteMsg::Burn { amount, memo, - decoys, .. - } => try_burn(deps, env, info, amount, memo, decoys, account_random_pos), + } => try_burn(deps, env, info, amount, memo), ExecuteMsg::RegisterReceive { code_hash, .. } => { try_register_receive(deps, info, code_hash) } @@ -278,7 +235,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S recipient, amount, memo, - decoys, .. } => try_transfer_from( deps, @@ -288,8 +244,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S recipient, amount, memo, - decoys, - account_random_pos, ), ExecuteMsg::SendFrom { owner, @@ -298,7 +252,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, msg, memo, - decoys, .. } => try_send_from( deps, @@ -310,20 +263,17 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, msg, - decoys, - account_random_pos, ), ExecuteMsg::BatchTransferFrom { actions, .. } => { - try_batch_transfer_from(deps, &env, info, actions, account_random_pos) + try_batch_transfer_from(deps, &env, info, actions) } ExecuteMsg::BatchSendFrom { actions, .. } => { - try_batch_send_from(deps, env, &info, actions, account_random_pos) + try_batch_send_from(deps, env, &info, actions) } ExecuteMsg::BurnFrom { owner, amount, memo, - decoys, .. } => try_burn_from( deps, @@ -332,11 +282,9 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S owner, amount, memo, - decoys, - account_random_pos, ), ExecuteMsg::BatchBurnFrom { actions, .. } => { - try_batch_burn_from(deps, &env, info, actions, account_random_pos) + try_batch_burn_from(deps, &env, info, actions) } // Mint @@ -344,7 +292,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S recipient, amount, memo, - decoys, .. } => try_mint( deps, @@ -353,11 +300,9 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S recipient, amount, memo, - decoys, - account_random_pos, ), ExecuteMsg::BatchMint { actions, .. } => { - try_batch_mint(deps, env, info, actions, account_random_pos) + try_batch_mint(deps, env, info, actions) } // Other @@ -419,7 +364,6 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result< QueryWithPermit::TransferHistory { page, page_size, - should_filter_decoys, } => { if !permit.check_permission(&TokenPermissions::History) { return Err(StdError::generic_err(format!( @@ -433,13 +377,11 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result< account, page.unwrap_or(0), page_size, - should_filter_decoys, ) } QueryWithPermit::TransactionHistory { page, page_size, - should_filter_decoys, } => { if !permit.check_permission(&TokenPermissions::History) { return Err(StdError::generic_err(format!( @@ -453,7 +395,6 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result< account, page.unwrap_or(0), page_size, - should_filter_decoys, ) } QueryWithPermit::Allowance { owner, spender } => { @@ -533,27 +474,23 @@ pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult { address, page, page_size, - should_filter_decoys, .. } => query_transfers( deps, address, page.unwrap_or(0), page_size, - should_filter_decoys, ), QueryMsg::TransactionHistory { address, page, page_size, - should_filter_decoys, .. } => query_transactions( deps, address, page.unwrap_or(0), page_size, - should_filter_decoys, ), QueryMsg::Allowance { owner, spender, .. } => query_allowance(deps, owner, spender), QueryMsg::AllowancesGiven { @@ -644,7 +581,6 @@ pub fn query_transfers( account: String, page: u32, page_size: u32, - should_filter_decoys: bool, ) -> StdResult { // Notice that if query_transfers() was called by a viewking-key call, the address of 'account' // has already been validated. @@ -657,7 +593,6 @@ pub fn query_transfers( account, page, page_size, - should_filter_decoys, )?; let result = QueryAnswer::TransferHistory { @@ -672,7 +607,6 @@ pub fn query_transactions( account: String, page: u32, page_size: u32, - should_filter_decoys: bool, ) -> StdResult { // Notice that if query_transactions() was called by a viewking-key call, the address of // 'account' has already been validated. @@ -681,7 +615,7 @@ pub fn query_transactions( let account = Addr::unchecked(account); let (txs, total) = - StoredExtendedTx::get_txs(deps.storage, account, page, page_size, should_filter_decoys)?; + StoredExtendedTx::get_txs(deps.storage, account, page, page_size)?; let result = QueryAnswer::TransactionHistory { txs, @@ -786,8 +720,6 @@ fn try_mint_impl( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, - decoys: Option>, - account_random_pos: Option, ) -> StdResult<()> { let raw_amount = amount.u128(); @@ -797,8 +729,6 @@ fn try_mint_impl( raw_amount, true, "", - &decoys, - &account_random_pos, )?; store_mint( @@ -809,8 +739,6 @@ fn try_mint_impl( denom, memo, block, - &decoys, - &account_random_pos, )?; Ok(()) @@ -824,8 +752,6 @@ fn try_mint( recipient: String, amount: Uint128, memo: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let recipient = deps.api.addr_validate(recipient.as_str())?; @@ -857,8 +783,6 @@ fn try_mint( constants.symbol, memo, &env.block, - decoys, - account_random_pos, )?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?)) @@ -869,7 +793,6 @@ fn try_batch_mint( env: Env, info: MessageInfo, actions: Vec, - account_random_pos: Option, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; @@ -901,8 +824,6 @@ fn try_batch_mint( constants.symbol.clone(), action.memo, &env.block, - action.decoys, - account_random_pos, )?; } @@ -1041,8 +962,6 @@ fn try_deposit( deps: DepsMut, env: Env, info: MessageInfo, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; @@ -1083,8 +1002,6 @@ fn try_deposit( raw_amount, true, "", - &decoys, - &account_random_pos, )?; store_deposit( @@ -1093,8 +1010,6 @@ fn try_deposit( Uint128::new(raw_amount), "uscrt".to_string(), &env.block, - &decoys, - &account_random_pos, )?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?)) @@ -1106,8 +1021,6 @@ fn try_redeem( info: MessageInfo, amount: Uint128, denom: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; if !constants.redeem_is_enabled { @@ -1142,8 +1055,6 @@ fn try_redeem( amount_raw, false, "redeem", - &decoys, - &account_random_pos, )?; let total_supply = TOTAL_SUPPLY.load(deps.storage)?; @@ -1176,8 +1087,6 @@ fn try_redeem( amount, constants.symbol, &env.block, - &decoys, - &account_random_pos, )?; let message = CosmosMsg::Bank(BankMsg::Send { @@ -1197,16 +1106,12 @@ fn try_transfer_impl( amount: Uint128, memo: Option, block: &cosmwasm_std::BlockInfo, - decoys: Option>, - account_random_pos: Option, ) -> StdResult<()> { perform_transfer( deps.storage, sender, recipient, amount.u128(), - &decoys, - &account_random_pos, )?; let symbol = CONFIG.load(deps.storage)?.symbol; @@ -1219,8 +1124,6 @@ fn try_transfer_impl( symbol, memo, block, - &decoys, - &account_random_pos, )?; Ok(()) @@ -1234,8 +1137,6 @@ fn try_transfer( recipient: String, amount: Uint128, memo: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let recipient = deps.api.addr_validate(recipient.as_str())?; @@ -1246,8 +1147,6 @@ fn try_transfer( amount, memo, &env.block, - decoys, - account_random_pos, )?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?)) @@ -1258,7 +1157,6 @@ fn try_batch_transfer( env: Env, info: MessageInfo, actions: Vec, - account_random_pos: Option, ) -> StdResult { for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; @@ -1269,8 +1167,6 @@ fn try_batch_transfer( action.amount, action.memo, &env.block, - action.decoys, - account_random_pos, )?; } @@ -1322,8 +1218,6 @@ fn try_send_impl( memo: Option, msg: Option, block: &cosmwasm_std::BlockInfo, - decoys: Option>, - account_random_pos: Option, ) -> StdResult<()> { try_transfer_impl( deps, @@ -1332,8 +1226,6 @@ fn try_send_impl( amount, memo.clone(), block, - decoys, - account_random_pos, )?; try_add_receiver_api_callback( @@ -1361,8 +1253,6 @@ fn try_send( amount: Uint128, memo: Option, msg: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let recipient = deps.api.addr_validate(recipient.as_str())?; @@ -1377,8 +1267,6 @@ fn try_send( memo, msg, &env.block, - decoys, - account_random_pos, )?; Ok(Response::new() @@ -1391,7 +1279,6 @@ fn try_batch_send( env: Env, info: MessageInfo, actions: Vec, - account_random_pos: Option, ) -> StdResult { let mut messages = vec![]; for action in actions { @@ -1406,8 +1293,6 @@ fn try_batch_send( action.memo, action.msg, &env.block, - action.decoys, - account_random_pos, )?; } @@ -1467,8 +1352,6 @@ fn try_transfer_from_impl( recipient: &Addr, amount: Uint128, memo: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult<()> { let raw_amount = amount.u128(); @@ -1479,8 +1362,6 @@ fn try_transfer_from_impl( owner, recipient, raw_amount, - &decoys, - &account_random_pos, )?; let symbol = CONFIG.load(deps.storage)?.symbol; @@ -1493,8 +1374,6 @@ fn try_transfer_from_impl( symbol, memo, &env.block, - &decoys, - &account_random_pos, )?; Ok(()) @@ -1509,8 +1388,6 @@ fn try_transfer_from( recipient: String, amount: Uint128, memo: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; @@ -1522,8 +1399,6 @@ fn try_transfer_from( &recipient, amount, memo, - decoys, - account_random_pos, )?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?)) @@ -1534,7 +1409,6 @@ fn try_batch_transfer_from( env: &Env, info: MessageInfo, actions: Vec, - account_random_pos: Option, ) -> StdResult { for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; @@ -1547,8 +1421,6 @@ fn try_batch_transfer_from( &recipient, action.amount, action.memo, - action.decoys, - account_random_pos, )?; } @@ -1571,8 +1443,6 @@ fn try_send_from_impl( amount: Uint128, memo: Option, msg: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult<()> { let spender = info.sender.clone(); try_transfer_from_impl( @@ -1583,8 +1453,6 @@ fn try_send_from_impl( &recipient, amount, memo.clone(), - decoys, - account_random_pos, )?; try_add_receiver_api_callback( @@ -1613,8 +1481,6 @@ fn try_send_from( amount: Uint128, memo: Option, msg: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; @@ -1630,8 +1496,6 @@ fn try_send_from( amount, memo, msg, - decoys, - account_random_pos, )?; Ok(Response::new() @@ -1644,7 +1508,6 @@ fn try_batch_send_from( env: Env, info: &MessageInfo, actions: Vec, - account_random_pos: Option, ) -> StdResult { let mut messages = vec![]; @@ -1662,8 +1525,6 @@ fn try_batch_send_from( action.amount, action.memo, action.msg, - action.decoys, - account_random_pos, )?; } @@ -1682,8 +1543,6 @@ fn try_burn_from( owner: String, amount: Uint128, memo: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let owner = deps.api.addr_validate(owner.as_str())?; let constants = CONFIG.load(deps.storage)?; @@ -1702,8 +1561,6 @@ fn try_burn_from( raw_amount, false, "burn", - &decoys, - &account_random_pos, )?; // remove from supply @@ -1726,8 +1583,6 @@ fn try_burn_from( constants.symbol, memo, &env.block, - &decoys, - &account_random_pos, )?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?)) @@ -1738,7 +1593,6 @@ fn try_batch_burn_from( env: &Env, info: MessageInfo, actions: Vec, - account_random_pos: Option, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; if !constants.burn_is_enabled { @@ -1761,8 +1615,6 @@ fn try_batch_burn_from( amount, false, "burn", - &action.decoys, - &account_random_pos, )?; // remove from supply @@ -1782,8 +1634,6 @@ fn try_batch_burn_from( constants.symbol.clone(), action.memo, &env.block, - &action.decoys, - &account_random_pos, )?; } @@ -1952,8 +1802,6 @@ fn try_burn( info: MessageInfo, amount: Uint128, memo: Option, - decoys: Option>, - account_random_pos: Option, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; if !constants.burn_is_enabled { @@ -1970,8 +1818,6 @@ fn try_burn( raw_amount, false, "burn", - &decoys, - &account_random_pos, )?; let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; @@ -1992,8 +1838,6 @@ fn try_burn( constants.symbol, memo, &env.block, - &decoys, - &account_random_pos, )?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?)) @@ -2004,18 +1848,14 @@ fn perform_transfer( from: &Addr, to: &Addr, amount: u128, - decoys: &Option>, - account_random_pos: &Option, ) -> StdResult<()> { - BalancesStore::update_balance(store, from, amount, false, "transfer", &None, &None)?; + BalancesStore::update_balance(store, from, amount, false, "transfer")?; BalancesStore::update_balance( store, to, amount, true, "transfer", - decoys, - account_random_pos, )?; Ok(()) @@ -2347,8 +2187,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1000), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2367,8 +2205,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(10000), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2379,63 +2215,6 @@ mod tests { assert!(error.contains("insufficient funds")); } - #[test] - fn test_decoys_balance_stays_on_transfer() { - let (init_result, mut deps) = init_helper(vec![ - InitialBalance { - address: "bob".to_string(), - amount: Uint128::new(5000), - }, - InitialBalance { - address: "lior".to_string(), - amount: Uint128::new(7000), - }, - ]); - - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let bob_addr = Addr::unchecked("bob".to_string()); - let alice_addr = Addr::unchecked("alice".to_string()); - let lior_addr = Addr::unchecked("lior".to_string()); - let jhon_addr = Addr::unchecked("jhon".to_string()); - - let bob_balance = BalancesStore::load(&deps.storage, &bob_addr); - let alice_balance = BalancesStore::load(&deps.storage, &alice_addr); - let lior_balance = BalancesStore::load(&deps.storage, &lior_addr); - let jhon_balance = BalancesStore::load(&deps.storage, &jhon_addr); - - let handle_msg = ExecuteMsg::Transfer { - recipient: "alice".to_string(), - amount: Uint128::new(1000), - memo: None, - decoys: Some(vec![lior_addr.clone(), jhon_addr.clone()]), - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - assert_eq!( - bob_balance - 1000, - BalancesStore::load(&deps.storage, &bob_addr) - ); - assert_eq!( - alice_balance + 1000, - BalancesStore::load(&deps.storage, &alice_addr) - ); - assert_eq!(lior_balance, BalancesStore::load(&deps.storage, &lior_addr)); - assert_eq!(jhon_balance, BalancesStore::load(&deps.storage, &jhon_addr)); - } - #[test] fn test_handle_send() { let (init_result, mut deps) = init_helper(vec![InitialBalance { @@ -2466,8 +2245,6 @@ mod tests { memo: Some("my memo".to_string()), padding: None, msg: Some(to_binary("hey hey you you").unwrap()), - decoys: None, - entropy: None, }; let info = mock_info("bob", &[]); @@ -2837,8 +2614,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2500), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -2869,8 +2644,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2500), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -2886,8 +2659,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2000), memo: None, - decoys: None, - entropy: None, padding: None, }; @@ -2922,8 +2693,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2000), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -2951,8 +2720,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -2983,8 +2750,6 @@ mod tests { amount: Uint128::new(2500), memo: None, msg: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3017,8 +2782,6 @@ mod tests { amount: Uint128::new(2500), memo: None, msg: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3057,8 +2820,6 @@ mod tests { amount: Uint128::new(2000), memo: Some("my memo".to_string()), msg: Some(send_msg), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3096,8 +2857,6 @@ mod tests { amount: Uint128::new(1), memo: None, msg: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3142,8 +2901,6 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3158,8 +2915,6 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3189,8 +2944,6 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3205,8 +2958,6 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2000), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3229,8 +2980,6 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(1), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3287,12 +3036,10 @@ mod tests { owner: name.to_string(), amount: Uint128::new(2500), memo: None, - decoys: None, }) .collect(); let handle_msg = ExecuteMsg::BatchBurnFrom { actions, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3334,8 +3081,6 @@ mod tests { owner: "name".to_string(), amount: Uint128::new(2500), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3353,13 +3098,11 @@ mod tests { owner: name.to_string(), amount: Uint128::new(*amount), memo: None, - decoys: None, }) .collect(); let handle_msg = ExecuteMsg::BatchBurnFrom { actions, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3386,13 +3129,11 @@ mod tests { owner: name.to_string(), amount: Uint128::new(allowance_size - *amount), memo: None, - decoys: None, }) .collect(); let handle_msg = ExecuteMsg::BatchBurnFrom { actions, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3419,12 +3160,10 @@ mod tests { owner: name.to_string(), amount: Uint128::new(1), memo: None, - decoys: None, }) .collect(); let handle_msg = ExecuteMsg::BatchBurnFrom { actions, - entropy: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3697,8 +3436,6 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("butler", &[]); @@ -3712,8 +3449,6 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("butler", &[]); @@ -3730,8 +3465,6 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("butler", &[]); @@ -3748,8 +3481,6 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: Option::from("uscrt".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("butler", &[]); @@ -3797,8 +3528,6 @@ mod tests { ); // test when deposit disabled let handle_msg = ExecuteMsg::Deposit { - decoys: None, - entropy: None, padding: None, }; let info = mock_info( @@ -3814,8 +3543,6 @@ mod tests { assert!(error.contains("Tried to deposit an unsupported coin uscrt")); let handle_msg = ExecuteMsg::Deposit { - decoys: None, - entropy: None, padding: None, }; @@ -3871,8 +3598,6 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -3887,8 +3612,6 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(burn_amount), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -3939,8 +3662,6 @@ mod tests { recipient: "lebron".to_string(), amount: Uint128::new(mint_amount), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -3956,8 +3677,6 @@ mod tests { recipient: "lebron".to_string(), amount: Uint128::new(mint_amount), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4090,8 +3809,6 @@ mod tests { recipient: "account".to_string(), amount: Uint128::new(123), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4107,8 +3824,6 @@ mod tests { let withdraw_msg = ExecuteMsg::Redeem { amount: Uint128::new(5000), denom: Option::from("uscrt".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -4153,8 +3868,6 @@ mod tests { recipient: "account".to_string(), amount: Uint128::new(123), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4170,8 +3883,6 @@ mod tests { let withdraw_msg = ExecuteMsg::Redeem { amount: Uint128::new(5000), denom: Option::from("uscrt".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -4250,8 +3961,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -4264,8 +3973,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4341,8 +4048,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -4355,8 +4060,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4431,8 +4134,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -4446,8 +4147,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4472,8 +4171,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -4487,8 +4184,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5340,8 +5035,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1000), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5354,8 +5047,6 @@ mod tests { recipient: "banana".to_string(), amount: Uint128::new(500), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5368,8 +5059,6 @@ mod tests { recipient: "mango".to_string(), amount: Uint128::new(2500), memo: None, - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5384,7 +5073,6 @@ mod tests { key: "key".to_string(), page: None, page_size: 0, - should_filter_decoys: false, }; let query_result = query(deps.as_ref(), mock_env(), query_msg); // let a: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); @@ -5400,7 +5088,6 @@ mod tests { key: "key".to_string(), page: None, page_size: 10, - should_filter_decoys: false, }; let query_result = query(deps.as_ref(), mock_env(), query_msg); let transfers = match from_binary(&query_result.unwrap()).unwrap() { @@ -5414,7 +5101,6 @@ mod tests { key: "key".to_string(), page: None, page_size: 2, - should_filter_decoys: false, }; let query_result = query(deps.as_ref(), mock_env(), query_msg); let transfers = match from_binary(&query_result.unwrap()).unwrap() { @@ -5428,143 +5114,6 @@ mod tests { key: "key".to_string(), page: Some(1), page_size: 2, - should_filter_decoys: false, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 1); - } - - #[test] - fn test_query_transfer_history_with_decoys() { - let (init_result, mut deps) = init_helper(vec![ - InitialBalance { - address: "bob".to_string(), - amount: Uint128::new(5000), - }, - InitialBalance { - address: "jhon".to_string(), - amount: Uint128::new(7000), - }, - ]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "alice_key".to_string(), - padding: None, - }; - let info = mock_info("alice", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "lior_key".to_string(), - padding: None, - }; - let info = mock_info("lior", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "banana_key".to_string(), - padding: None, - }; - let info = mock_info("banana", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(ensure_success(handle_result.unwrap())); - - let lior_addr = Addr::unchecked("lior".to_string()); - let jhon_addr = Addr::unchecked("jhon".to_string()); - let alice_addr = Addr::unchecked("alice".to_string()); - - let handle_msg = ExecuteMsg::Transfer { - recipient: "alice".to_string(), - amount: Uint128::new(1000), - memo: None, - decoys: Some(vec![ - lior_addr.clone(), - jhon_addr.clone(), - alice_addr.clone(), - ]), - - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - let handle_msg = ExecuteMsg::Transfer { - recipient: "banana".to_string(), - amount: Uint128::new(500), - memo: None, - decoys: None, - entropy: None, - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let query_msg = QueryMsg::TransferHistory { - address: "bob".to_string(), - key: "key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: true, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 2); - - let query_msg = QueryMsg::TransferHistory { - address: "alice".to_string(), - key: "alice_key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: false, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 2); - - let query_msg = QueryMsg::TransferHistory { - address: "alice".to_string(), - key: "alice_key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: true, }; let query_result = query(deps.as_ref(), mock_env(), query_msg); let transfers = match from_binary(&query_result.unwrap()).unwrap() { @@ -5572,78 +5121,6 @@ mod tests { _ => panic!("Unexpected"), }; assert_eq!(transfers.len(), 1); - - let query_msg = QueryMsg::TransferHistory { - address: "banana".to_string(), - key: "banana_key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: true, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 1); - - let query_msg = QueryMsg::TransferHistory { - address: "lior".to_string(), - key: "lior_key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: true, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 0); - - let query_msg = QueryMsg::Balance { - address: "bob".to_string(), - key: "key".to_string(), - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let balance = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Balance { amount } => amount, - _ => panic!("Unexpected"), - }; - assert_eq!(balance, Uint128::new(3500)); - - let query_msg = QueryMsg::Balance { - address: "alice".to_string(), - key: "alice_key".to_string(), - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let balance = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Balance { amount } => amount, - _ => panic!("Unexpected"), - }; - assert_eq!(balance, Uint128::new(1000)); - - let query_msg = QueryMsg::Balance { - address: "banana".to_string(), - key: "banana_key".to_string(), - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let balance = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Balance { amount } => amount, - _ => panic!("Unexpected"), - }; - assert_eq!(balance, Uint128::new(500)); - - let query_msg = QueryMsg::Balance { - address: "lior".to_string(), - key: "lior_key".to_string(), - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let balance = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::Balance { amount } => amount, - _ => panic!("Unexpected"), - }; - assert_eq!(balance, Uint128::new(0)); } #[test] @@ -5679,8 +5156,6 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(1), memo: Some("my burn message".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5696,8 +5171,6 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: Option::from("uscrt".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5714,8 +5187,6 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: Some("my mint message".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5725,8 +5196,6 @@ mod tests { assert!(ensure_success(handle_result.unwrap())); let handle_msg = ExecuteMsg::Deposit { - decoys: None, - entropy: None, padding: None, }; let info = mock_info( @@ -5748,8 +5217,6 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1000), memo: Some("my transfer message #1".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5763,8 +5230,6 @@ mod tests { recipient: "banana".to_string(), amount: Uint128::new(500), memo: Some("my transfer message #2".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5778,8 +5243,6 @@ mod tests { recipient: "mango".to_string(), amount: Uint128::new(2500), memo: Some("my transfer message #3".to_string()), - decoys: None, - entropy: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5794,7 +5257,6 @@ mod tests { key: "key".to_string(), page: None, page_size: 10, - should_filter_decoys: false, }; let query_result = query(deps.as_ref(), mock_env(), query_msg); let transfers = match from_binary(&query_result.unwrap()).unwrap() { @@ -5808,7 +5270,6 @@ mod tests { key: "key".to_string(), page: None, page_size: 10, - should_filter_decoys: false, }; let query_result = query(deps.as_ref(), mock_env(), query_msg); let transfers = match from_binary(&query_result.unwrap()).unwrap() { @@ -5933,402 +5394,4 @@ mod tests { assert_eq!(transfers, expected_transfers); } - #[test] - fn test_query_transaction_history_with_decoys() { - let (init_result, mut deps) = init_helper_with_config( - vec![ - InitialBalance { - address: "bob".to_string(), - amount: Uint128::new(5000), - }, - InitialBalance { - address: "jhon".to_string(), - amount: Uint128::new(7000), - }, - ], - true, - true, - true, - true, - 1000, - vec!["uscrt".to_string()], - ); - - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "alice_key".to_string(), - padding: None, - }; - let info = mock_info("alice", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "lior_key".to_string(), - padding: None, - }; - let info = mock_info("lior", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "jhon_key".to_string(), - padding: None, - }; - let info = mock_info("jhon", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(ensure_success(handle_result.unwrap())); - - let lior_addr = Addr::unchecked("lior".to_string()); - let jhon_addr = Addr::unchecked("jhon".to_string()); - let alice_addr = Addr::unchecked("alice".to_string()); - - let handle_msg = ExecuteMsg::Burn { - amount: Uint128::new(1), - memo: Some("my burn message".to_string()), - decoys: Some(vec![ - lior_addr.clone(), - jhon_addr.clone(), - alice_addr.clone(), - ]), - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "Pause handle failed: {}", - handle_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Redeem { - amount: Uint128::new(1000), - denom: Option::from("uscrt".to_string()), - decoys: Some(vec![ - lior_addr.clone(), - jhon_addr.clone(), - alice_addr.clone(), - ]), - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Mint { - recipient: "bob".to_string(), - amount: Uint128::new(100), - memo: Some("my mint message".to_string()), - decoys: Some(vec![ - lior_addr.clone(), - jhon_addr.clone(), - alice_addr.clone(), - ]), - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - let info = mock_info("admin", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::Deposit { - decoys: Some(vec![ - lior_addr.clone(), - jhon_addr.clone(), - alice_addr.clone(), - ]), - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - let info = mock_info( - "bob", - &[Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000), - }], - ); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - assert!( - handle_result.is_ok(), - "handle() failed: {}", - handle_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::Transfer { - recipient: "alice".to_string(), - amount: Uint128::new(1000), - memo: Some("my transfer message #1".to_string()), - decoys: Some(vec![ - lior_addr.clone(), - jhon_addr.clone(), - alice_addr.clone(), - ]), - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let handle_msg = ExecuteMsg::Transfer { - recipient: "banana".to_string(), - amount: Uint128::new(500), - memo: Some("my transfer message #2".to_string()), - decoys: Some(vec![ - lior_addr.clone(), - jhon_addr.clone(), - alice_addr.clone(), - ]), - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let handle_msg = ExecuteMsg::Transfer { - recipient: "mango".to_string(), - amount: Uint128::new(2500), - memo: Some("my transfer message #3".to_string()), - decoys: Some(vec![ - lior_addr.clone(), - jhon_addr.clone(), - alice_addr.clone(), - ]), - entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let query_msg = QueryMsg::TransactionHistory { - address: "lior".to_string(), - key: "lior_key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: true, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transactions = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransactionHistory { txs, .. } => txs, - other => panic!("Unexpected: {:?}", other), - }; - - assert!(transactions.is_empty()); - - let query_msg = QueryMsg::TransactionHistory { - address: "alice".to_string(), - key: "alice_key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: false, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transactions = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransactionHistory { txs, .. } => txs, - other => panic!("Unexpected: {:?}", other), - }; - - assert_eq!(transactions.len(), 7); // Transfer from bob - - let query_msg = QueryMsg::TransactionHistory { - address: "alice".to_string(), - key: "alice_key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: true, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transactions = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransactionHistory { txs, .. } => txs, - other => panic!("Unexpected: {:?}", other), - }; - - assert_eq!(transactions.len(), 1); // Transfer from bob - - let query_msg = QueryMsg::TransactionHistory { - address: "jhon".to_string(), - key: "jhon_key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: true, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transactions = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransactionHistory { txs, .. } => txs, - other => panic!("Unexpected: {:?}", other), - }; - - assert_eq!(transactions.len(), 1); // Mint on init - - let query_msg = QueryMsg::TransactionHistory { - address: "bob".to_string(), - key: "key".to_string(), - page: None, - page_size: 10, - should_filter_decoys: true, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transactions = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransactionHistory { txs, .. } => txs, - other => panic!("Unexpected: {:?}", other), - }; - - use crate::transaction_history::{ExtendedTx, TxAction}; - let expected_transactions = [ - ExtendedTx { - id: 9, - action: TxAction::Transfer { - from: Addr::unchecked("bob".to_string()), - sender: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("mango".to_string()), - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::new(2500), - }, - memo: Some("my transfer message #3".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - ExtendedTx { - id: 8, - action: TxAction::Transfer { - from: Addr::unchecked("bob".to_string()), - sender: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("banana".to_string()), - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::new(500), - }, - memo: Some("my transfer message #2".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - ExtendedTx { - id: 7, - action: TxAction::Transfer { - from: Addr::unchecked("bob".to_string()), - sender: Addr::unchecked("bob".to_string()), - recipient: Addr::unchecked("alice".to_string()), - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::new(1000), - }, - memo: Some("my transfer message #1".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - ExtendedTx { - id: 6, - action: TxAction::Deposit {}, - coins: Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000), - }, - memo: None, - block_time: 1571797419, - block_height: 12345, - }, - ExtendedTx { - id: 5, - action: TxAction::Mint { - minter: Addr::unchecked("admin".to_string()), - recipient: Addr::unchecked("bob".to_string()), - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::new(100), - }, - memo: Some("my mint message".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - ExtendedTx { - id: 4, - action: TxAction::Redeem {}, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::new(1000), - }, - memo: None, - block_time: 1571797419, - block_height: 12345, - }, - ExtendedTx { - id: 3, - action: TxAction::Burn { - burner: Addr::unchecked("bob".to_string()), - owner: Addr::unchecked("bob".to_string()), - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::new(1), - }, - memo: Some("my burn message".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - ExtendedTx { - id: 1, - action: TxAction::Mint { - minter: Addr::unchecked("admin".to_string()), - recipient: Addr::unchecked("bob".to_string()), - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::new(5000), - }, - - memo: Some("Initial Balance".to_string()), - block_time: 1571797419, - block_height: 12345, - }, - ]; - - assert_eq!(transactions, expected_transactions); - } } diff --git a/src/msg.rs b/src/msg.rs index cc583dd7..d9affc65 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -4,7 +4,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::batch; -use crate::batch::HasDecoy; use crate::transaction_history::{ExtendedTx, Tx}; use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128}; use secret_toolkit::permit::Permit; @@ -93,13 +92,9 @@ pub enum ExecuteMsg { Redeem { amount: Uint128, denom: Option, - decoys: Option>, - entropy: Option, padding: Option, }, Deposit { - decoys: Option>, - entropy: Option, padding: Option, }, @@ -108,8 +103,6 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, - decoys: Option>, - entropy: Option, padding: Option, }, Send { @@ -118,25 +111,19 @@ pub enum ExecuteMsg { amount: Uint128, msg: Option, memo: Option, - decoys: Option>, - entropy: Option, padding: Option, }, BatchTransfer { actions: Vec, - entropy: Option, padding: Option, }, BatchSend { actions: Vec, - entropy: Option, padding: Option, }, Burn { amount: Uint128, memo: Option, - decoys: Option>, - entropy: Option, padding: Option, }, RegisterReceive { @@ -170,8 +157,6 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, - decoys: Option>, - entropy: Option, padding: Option, }, SendFrom { @@ -181,31 +166,24 @@ pub enum ExecuteMsg { amount: Uint128, msg: Option, memo: Option, - decoys: Option>, - entropy: Option, padding: Option, }, BatchTransferFrom { actions: Vec, - entropy: Option, padding: Option, }, BatchSendFrom { actions: Vec, - entropy: Option, padding: Option, }, BurnFrom { owner: String, amount: Uint128, memo: Option, - decoys: Option>, - entropy: Option, padding: Option, }, BatchBurnFrom { actions: Vec, - entropy: Option, padding: Option, }, @@ -214,13 +192,10 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, - decoys: Option>, - entropy: Option, padding: Option, }, BatchMint { actions: Vec, - entropy: Option, padding: Option, }, AddMinters { @@ -257,78 +232,6 @@ pub enum ExecuteMsg { }, } -pub trait Decoyable { - fn get_minimal_decoys_size(&self) -> usize; - fn get_entropy(self) -> Option; -} - -impl Decoyable for ExecuteMsg { - fn get_minimal_decoys_size(&self) -> usize { - match self { - ExecuteMsg::Deposit { decoys, .. } - | ExecuteMsg::Redeem { decoys, .. } - | ExecuteMsg::Transfer { decoys, .. } - | ExecuteMsg::Send { decoys, .. } - | ExecuteMsg::Burn { decoys, .. } - | ExecuteMsg::Mint { decoys, .. } - | ExecuteMsg::TransferFrom { decoys, .. } - | ExecuteMsg::SendFrom { decoys, .. } - | ExecuteMsg::BurnFrom { decoys, .. } => { - if let Some(user_decoys) = decoys { - return user_decoys.len(); - } - - 0 - } - ExecuteMsg::BatchSendFrom { actions, .. } => get_min_decoys_count(actions), - ExecuteMsg::BatchTransferFrom { actions, .. } => get_min_decoys_count(actions), - ExecuteMsg::BatchTransfer { actions, .. } => get_min_decoys_count(actions), - ExecuteMsg::BatchSend { actions, .. } => get_min_decoys_count(actions), - ExecuteMsg::BatchBurnFrom { actions, .. } => get_min_decoys_count(actions), - ExecuteMsg::BatchMint { actions, .. } => get_min_decoys_count(actions), - _ => 0, - } - } - - fn get_entropy(self) -> Option { - match self { - ExecuteMsg::Deposit { entropy, .. } - | ExecuteMsg::Redeem { entropy, .. } - | ExecuteMsg::Transfer { entropy, .. } - | ExecuteMsg::Send { entropy, .. } - | ExecuteMsg::Burn { entropy, .. } - | ExecuteMsg::Mint { entropy, .. } - | ExecuteMsg::TransferFrom { entropy, .. } - | ExecuteMsg::SendFrom { entropy, .. } - | ExecuteMsg::BurnFrom { entropy, .. } - | ExecuteMsg::BatchTransferFrom { entropy, .. } - | ExecuteMsg::BatchSendFrom { entropy, .. } - | ExecuteMsg::BatchTransfer { entropy, .. } - | ExecuteMsg::BatchSend { entropy, .. } - | ExecuteMsg::BatchBurnFrom { entropy, .. } - | ExecuteMsg::BatchMint { entropy, .. } => entropy, - _ => None, - } - } -} - -fn get_min_decoys_count(actions: &[T]) -> usize { - let mut min_decoys_count = usize::MAX; - for action in actions { - if let Some(user_decoys) = &action.decoys() { - if user_decoys.len() < min_decoys_count { - min_decoys_count = user_decoys.len(); - } - } - } - - if min_decoys_count == usize::MAX { - 0 - } else { - min_decoys_count - } -} - #[derive(Serialize, Deserialize, JsonSchema, Debug)] #[serde(rename_all = "snake_case")] pub enum ExecuteAnswer { @@ -467,14 +370,12 @@ pub enum QueryMsg { key: String, page: Option, page_size: u32, - should_filter_decoys: bool, }, TransactionHistory { address: String, key: String, page: Option, page_size: u32, - should_filter_decoys: bool, }, Minters {}, WithPermit { @@ -544,12 +445,10 @@ pub enum QueryWithPermit { TransferHistory { page: Option, page_size: u32, - should_filter_decoys: bool, }, TransactionHistory { page: Option, page_size: u32, - should_filter_decoys: bool, }, } diff --git a/src/state.rs b/src/state.rs index ece815a1..d95089dd 100644 --- a/src/state.rs +++ b/src/state.rs @@ -139,75 +139,25 @@ impl BalancesStore { amount_to_be_updated: u128, should_add: bool, operation_name: &str, - decoys: &Option>, - account_random_pos: &Option, ) -> StdResult<()> { - match decoys { - None => { - let mut balance = Self::load(store, account); - balance = match should_add { - true => { - safe_add(&mut balance, amount_to_be_updated); - balance - } - false => { - if let Some(balance) = balance.checked_sub(amount_to_be_updated) { - balance - } else { - return Err(StdError::generic_err(format!( - "insufficient funds to {operation_name}: balance={balance}, required={amount_to_be_updated}", - ))); - } - } - }; - - Self::save(store, account, balance) + let mut balance = Self::load(store, account); + balance = match should_add { + true => { + safe_add(&mut balance, amount_to_be_updated); + balance } - Some(decoys_vec) => { - // It should always be set when decoys_vec is set - let account_pos = account_random_pos.unwrap(); - - let mut accounts_to_be_written: Vec<&Addr> = vec![]; - - let (first_part, second_part) = decoys_vec.split_at(account_pos); - accounts_to_be_written.extend(first_part); - accounts_to_be_written.push(account); - accounts_to_be_written.extend(second_part); - - // In a case where the account is also a decoy somehow - let mut was_account_updated = false; - - for acc in accounts_to_be_written.iter() { - // Always load account balance to obfuscate the real account - // Please note that decoys are not always present in the DB. In this case it is ok beacuse load will return 0. - let mut acc_balance = Self::load(store, acc); - let mut new_balance = acc_balance; - - if *acc == account && !was_account_updated { - was_account_updated = true; - new_balance = match should_add { - true => { - safe_add(&mut acc_balance, amount_to_be_updated); - acc_balance - } - false => { - if let Some(balance) = acc_balance.checked_sub(amount_to_be_updated) - { - balance - } else { - return Err(StdError::generic_err(format!( - "insufficient funds to {operation_name}: balance={acc_balance}, required={amount_to_be_updated}", - ))); - } - } - }; - } - Self::save(store, acc, new_balance)?; + false => { + if let Some(balance) = balance.checked_sub(amount_to_be_updated) { + balance + } else { + return Err(StdError::generic_err(format!( + "insufficient funds to {operation_name}: balance={balance}, required={amount_to_be_updated}", + ))); } - - Ok(()) } - } + }; + + Self::save(store, account, balance) } } diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 0b3c3270..40540653 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -47,9 +47,6 @@ pub enum TxAction { }, Deposit {}, Redeem {}, - Decoy { - address: Addr, - }, } // Note that id is a globally incrementing counter. @@ -138,7 +135,6 @@ impl StoredLegacyTransfer { for_address: Addr, page: u32, page_size: u32, - should_filter_decoys: bool, ) -> StdResult<(Vec, u64)> { let current_addr_store = TRANSFERS.add_suffix(for_address.as_bytes()); let len = current_addr_store.get_len(storage)? as u64; @@ -151,20 +147,9 @@ impl StoredLegacyTransfer { .take(page_size as _); // The `and_then` here flattens the `StdResult>` to an `StdResult` - let transfers: StdResult> = if should_filter_decoys { - transfer_iter - .filter(|transfer| match transfer { - Err(_) => true, - Ok(t) => t.block_height != 0, - }) - .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) - .collect() - } else { - transfer_iter - .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) - .collect() - }; - + let transfers: StdResult> = transfer_iter + .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) + .collect(); transfers.map(|txs| (txs, len)) } } @@ -177,7 +162,6 @@ enum TxCode { Burn = 2, Deposit = 3, Redeem = 4, - Decoy = 255, } impl TxCode { @@ -193,9 +177,9 @@ impl TxCode { 2 => Ok(Burn), 3 => Ok(Deposit), 4 => Ok(Redeem), - 255 => Ok(Decoy), other => Err(StdError::generic_err(format!( - "Unexpected Tx code in transaction history: {other} Storage is corrupted.", + "Unexpected Tx code in transaction history: {} Storage is corrupted.", + other ))), } } @@ -251,14 +235,6 @@ impl StoredTxAction { address3: None, } } - fn decoy(recipient: &Addr) -> Self { - Self { - tx_type: TxCode::Decoy.to_u8(), - address1: Some(recipient.clone()), - address2: None, - address3: None, - } - } fn into_tx_action(self) -> StdResult { let transfer_addr_err = || { @@ -272,9 +248,6 @@ impl StoredTxAction { let burn_addr_err = || { StdError::generic_err("Missing address in stored Burn transaction. Storage is corrupt") }; - let decoy_addr_err = || { - StdError::generic_err("Missing address in stored decoy transaction. Storage is corrupt") - }; // In all of these, we ignore fields that we don't expect to find populated let action = match TxCode::from_u8(self.tx_type)? { @@ -300,10 +273,6 @@ impl StoredTxAction { } TxCode::Deposit => TxAction::Deposit {}, TxCode::Redeem => TxAction::Redeem {}, - TxCode::Decoy => { - let address = self.address1.ok_or_else(decoy_addr_err)?; - TxAction::Decoy { address } - } }; Ok(action) @@ -378,7 +347,6 @@ impl StoredExtendedTx { for_address: Addr, page: u32, page_size: u32, - should_filter_decoys: bool, ) -> StdResult<(Vec, u64)> { let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes()); let len = current_addr_store.get_len(storage)? as u64; @@ -392,20 +360,9 @@ impl StoredExtendedTx { .take(page_size as _); // The `and_then` here flattens the `StdResult>` to an `StdResult` - let txs: StdResult> = if should_filter_decoys { - tx_iter - .filter(|tx| match tx { - Err(_) => true, - Ok(t) => t.action.tx_type != TxCode::Decoy.to_u8(), - }) - .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) - .collect() - } else { - tx_iter - .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) - .collect() - }; - + let txs: StdResult> = tx_iter + .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) + .collect(); txs.map(|txs| (txs, len)) } } @@ -418,85 +375,6 @@ fn increment_tx_count(store: &mut dyn Storage) -> StdResult { Ok(id) } -fn store_tx_with_decoys( - store: &mut dyn Storage, - tx: &StoredExtendedTx, - for_address: &Addr, - block: &cosmwasm_std::BlockInfo, - decoys: &Option>, - account_random_pos: &Option, -) -> StdResult<()> { - let mut index_changer: Option = None; - match decoys { - None => StoredExtendedTx::append_tx(store, tx, for_address)?, - Some(user_decoys) => { - // It should always be set when decoys_vec is set - let account_pos = account_random_pos.unwrap(); - - for i in 0..user_decoys.len() + 1 { - if i == account_pos { - StoredExtendedTx::append_tx(store, tx, for_address)?; - index_changer = Some(1); - continue; - } - - let index = i - index_changer.unwrap_or_default(); - let decoy_action = StoredTxAction::decoy(&user_decoys[index]); - let decoy_tx = StoredExtendedTx::new( - tx.id, - decoy_action, - tx.coins.clone().into(), - tx.memo.clone(), - block, - ); - StoredExtendedTx::append_tx(store, &decoy_tx, &user_decoys[index])?; - } - } - } - - Ok(()) -} - -fn store_transfer_tx_with_decoys( - store: &mut dyn Storage, - transfer: StoredLegacyTransfer, - receiver: &Addr, - decoys: &Option>, - account_random_pos: &Option, -) -> StdResult<()> { - let mut index_changer: Option = None; - match decoys { - None => StoredLegacyTransfer::append_transfer(store, &transfer, receiver)?, - Some(user_decoys) => { - // It should always be set when decoys_vec is set - let account_pos = account_random_pos.unwrap(); - - for i in 0..user_decoys.len() + 1 { - if i == account_pos { - StoredLegacyTransfer::append_transfer(store, &transfer, receiver)?; - index_changer = Some(1); - continue; - } - - let index = i - index_changer.unwrap_or_default(); - let decoy_transfer = StoredLegacyTransfer { - id: transfer.id, - from: transfer.from.clone(), - sender: transfer.sender.clone(), - receiver: user_decoys[index].clone(), - coins: transfer.coins.clone(), - memo: transfer.memo.clone(), - block_time: transfer.block_time, - block_height: 0, // To identify the decoy - }; - StoredLegacyTransfer::append_transfer(store, &decoy_transfer, &user_decoys[index])?; - } - } - } - - Ok(()) -} - #[allow(clippy::too_many_arguments)] // We just need them pub fn store_transfer( store: &mut dyn Storage, @@ -507,8 +385,6 @@ pub fn store_transfer( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, - decoys: &Option>, - account_random_pos: &Option, ) -> StdResult<()> { let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; @@ -536,16 +412,14 @@ pub fn store_transfer( StoredExtendedTx::append_tx(store, &tx, sender)?; StoredLegacyTransfer::append_transfer(store, &transfer, sender)?; } - // Always write to the recipient's history // cosmwasm_std::debug_print("saving transaction history for receiver"); - store_tx_with_decoys(store, &tx, receiver, block, decoys, account_random_pos)?; - store_transfer_tx_with_decoys(store, transfer, receiver, decoys, account_random_pos)?; + StoredExtendedTx::append_tx(store, &tx, receiver)?; + StoredLegacyTransfer::append_transfer(store, &transfer, receiver)?; Ok(()) } -#[allow(clippy::too_many_arguments)] // We just need them pub fn store_mint( store: &mut dyn Storage, minter: Addr, @@ -554,8 +428,6 @@ pub fn store_mint( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, - decoys: &Option>, - account_random_pos: &Option, ) -> StdResult<()> { let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; @@ -563,7 +435,7 @@ pub fn store_mint( let tx = StoredExtendedTx::new(id, action, coins, memo, block); if minter != recipient { - store_tx_with_decoys(store, &tx, &recipient, block, decoys, account_random_pos)?; + StoredExtendedTx::append_tx(store, &tx, &recipient)?; } StoredExtendedTx::append_tx(store, &tx, &minter)?; @@ -580,8 +452,6 @@ pub fn store_burn( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, - decoys: &Option>, - account_random_pos: &Option, ) -> StdResult<()> { let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; @@ -589,10 +459,11 @@ pub fn store_burn( let tx = StoredExtendedTx::new(id, action, coins, memo, block); if burner != owner { - store_tx_with_decoys(store, &tx, &owner, block, decoys, account_random_pos)?; + StoredExtendedTx::append_tx(store, &tx, &owner)?; } StoredExtendedTx::append_tx(store, &tx, &burner)?; + Ok(()) } @@ -602,15 +473,15 @@ pub fn store_deposit( amount: Uint128, denom: String, block: &cosmwasm_std::BlockInfo, - decoys: &Option>, - account_random_pos: &Option, ) -> StdResult<()> { let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; let action = StoredTxAction::deposit(); let tx = StoredExtendedTx::new(id, action, coins, None, block); - store_tx_with_decoys(store, &tx, recipient, block, decoys, account_random_pos) + StoredExtendedTx::append_tx(store, &tx, recipient)?; + + Ok(()) } pub fn store_redeem( @@ -619,13 +490,13 @@ pub fn store_redeem( amount: Uint128, denom: String, block: &cosmwasm_std::BlockInfo, - decoys: &Option>, - account_random_pos: &Option, ) -> StdResult<()> { let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; let action = StoredTxAction::redeem(); let tx = StoredExtendedTx::new(id, action, coins, None, block); - store_tx_with_decoys(store, &tx, redeemer, block, decoys, account_random_pos) -} + StoredExtendedTx::append_tx(store, &tx, redeemer)?; + + Ok(()) +} \ No newline at end of file From 822a306ed782067e1b0a113fcaf94f82ba0b1041 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 14 May 2024 20:21:28 +1200 Subject: [PATCH 03/87] update authors --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e6435002..6b4d585d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "snip20-reference-impl" version = "1.0.0" -authors = ["Itzik "] +authors = ["@reuvenpo","@toml01","@assafmo","@liorbond","Itzik ","@darwinzer0"] edition = "2021" exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. From ad97a47a53a882f545df9c8fd950dc638efef534 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 14 May 2024 20:25:56 +1200 Subject: [PATCH 04/87] update cosmwasm-std, cosmwasm-storage, and secret toolkit versions --- Cargo.lock | 212 +++++++++++++++++++++++++----------------------- Cargo.toml | 14 +--- src/contract.rs | 3 +- 3 files changed, 118 insertions(+), 111 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e3ff6b2..19581bbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "cc" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" + [[package]] name = "cfg-if" version = "1.0.0" @@ -89,22 +95,11 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -[[package]] -name = "cosmwasm-crypto" -version = "1.1.9" -source = "git+https://github.com/scrtlabs/cosmwasm/?tag=v1.1.9-secret#e40a15f04dae80680dbe22aef760e5eaab6b0a19" -dependencies = [ - "digest 0.10.6", - "ed25519-zebra", - "k256", - "rand_core 0.6.4", - "thiserror", -] - [[package]] name = "cosmwasm-derive" -version = "1.1.9" -source = "git+https://github.com/scrtlabs/cosmwasm/?tag=v1.1.9-secret#e40a15f04dae80680dbe22aef760e5eaab6b0a19" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" dependencies = [ "syn 1.0.109", ] @@ -133,33 +128,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "cosmwasm-std" -version = "1.1.9" -source = "git+https://github.com/scrtlabs/cosmwasm/?tag=v1.1.9-secret#e40a15f04dae80680dbe22aef760e5eaab6b0a19" -dependencies = [ - "base64 0.13.1", - "cosmwasm-crypto", - "cosmwasm-derive", - "derivative", - "forward_ref", - "hex", - "schemars", - "serde", - "serde-json-wasm", - "thiserror", - "uint", -] - -[[package]] -name = "cosmwasm-storage" -version = "1.1.9" -source = "git+https://github.com/scrtlabs/cosmwasm/?tag=v1.1.9-secret#e40a15f04dae80680dbe22aef760e5eaab6b0a19" -dependencies = [ - "cosmwasm-std", - "serde", -] - [[package]] name = "cpufeatures" version = "0.2.5" @@ -430,18 +398,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -482,13 +450,13 @@ dependencies = [ [[package]] name = "remain" -version = "0.2.7" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4b7d9b4676922ecbbad6d317e0f847762c4b28b935a2db3b44bd4f36c1aa7f" +checksum = "46aef80f842736de545ada6ec65b81ee91504efd6853f4b96de7414c42ae7443" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.63", ] [[package]] @@ -555,16 +523,75 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "secret-cosmwasm-crypto" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8535d61c88d0a6c222df2cebb69859d8e9ba419a299a1bc84c904b0d9c00c7b2" +dependencies = [ + "digest 0.10.6", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "secret-cosmwasm-std" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4393b01aa6587007161a6bb193859deaa8165ab06c8a35f253d329ff99e4d" +dependencies = [ + "base64 0.13.1", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "secret-cosmwasm-crypto", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "secret-cosmwasm-storage" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb43da2cb72a53b16ea1555bca794fb828b48ab24ebeb45f8e26f1881c45a783" +dependencies = [ + "secret-cosmwasm-std", + "serde", +] + [[package]] name = "secret-toolkit" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338c972c0a98de51ccbb859312eb7672bc64b9050b086f058748ba26a509edbb" dependencies = [ "secret-toolkit-crypto", "secret-toolkit-permit", "secret-toolkit-serialization", - "secret-toolkit-snip20", - "secret-toolkit-snip721", "secret-toolkit-storage", "secret-toolkit-utils", "secret-toolkit-viewing-key", @@ -572,93 +599,78 @@ dependencies = [ [[package]] name = "secret-toolkit-crypto" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003d7d5772c67f2240b7f298f96eb73a8a501916fe18c1d730ebfd591bf7e519" dependencies = [ - "cosmwasm-std", "rand_chacha", "rand_core 0.6.4", + "secp256k1", + "secret-cosmwasm-std", "sha2 0.10.6", ] [[package]] name = "secret-toolkit-permit" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4330571400b5959450fa37040609e6804a147d83f606783506bc2275f1527712" dependencies = [ "bech32", - "cosmwasm-std", "remain", "ripemd", "schemars", + "secret-cosmwasm-std", "secret-toolkit-crypto", "serde", ] [[package]] name = "secret-toolkit-serialization" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890adaeaa710f9f7068a807eb1553edc8c30ce9907290895c9097dd642fc613b" dependencies = [ "bincode2", - "cosmwasm-std", "schemars", - "serde", -] - -[[package]] -name = "secret-toolkit-snip20" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" -dependencies = [ - "cosmwasm-std", - "schemars", - "secret-toolkit-utils", - "serde", -] - -[[package]] -name = "secret-toolkit-snip721" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" -dependencies = [ - "cosmwasm-std", - "schemars", - "secret-toolkit-utils", + "secret-cosmwasm-std", "serde", ] [[package]] name = "secret-toolkit-storage" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e8c5418af3e7ae1d1331c383b32d56c74a340dbc3b972d53555a768698f2a3" dependencies = [ - "cosmwasm-std", - "cosmwasm-storage", + "secret-cosmwasm-std", + "secret-cosmwasm-storage", "secret-toolkit-serialization", "serde", ] [[package]] name = "secret-toolkit-utils" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f1cba2e70fd701e3dfc6072807c02eeeb9776bee49e346a9c7745d84ff40c8" dependencies = [ - "cosmwasm-std", - "cosmwasm-storage", "schemars", + "secret-cosmwasm-std", + "secret-cosmwasm-storage", "serde", ] [[package]] name = "secret-toolkit-viewing-key" -version = "0.8.0" -source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d89a0b69fa9b12735a612fa30e6e7e48130943982f1783b7ddd5c46ed09e921" dependencies = [ "base64 0.21.0", - "cosmwasm-std", - "cosmwasm-storage", "schemars", + "secret-cosmwasm-std", + "secret-cosmwasm-storage", "secret-toolkit-crypto", "secret-toolkit-utils", "serde", @@ -691,7 +703,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.4", + "syn 2.0.63", ] [[package]] @@ -756,10 +768,10 @@ version = "1.0.0" dependencies = [ "base64 0.21.0", "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", "rand", "schemars", + "secret-cosmwasm-std", + "secret-cosmwasm-storage", "secret-toolkit", "secret-toolkit-crypto", "serde", @@ -800,9 +812,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.4" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c622ae390c9302e214c31013517c2061ecb2699935882c60a9b37f82f8625ae" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 6b4d585d..6a53632c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,17 +32,11 @@ backtraces = ["cosmwasm-std/backtraces"] # debug-print = ["cosmwasm-std/debug-print"] [dependencies] -cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm/", default-features = false, tag = "v1.1.9-secret" } -cosmwasm-storage = { git = "https://github.com/scrtlabs/cosmwasm/", tag = "v1.1.9-secret" } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } +cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } rand = { version = "0.8.5", default-features = false } -secret-toolkit = { git = "https://github.com/scrtlabs/secret-toolkit", features = [ - "permit", - "viewing-key", -], rev = "9b74bdac71c2fedcc12246f18cdfdd94b8991282" } -secret-toolkit-crypto = { git = "https://github.com/scrtlabs/secret-toolkit", features = [ - "rand", - "hash", -], rev = "9b74bdac71c2fedcc12246f18cdfdd94b8991282" } +secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] } +secret-toolkit-crypto = { version = "0.10.0", features = ["rand", "hash"] } schemars = "0.8.12" serde = { version = "1.0.158", default-features = false, features = ["derive"] } diff --git a/src/contract.rs b/src/contract.rs index c6dcf368..427d2318 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -2674,8 +2674,9 @@ mod tests { height: 12_345, time: Timestamp::from_seconds(1_571_797_420), chain_id: "cosmos-testnet-14002".to_string(), + random: None, }, - transaction: Some(TransactionInfo { index: 3 }), + transaction: Some(TransactionInfo { index: 3, hash: "1010".to_string()}), contract: ContractInfo { address: Addr::unchecked(MOCK_CONTRACT_ADDR.to_string()), code_hash: "".to_string(), From 32bd10c462d96c9ca6c7837b3c99d898d024bd71 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 15 May 2024 21:42:32 +1200 Subject: [PATCH 05/87] fix tx history store as Addr bug --- src/contract.rs | 16 +++++++- src/transaction_history.rs | 75 +++++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 427d2318..c68ac44d 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -86,6 +86,7 @@ pub fn instantiate( store_mint( deps.storage, + deps.api, admin.clone(), balance_address, balance.amount, @@ -590,6 +591,7 @@ pub fn query_transfers( let (txs, total) = StoredLegacyTransfer::get_transfers( deps.storage, + deps.api, account, page, page_size, @@ -615,7 +617,13 @@ pub fn query_transactions( let account = Addr::unchecked(account); let (txs, total) = - StoredExtendedTx::get_txs(deps.storage, account, page, page_size)?; + StoredExtendedTx::get_txs( + deps.storage, + deps.api, + account, + page, + page_size + )?; let result = QueryAnswer::TransactionHistory { txs, @@ -733,6 +741,7 @@ fn try_mint_impl( store_mint( deps.storage, + deps.api, minter, recipient, amount, @@ -1117,6 +1126,7 @@ fn try_transfer_impl( let symbol = CONFIG.load(deps.storage)?.symbol; store_transfer( deps.storage, + deps.api, sender, sender, recipient, @@ -1367,6 +1377,7 @@ fn try_transfer_from_impl( let symbol = CONFIG.load(deps.storage)?.symbol; store_transfer( deps.storage, + deps.api, owner, spender, recipient, @@ -1577,6 +1588,7 @@ fn try_burn_from( store_burn( deps.storage, + deps.api, owner, info.sender, amount, @@ -1628,6 +1640,7 @@ fn try_batch_burn_from( store_burn( deps.storage, + deps.api, owner, spender.clone(), action.amount, @@ -1832,6 +1845,7 @@ fn try_burn( store_burn( deps.storage, + deps.api, info.sender.clone(), info.sender, amount, diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 40540653..708e737e 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, Coin, StdError, StdResult, Storage, Uint128}; +use cosmwasm_std::{Addr, Api, CanonicalAddr, Coin, StdError, StdResult, Storage, Uint128}; use secret_toolkit::storage::AppendStore; @@ -96,9 +96,9 @@ impl From for Coin { #[serde(rename_all = "snake_case")] pub struct StoredLegacyTransfer { id: u64, - from: Addr, - sender: Addr, - receiver: Addr, + from: CanonicalAddr, + sender: CanonicalAddr, + receiver: CanonicalAddr, coins: StoredCoin, memo: Option, block_time: u64, @@ -107,12 +107,12 @@ pub struct StoredLegacyTransfer { static TRANSFERS: AppendStore = AppendStore::new(PREFIX_TRANSFERS); impl StoredLegacyTransfer { - pub fn into_humanized(self) -> StdResult { + pub fn into_humanized(self, api: &dyn Api) -> StdResult { let tx = Tx { id: self.id, - from: self.from, - sender: self.sender, - receiver: self.receiver, + from: api.addr_humanize(&self.from)?, + sender: api.addr_humanize(&self.sender)?, + receiver: api.addr_humanize(&self.receiver)?, coins: self.coins.into(), memo: self.memo, block_time: Some(self.block_time), @@ -132,6 +132,7 @@ impl StoredLegacyTransfer { pub fn get_transfers( storage: &dyn Storage, + api: &dyn Api, for_address: Addr, page: u32, page_size: u32, @@ -148,7 +149,7 @@ impl StoredLegacyTransfer { // The `and_then` here flattens the `StdResult>` to an `StdResult` let transfers: StdResult> = transfer_iter - .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) + .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) .collect(); transfers.map(|txs| (txs, len)) } @@ -189,13 +190,13 @@ impl TxCode { #[serde(rename_all = "snake_case")] struct StoredTxAction { tx_type: u8, - address1: Option, - address2: Option, - address3: Option, + address1: Option, + address2: Option, + address3: Option, } impl StoredTxAction { - fn transfer(from: Addr, sender: Addr, recipient: Addr) -> Self { + fn transfer(from: CanonicalAddr, sender: CanonicalAddr, recipient: CanonicalAddr) -> Self { Self { tx_type: TxCode::Transfer.to_u8(), address1: Some(from), @@ -203,7 +204,7 @@ impl StoredTxAction { address3: Some(recipient), } } - fn mint(minter: Addr, recipient: Addr) -> Self { + fn mint(minter: CanonicalAddr, recipient: CanonicalAddr) -> Self { Self { tx_type: TxCode::Mint.to_u8(), address1: Some(minter), @@ -211,7 +212,7 @@ impl StoredTxAction { address3: None, } } - fn burn(owner: Addr, burner: Addr) -> Self { + fn burn(owner: CanonicalAddr, burner: CanonicalAddr) -> Self { Self { tx_type: TxCode::Burn.to_u8(), address1: Some(burner), @@ -236,7 +237,7 @@ impl StoredTxAction { } } - fn into_tx_action(self) -> StdResult { + fn into_tx_action(self, api: &dyn Api) -> StdResult { let transfer_addr_err = || { StdError::generic_err( "Missing address in stored Transfer transaction. Storage is corrupt", @@ -256,20 +257,26 @@ impl StoredTxAction { let sender = self.address2.ok_or_else(transfer_addr_err)?; let recipient = self.address3.ok_or_else(transfer_addr_err)?; TxAction::Transfer { - from, - sender, - recipient, + from: api.addr_humanize(&from)?, + sender: api.addr_humanize(&sender)?, + recipient: api.addr_humanize(&recipient)?, } } TxCode::Mint => { let minter = self.address1.ok_or_else(mint_addr_err)?; let recipient = self.address2.ok_or_else(mint_addr_err)?; - TxAction::Mint { minter, recipient } + TxAction::Mint { + minter: api.addr_humanize(&minter)?, + recipient: api.addr_humanize(&recipient)? + } } TxCode::Burn => { let burner = self.address1.ok_or_else(burn_addr_err)?; let owner = self.address2.ok_or_else(burn_addr_err)?; - TxAction::Burn { burner, owner } + TxAction::Burn { + burner: api.addr_humanize(&burner)?, + owner: api.addr_humanize(&owner)? + } } TxCode::Deposit => TxAction::Deposit {}, TxCode::Redeem => TxAction::Redeem {}, @@ -310,10 +317,10 @@ impl StoredExtendedTx { } } - fn into_humanized(self) -> StdResult { + fn into_humanized(self, api: &dyn Api) -> StdResult { Ok(ExtendedTx { id: self.id, - action: self.action.into_tx_action()?, + action: self.action.into_tx_action(api)?, coins: self.coins.into(), memo: self.memo, block_time: self.block_time, @@ -344,6 +351,7 @@ impl StoredExtendedTx { pub fn get_txs( storage: &dyn Storage, + api: &dyn Api, for_address: Addr, page: u32, page_size: u32, @@ -361,7 +369,7 @@ impl StoredExtendedTx { // The `and_then` here flattens the `StdResult>` to an `StdResult` let txs: StdResult> = tx_iter - .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x)) + .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) .collect(); txs.map(|txs| (txs, len)) } @@ -378,6 +386,7 @@ fn increment_tx_count(store: &mut dyn Storage) -> StdResult { #[allow(clippy::too_many_arguments)] // We just need them pub fn store_transfer( store: &mut dyn Storage, + api: &dyn Api, owner: &Addr, sender: &Addr, receiver: &Addr, @@ -390,9 +399,9 @@ pub fn store_transfer( let coins = Coin { denom, amount }; let transfer = StoredLegacyTransfer { id, - from: owner.clone(), - sender: sender.clone(), - receiver: receiver.clone(), + from: api.addr_canonicalize(owner.as_str())?, + sender: api.addr_canonicalize(sender.as_str())?, + receiver: api.addr_canonicalize(receiver.as_str())?, coins: coins.into(), memo, block_time: block.time.seconds(), @@ -422,6 +431,7 @@ pub fn store_transfer( pub fn store_mint( store: &mut dyn Storage, + api: &dyn Api, minter: Addr, recipient: Addr, amount: Uint128, @@ -431,7 +441,10 @@ pub fn store_mint( ) -> StdResult<()> { let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; - let action = StoredTxAction::mint(minter.clone(), recipient.clone()); + let action = StoredTxAction::mint( + api.addr_canonicalize(minter.as_str())?, + api.addr_canonicalize(recipient.as_str())? + ); let tx = StoredExtendedTx::new(id, action, coins, memo, block); if minter != recipient { @@ -446,6 +459,7 @@ pub fn store_mint( #[allow(clippy::too_many_arguments)] pub fn store_burn( store: &mut dyn Storage, + api: &dyn Api, owner: Addr, burner: Addr, amount: Uint128, @@ -455,7 +469,10 @@ pub fn store_burn( ) -> StdResult<()> { let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; - let action = StoredTxAction::burn(owner.clone(), burner.clone()); + let action = StoredTxAction::burn( + api.addr_canonicalize(owner.as_str())?, + api.addr_canonicalize(burner.as_str())? + ); let tx = StoredExtendedTx::new(id, action, coins, memo, block); if burner != owner { From 22b2ff5e0d84ee07f033b26af5ea863802de908b Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 16 May 2024 12:36:28 +1200 Subject: [PATCH 06/87] remove legacy transfer --- src/contract.rs | 211 +++---------------------------------- src/dwbs.rs | 0 src/lib.rs | 2 + src/msg.rs | 9 +- src/strings.rs | 1 + src/transaction_history.rs | 158 ++++++--------------------- 6 files changed, 53 insertions(+), 328 deletions(-) create mode 100644 src/dwbs.rs create mode 100644 src/strings.rs diff --git a/src/contract.rs b/src/contract.rs index c68ac44d..5cd09c0e 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -19,9 +19,9 @@ use crate::state::{ safe_add, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, TOTAL_SUPPLY, }; +use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredExtendedTx, - StoredLegacyTransfer, + store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredTx, }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -362,23 +362,8 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result< query_balance(deps, account) } - QueryWithPermit::TransferHistory { - page, - page_size, - } => { - if !permit.check_permission(&TokenPermissions::History) { - return Err(StdError::generic_err(format!( - "No permission to query history, got permissions {:?}", - permit.params.permissions - ))); - } - - query_transfers( - deps, - account, - page.unwrap_or(0), - page_size, - ) + QueryWithPermit::TransferHistory { .. } => { + return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)); } QueryWithPermit::TransactionHistory { page, @@ -471,17 +456,9 @@ pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult { return match msg { // Base QueryMsg::Balance { address, .. } => query_balance(deps, address), - QueryMsg::TransferHistory { - address, - page, - page_size, - .. - } => query_transfers( - deps, - address, - page.unwrap_or(0), - page_size, - ), + QueryMsg::TransferHistory { .. } => { + return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)); + }, QueryMsg::TransactionHistory { address, page, @@ -577,33 +554,6 @@ fn query_contract_status(storage: &dyn Storage) -> StdResult { }) } -pub fn query_transfers( - deps: Deps, - account: String, - page: u32, - page_size: u32, -) -> StdResult { - // Notice that if query_transfers() was called by a viewking-key call, the address of 'account' - // has already been validated. - // The address of 'account' should not be validated if query_transfers() was called by a permit - // call, for compatibility with non-Secret addresses. - let account = Addr::unchecked(account); - - let (txs, total) = StoredLegacyTransfer::get_transfers( - deps.storage, - deps.api, - account, - page, - page_size, - )?; - - let result = QueryAnswer::TransferHistory { - txs, - total: Some(total), - }; - to_binary(&result) -} - pub fn query_transactions( deps: Deps, account: String, @@ -617,7 +567,7 @@ pub fn query_transactions( let account = Addr::unchecked(account); let (txs, total) = - StoredExtendedTx::get_txs( + StoredTx::get_txs( deps.storage, deps.api, account, @@ -5024,120 +4974,6 @@ mod tests { assert_eq!(balance, Uint128::new(5000)); } - #[test] - fn test_query_transfer_history() { - let (init_result, mut deps) = init_helper(vec![InitialBalance { - address: "bob".to_string(), - amount: Uint128::new(5000), - }]); - assert!( - init_result.is_ok(), - "Init failed: {}", - init_result.err().unwrap() - ); - - let handle_msg = ExecuteMsg::SetViewingKey { - key: "key".to_string(), - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - assert!(ensure_success(handle_result.unwrap())); - - let handle_msg = ExecuteMsg::Transfer { - recipient: "alice".to_string(), - amount: Uint128::new(1000), - memo: None, - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - let handle_msg = ExecuteMsg::Transfer { - recipient: "banana".to_string(), - amount: Uint128::new(500), - memo: None, - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - let handle_msg = ExecuteMsg::Transfer { - recipient: "mango".to_string(), - amount: Uint128::new(2500), - memo: None, - padding: None, - }; - let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); - - let result = handle_result.unwrap(); - assert!(ensure_success(result)); - - let query_msg = QueryMsg::TransferHistory { - address: "bob".to_string(), - key: "key".to_string(), - page: None, - page_size: 0, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - // let a: QueryAnswer = from_binary(&query_result.unwrap()).unwrap(); - // println!("{:?}", a); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert!(transfers.is_empty()); - - let query_msg = QueryMsg::TransferHistory { - address: "bob".to_string(), - key: "key".to_string(), - page: None, - page_size: 10, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 3); - - let query_msg = QueryMsg::TransferHistory { - address: "bob".to_string(), - key: "key".to_string(), - page: None, - page_size: 2, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 2); - - let query_msg = QueryMsg::TransferHistory { - address: "bob".to_string(), - key: "key".to_string(), - page: Some(1), - page_size: 2, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 1); - } - #[test] fn test_query_transaction_history() { let (init_result, mut deps) = init_helper_with_config( @@ -5267,19 +5103,6 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - let query_msg = QueryMsg::TransferHistory { - address: "bob".to_string(), - key: "key".to_string(), - page: None, - page_size: 10, - }; - let query_result = query(deps.as_ref(), mock_env(), query_msg); - let transfers = match from_binary(&query_result.unwrap()).unwrap() { - QueryAnswer::TransferHistory { txs, .. } => txs, - _ => panic!("Unexpected"), - }; - assert_eq!(transfers.len(), 3); - let query_msg = QueryMsg::TransactionHistory { address: "bob".to_string(), key: "key".to_string(), @@ -5292,9 +5115,9 @@ mod tests { other => panic!("Unexpected: {:?}", other), }; - use crate::transaction_history::{ExtendedTx, TxAction}; + use crate::transaction_history::{Tx, TxAction}; let expected_transfers = [ - ExtendedTx { + Tx { id: 8, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -5309,7 +5132,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - ExtendedTx { + Tx { id: 7, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -5324,7 +5147,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - ExtendedTx { + Tx { id: 6, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -5339,7 +5162,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - ExtendedTx { + Tx { id: 5, action: TxAction::Deposit {}, coins: Coin { @@ -5350,7 +5173,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - ExtendedTx { + Tx { id: 4, action: TxAction::Mint { minter: Addr::unchecked("admin".to_string()), @@ -5364,7 +5187,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - ExtendedTx { + Tx { id: 3, action: TxAction::Redeem {}, coins: Coin { @@ -5375,7 +5198,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - ExtendedTx { + Tx { id: 2, action: TxAction::Burn { burner: Addr::unchecked("bob".to_string()), @@ -5389,7 +5212,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - ExtendedTx { + Tx { id: 1, action: TxAction::Mint { minter: Addr::unchecked("admin".to_string()), diff --git a/src/dwbs.rs b/src/dwbs.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/lib.rs b/src/lib.rs index 9bafd896..ecf11534 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,5 @@ pub mod msg; pub mod receiver; pub mod state; mod transaction_history; +mod dwbs; +mod strings; \ No newline at end of file diff --git a/src/msg.rs b/src/msg.rs index d9affc65..88166514 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -3,8 +3,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::batch; -use crate::transaction_history::{ExtendedTx, Tx}; +use crate::{batch, transaction_history::Tx}; use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128}; use secret_toolkit::permit::Permit; @@ -495,12 +494,8 @@ pub enum QueryAnswer { Balance { amount: Uint128, }, - TransferHistory { - txs: Vec, - total: Option, - }, TransactionHistory { - txs: Vec, + txs: Vec, total: Option, }, ViewingKeyError { diff --git a/src/strings.rs b/src/strings.rs new file mode 100644 index 00000000..554c48e0 --- /dev/null +++ b/src/strings.rs @@ -0,0 +1 @@ +pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str = "`transfer_history` query is now UNSUPPORTED. Use `transaction_history` instead."; \ No newline at end of file diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 708e737e..ef87f8e8 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -8,26 +8,6 @@ use secret_toolkit::storage::AppendStore; use crate::state::TX_COUNT; const PREFIX_TXS: &[u8] = b"transactions"; -const PREFIX_TRANSFERS: &[u8] = b"transfers"; - -// Note that id is a globally incrementing counter. -// Since it's 64 bits long, even at 50 tx/s it would take -// over 11 billion years for it to rollback. I'm pretty sure -// we'll have bigger issues by then. -#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] -pub struct Tx { - pub id: u64, - pub from: Addr, - pub sender: Addr, - pub receiver: Addr, - pub coins: Coin, - #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, - // The block time and block height are optional so that the JSON schema - // reflects that some SNIP-20 contracts may not include this info. - pub block_time: Option, - pub block_height: Option, -} #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "snake_case")] @@ -55,7 +35,7 @@ pub enum TxAction { // we'll have bigger issues by then. #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] #[serde(rename_all = "snake_case")] -pub struct ExtendedTx { +pub struct Tx { pub id: u64, pub action: TxAction, pub coins: Coin, @@ -91,70 +71,6 @@ impl From for Coin { } } -/// This type is the stored version of the legacy transfers -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub struct StoredLegacyTransfer { - id: u64, - from: CanonicalAddr, - sender: CanonicalAddr, - receiver: CanonicalAddr, - coins: StoredCoin, - memo: Option, - block_time: u64, - block_height: u64, -} -static TRANSFERS: AppendStore = AppendStore::new(PREFIX_TRANSFERS); - -impl StoredLegacyTransfer { - pub fn into_humanized(self, api: &dyn Api) -> StdResult { - let tx = Tx { - id: self.id, - from: api.addr_humanize(&self.from)?, - sender: api.addr_humanize(&self.sender)?, - receiver: api.addr_humanize(&self.receiver)?, - coins: self.coins.into(), - memo: self.memo, - block_time: Some(self.block_time), - block_height: Some(self.block_height), - }; - Ok(tx) - } - - fn append_transfer( - store: &mut dyn Storage, - tx: &StoredLegacyTransfer, - for_address: &Addr, - ) -> StdResult<()> { - let current_addr_store = TRANSFERS.add_suffix(for_address.as_bytes()); - current_addr_store.push(store, tx) - } - - pub fn get_transfers( - storage: &dyn Storage, - api: &dyn Api, - for_address: Addr, - page: u32, - page_size: u32, - ) -> StdResult<(Vec, u64)> { - let current_addr_store = TRANSFERS.add_suffix(for_address.as_bytes()); - let len = current_addr_store.get_len(storage)? as u64; - // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` - // txs from the start. - let transfer_iter = current_addr_store - .iter(storage)? - .rev() - .skip((page * page_size) as _) - .take(page_size as _); - - // The `and_then` here flattens the `StdResult>` to an `StdResult` - let transfers: StdResult> = transfer_iter - .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) - .collect(); - transfers.map(|txs| (txs, len)) - } -} - #[derive(Clone, Copy, Debug)] #[repr(u8)] enum TxCode { @@ -286,11 +202,11 @@ impl StoredTxAction { } } -static TRANSACTIONS: AppendStore = AppendStore::new(PREFIX_TXS); +static TRANSACTIONS: AppendStore = AppendStore::new(PREFIX_TXS); #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "snake_case")] -pub struct StoredExtendedTx { +pub struct StoredTx { id: u64, action: StoredTxAction, coins: StoredCoin, @@ -299,7 +215,7 @@ pub struct StoredExtendedTx { block_height: u64, } -impl StoredExtendedTx { +impl StoredTx { fn new( id: u64, action: StoredTxAction, @@ -317,8 +233,8 @@ impl StoredExtendedTx { } } - fn into_humanized(self, api: &dyn Api) -> StdResult { - Ok(ExtendedTx { + fn into_humanized(self, api: &dyn Api) -> StdResult { + Ok(Tx { id: self.id, action: self.action.into_tx_action(api)?, coins: self.coins.into(), @@ -328,21 +244,9 @@ impl StoredExtendedTx { }) } - fn from_stored_legacy_transfer(transfer: StoredLegacyTransfer) -> Self { - let action = StoredTxAction::transfer(transfer.from, transfer.sender, transfer.receiver); - Self { - id: transfer.id, - action, - coins: transfer.coins, - memo: transfer.memo, - block_time: transfer.block_time, - block_height: transfer.block_height, - } - } - fn append_tx( store: &mut dyn Storage, - tx: &StoredExtendedTx, + tx: &StoredTx, for_address: &Addr, ) -> StdResult<()> { let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes()); @@ -355,7 +259,7 @@ impl StoredExtendedTx { for_address: Addr, page: u32, page_size: u32, - ) -> StdResult<(Vec, u64)> { + ) -> StdResult<(Vec, u64)> { let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes()); let len = current_addr_store.get_len(storage)? as u64; @@ -367,8 +271,8 @@ impl StoredExtendedTx { .skip((page * page_size) as _) .take(page_size as _); - // The `and_then` here flattens the `StdResult>` to an `StdResult` - let txs: StdResult> = tx_iter + // The `and_then` here flattens the `StdResult>` to an `StdResult` + let txs: StdResult> = tx_iter .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) .collect(); txs.map(|txs| (txs, len)) @@ -397,34 +301,34 @@ pub fn store_transfer( ) -> StdResult<()> { let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; - let transfer = StoredLegacyTransfer { + let action = StoredTxAction::transfer( + api.addr_canonicalize(owner.as_str())?, + api.addr_canonicalize(sender.as_str())?, + api.addr_canonicalize(receiver.as_str())? + ); + let tx = StoredTx { id, - from: api.addr_canonicalize(owner.as_str())?, - sender: api.addr_canonicalize(sender.as_str())?, - receiver: api.addr_canonicalize(receiver.as_str())?, + action, coins: coins.into(), memo, block_time: block.time.seconds(), block_height: block.height, }; - let tx = StoredExtendedTx::from_stored_legacy_transfer(transfer.clone()); // Write to the owners history if it's different from the other two addresses + // TODO: check if we want to always write this. if owner != sender && owner != receiver { // cosmwasm_std::debug_print("saving transaction history for owner"); - StoredExtendedTx::append_tx(store, &tx, owner)?; - StoredLegacyTransfer::append_transfer(store, &transfer, owner)?; + StoredTx::append_tx(store, &tx, owner)?; } // Write to the sender's history if it's different from the receiver if sender != receiver { // cosmwasm_std::debug_print("saving transaction history for sender"); - StoredExtendedTx::append_tx(store, &tx, sender)?; - StoredLegacyTransfer::append_transfer(store, &transfer, sender)?; + StoredTx::append_tx(store, &tx, sender)?; } // Always write to the recipient's history // cosmwasm_std::debug_print("saving transaction history for receiver"); - StoredExtendedTx::append_tx(store, &tx, receiver)?; - StoredLegacyTransfer::append_transfer(store, &transfer, receiver)?; + StoredTx::append_tx(store, &tx, receiver)?; Ok(()) } @@ -445,13 +349,13 @@ pub fn store_mint( api.addr_canonicalize(minter.as_str())?, api.addr_canonicalize(recipient.as_str())? ); - let tx = StoredExtendedTx::new(id, action, coins, memo, block); + let tx = StoredTx::new(id, action, coins, memo, block); if minter != recipient { - StoredExtendedTx::append_tx(store, &tx, &recipient)?; + StoredTx::append_tx(store, &tx, &recipient)?; } - StoredExtendedTx::append_tx(store, &tx, &minter)?; + StoredTx::append_tx(store, &tx, &minter)?; Ok(()) } @@ -473,13 +377,13 @@ pub fn store_burn( api.addr_canonicalize(owner.as_str())?, api.addr_canonicalize(burner.as_str())? ); - let tx = StoredExtendedTx::new(id, action, coins, memo, block); + let tx = StoredTx::new(id, action, coins, memo, block); if burner != owner { - StoredExtendedTx::append_tx(store, &tx, &owner)?; + StoredTx::append_tx(store, &tx, &owner)?; } - StoredExtendedTx::append_tx(store, &tx, &burner)?; + StoredTx::append_tx(store, &tx, &burner)?; Ok(()) } @@ -494,9 +398,9 @@ pub fn store_deposit( let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; let action = StoredTxAction::deposit(); - let tx = StoredExtendedTx::new(id, action, coins, None, block); + let tx = StoredTx::new(id, action, coins, None, block); - StoredExtendedTx::append_tx(store, &tx, recipient)?; + StoredTx::append_tx(store, &tx, recipient)?; Ok(()) } @@ -511,9 +415,9 @@ pub fn store_redeem( let id = increment_tx_count(store)?; let coins = Coin { denom, amount }; let action = StoredTxAction::redeem(); - let tx = StoredExtendedTx::new(id, action, coins, None, block); + let tx = StoredTx::new(id, action, coins, None, block); - StoredExtendedTx::append_tx(store, &tx, redeemer)?; + StoredTx::append_tx(store, &tx, redeemer)?; Ok(()) } \ No newline at end of file From fba38959a20a5e525ef502a6b9bc19be1d3230d8 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 18 May 2024 19:53:43 +1200 Subject: [PATCH 07/87] dev: start dwb and remove denom from history storage --- src/contract.rs | 120 ++++++++++++++++++++----------------- src/dwb.rs | 31 ++++++++++ src/dwbs.rs | 0 src/lib.rs | 2 +- src/msg.rs | 20 +++++-- src/state.rs | 1 - src/transaction_history.rs | 30 ++++------ 7 files changed, 124 insertions(+), 80 deletions(-) create mode 100644 src/dwb.rs delete mode 100644 src/dwbs.rs diff --git a/src/contract.rs b/src/contract.rs index 5cd09c0e..b24b5580 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -10,9 +10,11 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::sha_256; use crate::batch; +use crate::dwb::{DelayedWriteBuffer, DWB, DWB_LEN}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, + TxWithCoins, }; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ @@ -21,7 +23,7 @@ use crate::state::{ }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredTx, + store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredTx, TxAction, }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -62,39 +64,42 @@ pub fn instantiate( let prng_seed_hashed = sha_256(&msg.prng_seed.0); PrngStore::save(deps.storage, prng_seed_hashed)?; - { - let initial_balances = msg.initial_balances.unwrap_or_default(); - for balance in initial_balances { - let amount = balance.amount.u128(); - let balance_address = deps.api.addr_validate(balance.address.as_str())?; - // Here amount is also the amount to be added because the account has no prior balance - BalancesStore::update_balance( - deps.storage, - &balance_address, - amount, - true, - "", - )?; - - if let Some(new_total_supply) = total_supply.checked_add(amount) { - total_supply = new_total_supply; - } else { - return Err(StdError::generic_err( - "The sum of all initial balances exceeds the maximum possible total supply", - )); - } + // initialize the delay write buffer + DWB.save(deps.storage, &DelayedWriteBuffer{ + empty_space_counter: DWB_LEN, + elements: vec![], + })?; + + let initial_balances = msg.initial_balances.unwrap_or_default(); + for balance in initial_balances { + let amount = balance.amount.u128(); + let balance_address = deps.api.addr_validate(balance.address.as_str())?; + // Here amount is also the amount to be added because the account has no prior balance + BalancesStore::update_balance( + deps.storage, + &balance_address, + amount, + true, + "", + )?; - store_mint( - deps.storage, - deps.api, - admin.clone(), - balance_address, - balance.amount, - msg.symbol.clone(), - Some("Initial Balance".to_string()), - &env.block, - )?; + if let Some(new_total_supply) = total_supply.checked_add(amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err( + "The sum of all initial balances exceeds the maximum possible total supply", + )); } + + store_mint( + deps.storage, + deps.api, + admin.clone(), + balance_address, + balance.amount, + Some("Initial Balance".to_string()), + &env.block, + )?; } let supported_denoms = match msg.supported_denoms { @@ -575,6 +580,25 @@ pub fn query_transactions( page_size )?; + let symbol = CONFIG.load(deps.storage)?.symbol; + let txs = txs.iter().map(|tx| { + let denom = match tx.action { + TxAction::Deposit { } => "uscrt".to_string(), + _ => symbol.clone() + }; + TxWithCoins { + id: tx.id, + action: tx.action.clone(), + coins: Coin { + denom, + amount: tx.amount, + }, + memo: tx.memo.clone(), + block_height: tx.block_height, + block_time: tx.block_time, + } + }).collect(); + let result = QueryAnswer::TransactionHistory { txs, total: Some(total), @@ -675,7 +699,6 @@ fn try_mint_impl( minter: Addr, recipient: Addr, amount: Uint128, - denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { @@ -695,7 +718,6 @@ fn try_mint_impl( minter, recipient, amount, - denom, memo, block, )?; @@ -739,7 +761,6 @@ fn try_mint( info.sender, recipient, Uint128::new(minted_amount), - constants.symbol, memo, &env.block, )?; @@ -780,7 +801,6 @@ fn try_batch_mint( info.sender.clone(), recipient, Uint128::new(actual_amount), - constants.symbol.clone(), action.memo, &env.block, )?; @@ -967,7 +987,7 @@ fn try_deposit( deps.storage, sender_address, Uint128::new(raw_amount), - "uscrt".to_string(), + //"uscrt".to_string(), &env.block, )?; @@ -1044,7 +1064,6 @@ fn try_redeem( deps.storage, sender_address, amount, - constants.symbol, &env.block, )?; @@ -1073,7 +1092,6 @@ fn try_transfer_impl( amount.u128(), )?; - let symbol = CONFIG.load(deps.storage)?.symbol; store_transfer( deps.storage, deps.api, @@ -1081,7 +1099,6 @@ fn try_transfer_impl( sender, recipient, amount, - symbol, memo, block, )?; @@ -1324,7 +1341,6 @@ fn try_transfer_from_impl( raw_amount, )?; - let symbol = CONFIG.load(deps.storage)?.symbol; store_transfer( deps.storage, deps.api, @@ -1332,7 +1348,6 @@ fn try_transfer_from_impl( spender, recipient, amount, - symbol, memo, &env.block, )?; @@ -1542,7 +1557,6 @@ fn try_burn_from( owner, info.sender, amount, - constants.symbol, memo, &env.block, )?; @@ -1594,7 +1608,6 @@ fn try_batch_burn_from( owner, spender.clone(), action.amount, - constants.symbol.clone(), action.memo, &env.block, )?; @@ -1799,7 +1812,6 @@ fn try_burn( info.sender.clone(), info.sender, amount, - constants.symbol, memo, &env.block, )?; @@ -5115,9 +5127,9 @@ mod tests { other => panic!("Unexpected: {:?}", other), }; - use crate::transaction_history::{Tx, TxAction}; + use crate::transaction_history::TxAction; let expected_transfers = [ - Tx { + TxWithCoins { id: 8, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -5132,7 +5144,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - Tx { + TxWithCoins { id: 7, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -5147,7 +5159,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - Tx { + TxWithCoins { id: 6, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -5162,7 +5174,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - Tx { + TxWithCoins { id: 5, action: TxAction::Deposit {}, coins: Coin { @@ -5173,7 +5185,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - Tx { + TxWithCoins { id: 4, action: TxAction::Mint { minter: Addr::unchecked("admin".to_string()), @@ -5187,7 +5199,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - Tx { + TxWithCoins { id: 3, action: TxAction::Redeem {}, coins: Coin { @@ -5198,7 +5210,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - Tx { + TxWithCoins { id: 2, action: TxAction::Burn { burner: Addr::unchecked("bob".to_string()), @@ -5212,7 +5224,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - Tx { + TxWithCoins { id: 1, action: TxAction::Mint { minter: Addr::unchecked("admin".to_string()), diff --git a/src/dwb.rs b/src/dwb.rs new file mode 100644 index 00000000..8a2eba44 --- /dev/null +++ b/src/dwb.rs @@ -0,0 +1,31 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use cosmwasm_std::CanonicalAddr; +use secret_toolkit::storage::Item; + +pub const DWB_LEN: u16 = 64; +pub const KEY_DWB: &[u8] = b"dwb"; +pub const KEY_NEXT_LIST_ID: &[u8] = b"dwb-list"; + +pub static DWB: Item = Item::new(KEY_DWB); +pub static NEXT_LIST_ID: Item = Item::new(KEY_NEXT_LIST_ID); + +#[derive(Serialize, Deserialize, Debug)] +pub struct DelayedWriteBuffer { + pub empty_space_counter: u16, + pub elements: Vec, +} + +impl DelayedWriteBuffer { + #[inline] + pub fn saturated(&self) -> bool { + self.empty_space_counter == 0 + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct DelayedWriteBufferElement { + pub recipient: CanonicalAddr, + pub amount: u128, + pub list_id: u64, +} diff --git a/src/dwbs.rs b/src/dwbs.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/lib.rs b/src/lib.rs index ecf11534..4b64ae10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,5 @@ pub mod msg; pub mod receiver; pub mod state; mod transaction_history; -mod dwbs; +mod dwb; mod strings; \ No newline at end of file diff --git a/src/msg.rs b/src/msg.rs index 88166514..61c33aa7 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -3,8 +3,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{batch, transaction_history::Tx}; -use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128}; +use crate::{batch, transaction_history::TxAction}; +use cosmwasm_std::{Addr, Api, Binary, Coin, StdError, StdResult, Uint128}; use secret_toolkit::permit::Permit; #[cfg_attr(test, derive(Eq, PartialEq))] @@ -53,7 +53,7 @@ pub struct InitConfig { /// Indicates whether burn functionality should be enabled /// default: False enable_burn: Option, - /// Indicated whether an admin can modify supported denoms + /// Indicates whether an admin can modify supported denoms /// default: False can_modify_denoms: Option, } @@ -495,7 +495,7 @@ pub enum QueryAnswer { amount: Uint128, }, TransactionHistory { - txs: Vec, + txs: Vec, total: Option, }, ViewingKeyError { @@ -506,6 +506,18 @@ pub enum QueryAnswer { }, } +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct TxWithCoins { + pub id: u64, + pub action: TxAction, + pub coins: Coin, + #[serde(skip_serializing_if = "Option::is_none")] + pub memo: Option, + pub block_time: u64, + pub block_height: u64, +} + #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] pub struct AllowanceGivenResult { pub spender: Addr, diff --git a/src/state.rs b/src/state.rs index d95089dd..7056bd79 100644 --- a/src/state.rs +++ b/src/state.rs @@ -15,7 +15,6 @@ pub const KEY_PRNG: &[u8] = b"prng"; pub const KEY_MINTERS: &[u8] = b"minters"; pub const KEY_TX_COUNT: &[u8] = b"tx-count"; -pub const PREFIX_CONFIG: &[u8] = b"config"; pub const PREFIX_BALANCES: &[u8] = b"balances"; pub const PREFIX_ALLOWANCES: &[u8] = b"allowances"; pub const PREFIX_ALLOWED: &[u8] = b"allowed"; diff --git a/src/transaction_history.rs b/src/transaction_history.rs index ef87f8e8..5edd2abe 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -38,7 +38,7 @@ pub enum TxAction { pub struct Tx { pub id: u64, pub action: TxAction, - pub coins: Coin, + pub amount: Uint128, #[serde(skip_serializing_if = "Option::is_none")] pub memo: Option, pub block_time: u64, @@ -209,7 +209,7 @@ static TRANSACTIONS: AppendStore = AppendStore::new(PREFIX_TXS); pub struct StoredTx { id: u64, action: StoredTxAction, - coins: StoredCoin, + amount: u128, memo: Option, block_time: u64, block_height: u64, @@ -219,14 +219,14 @@ impl StoredTx { fn new( id: u64, action: StoredTxAction, - coins: Coin, + amount: Uint128, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> Self { Self { id, action, - coins: coins.into(), + amount: amount.u128(), memo, block_time: block.time.seconds(), block_height: block.height, @@ -237,7 +237,7 @@ impl StoredTx { Ok(Tx { id: self.id, action: self.action.into_tx_action(api)?, - coins: self.coins.into(), + amount: Uint128::from(self.amount), memo: self.memo, block_time: self.block_time, block_height: self.block_height, @@ -295,12 +295,10 @@ pub fn store_transfer( sender: &Addr, receiver: &Addr, amount: Uint128, - denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let id = increment_tx_count(store)?; - let coins = Coin { denom, amount }; let action = StoredTxAction::transfer( api.addr_canonicalize(owner.as_str())?, api.addr_canonicalize(sender.as_str())?, @@ -309,7 +307,7 @@ pub fn store_transfer( let tx = StoredTx { id, action, - coins: coins.into(), + amount: amount.u128(), memo, block_time: block.time.seconds(), block_height: block.height, @@ -339,17 +337,15 @@ pub fn store_mint( minter: Addr, recipient: Addr, amount: Uint128, - denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let id = increment_tx_count(store)?; - let coins = Coin { denom, amount }; let action = StoredTxAction::mint( api.addr_canonicalize(minter.as_str())?, api.addr_canonicalize(recipient.as_str())? ); - let tx = StoredTx::new(id, action, coins, memo, block); + let tx = StoredTx::new(id, action, amount, memo, block); if minter != recipient { StoredTx::append_tx(store, &tx, &recipient)?; @@ -367,17 +363,15 @@ pub fn store_burn( owner: Addr, burner: Addr, amount: Uint128, - denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let id = increment_tx_count(store)?; - let coins = Coin { denom, amount }; let action = StoredTxAction::burn( api.addr_canonicalize(owner.as_str())?, api.addr_canonicalize(burner.as_str())? ); - let tx = StoredTx::new(id, action, coins, memo, block); + let tx = StoredTx::new(id, action, amount, memo, block); if burner != owner { StoredTx::append_tx(store, &tx, &owner)?; @@ -392,13 +386,11 @@ pub fn store_deposit( store: &mut dyn Storage, recipient: &Addr, amount: Uint128, - denom: String, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let id = increment_tx_count(store)?; - let coins = Coin { denom, amount }; let action = StoredTxAction::deposit(); - let tx = StoredTx::new(id, action, coins, None, block); + let tx = StoredTx::new(id, action, amount, None, block); StoredTx::append_tx(store, &tx, recipient)?; @@ -409,13 +401,11 @@ pub fn store_redeem( store: &mut dyn Storage, redeemer: &Addr, amount: Uint128, - denom: String, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let id = increment_tx_count(store)?; - let coins = Coin { denom, amount }; let action = StoredTxAction::redeem(); - let tx = StoredTx::new(id, action, coins, None, block); + let tx = StoredTx::new(id, action, amount, None, block); StoredTx::append_tx(store, &tx, redeemer)?; From c0a034e7411935b694a8fe49abd0d85a9cd3c3b1 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 18 May 2024 21:49:38 +1200 Subject: [PATCH 08/87] adding rng --- src/contract.rs | 32 +++++++++++++++++++--- src/lib.rs | 2 +- src/transaction_history.rs | 54 ++++++++++++++++++++++---------------- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index b24b5580..88d0bb78 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -7,7 +7,7 @@ use cosmwasm_std::{ use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; -use secret_toolkit_crypto::sha_256; +use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; use crate::dwb::{DelayedWriteBuffer, DWB, DWB_LEN}; @@ -18,8 +18,7 @@ use crate::msg::{ }; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, - CONFIG, CONTRACT_STATUS, TOTAL_SUPPLY, + safe_add, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, PRNG, TOTAL_SUPPLY }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ @@ -1079,6 +1078,7 @@ fn try_redeem( #[allow(clippy::too_many_arguments)] fn try_transfer_impl( deps: &mut DepsMut, + rng: &mut ContractPrng, sender: &Addr, recipient: &Addr, amount: Uint128, @@ -1115,10 +1115,14 @@ fn try_transfer( amount: Uint128, memo: Option, ) -> StdResult { - let recipient = deps.api.addr_validate(recipient.as_str())?; + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + + let recipient: Addr = deps.api.addr_validate(recipient.as_str())?; try_transfer_impl( &mut deps, + &mut rng, &info.sender, &recipient, amount, @@ -1135,10 +1139,14 @@ fn try_batch_transfer( info: MessageInfo, actions: Vec, ) -> StdResult { + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_transfer_impl( &mut deps, + &mut rng, &info.sender, &recipient, action.amount, @@ -1187,6 +1195,7 @@ fn try_add_receiver_api_callback( #[allow(clippy::too_many_arguments)] fn try_send_impl( deps: &mut DepsMut, + rng: &mut ContractPrng, messages: &mut Vec, sender: Addr, recipient: Addr, @@ -1198,6 +1207,7 @@ fn try_send_impl( ) -> StdResult<()> { try_transfer_impl( deps, + rng, &sender, &recipient, amount, @@ -1231,11 +1241,15 @@ fn try_send( memo: Option, msg: Option, ) -> StdResult { + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + let recipient = deps.api.addr_validate(recipient.as_str())?; let mut messages = vec![]; try_send_impl( &mut deps, + &mut rng, &mut messages, info.sender, recipient, @@ -1257,11 +1271,15 @@ fn try_batch_send( info: MessageInfo, actions: Vec, ) -> StdResult { + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + let mut messages = vec![]; for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_send_impl( &mut deps, + &mut rng, &mut messages, info.sender.clone(), recipient, @@ -1825,7 +1843,13 @@ fn perform_transfer( to: &Addr, amount: u128, ) -> StdResult<()> { + // load delayed write buffer + let dwb = DWB.load(store)?; + + // check have enough funds and withdraw `amount` from `from` account BalancesStore::update_balance(store, from, amount, false, "transfer")?; + + // TODO: BalancesStore::update_balance( store, to, diff --git a/src/lib.rs b/src/lib.rs index 4b64ae10..b070ddc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,4 +5,4 @@ pub mod receiver; pub mod state; mod transaction_history; mod dwb; -mod strings; \ No newline at end of file +mod strings; diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 5edd2abe..64db070e 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -1,9 +1,9 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, Api, CanonicalAddr, Coin, StdError, StdResult, Storage, Uint128}; +use cosmwasm_std::{Addr, Api, BlockInfo, CanonicalAddr, Coin, StdError, StdResult, Storage, Uint128}; -use secret_toolkit::storage::AppendStore; +use secret_toolkit::storage::{AppendStore, Item}; use crate::state::TX_COUNT; @@ -202,12 +202,13 @@ impl StoredTxAction { } } -static TRANSACTIONS: AppendStore = AppendStore::new(PREFIX_TXS); +// use with add_suffix tx id (u64) +// does not need to be an AppendStore because we never need to iterate over global list of txs +static TRANSACTIONS: Item = Item::new(PREFIX_TXS); #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "snake_case")] pub struct StoredTx { - id: u64, action: StoredTxAction, amount: u128, memo: Option, @@ -287,6 +288,27 @@ fn increment_tx_count(store: &mut dyn Storage) -> StdResult { Ok(id) } +fn append_new_stored_tx( + store: &mut dyn Storage, + action: &StoredTxAction, + amount: Uint128, + memo: Option, + block: &BlockInfo, +) -> StdResult { + let id = TX_COUNT.load(store).unwrap_or_default(); + let stored_tx = StoredTx { + action: action.clone(), + amount: amount.u128(), + memo, + block_time: block.time.seconds(), + block_height: block.height, + }; + + TRANSACTIONS.add_suffix(&id.to_be_bytes()).save(store, &stored_tx)?; + TX_COUNT.save(store, &(id+1))?; + Ok(id) +} + #[allow(clippy::too_many_arguments)] // We just need them pub fn store_transfer( store: &mut dyn Storage, @@ -296,22 +318,14 @@ pub fn store_transfer( receiver: &Addr, amount: Uint128, memo: Option, - block: &cosmwasm_std::BlockInfo, + block: &BlockInfo, ) -> StdResult<()> { - let id = increment_tx_count(store)?; let action = StoredTxAction::transfer( api.addr_canonicalize(owner.as_str())?, api.addr_canonicalize(sender.as_str())?, api.addr_canonicalize(receiver.as_str())? ); - let tx = StoredTx { - id, - action, - amount: amount.u128(), - memo, - block_time: block.time.seconds(), - block_height: block.height, - }; + let id = append_new_stored_tx(store, &action, amount, memo, block)?; // Write to the owners history if it's different from the other two addresses // TODO: check if we want to always write this. @@ -340,12 +354,11 @@ pub fn store_mint( memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { - let id = increment_tx_count(store)?; let action = StoredTxAction::mint( api.addr_canonicalize(minter.as_str())?, api.addr_canonicalize(recipient.as_str())? ); - let tx = StoredTx::new(id, action, amount, memo, block); + let id = append_new_stored_tx(store, &action, amount, memo, block)?; if minter != recipient { StoredTx::append_tx(store, &tx, &recipient)?; @@ -366,12 +379,11 @@ pub fn store_burn( memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { - let id = increment_tx_count(store)?; let action = StoredTxAction::burn( api.addr_canonicalize(owner.as_str())?, api.addr_canonicalize(burner.as_str())? ); - let tx = StoredTx::new(id, action, amount, memo, block); + let id = append_new_stored_tx(store, &action, amount, memo, block)?; if burner != owner { StoredTx::append_tx(store, &tx, &owner)?; @@ -388,9 +400,8 @@ pub fn store_deposit( amount: Uint128, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { - let id = increment_tx_count(store)?; let action = StoredTxAction::deposit(); - let tx = StoredTx::new(id, action, amount, None, block); + let id = append_new_stored_tx(store, &action, amount, None, block)?; StoredTx::append_tx(store, &tx, recipient)?; @@ -403,9 +414,8 @@ pub fn store_redeem( amount: Uint128, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { - let id = increment_tx_count(store)?; let action = StoredTxAction::redeem(); - let tx = StoredTx::new(id, action, amount, None, block); + let id = append_new_stored_tx(store, &action, amount, None, block)?; StoredTx::append_tx(store, &tx, redeemer)?; From 3108d7a0d2f1fc369aa1a729237a47b30c6ab58b Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 18 May 2024 23:17:28 +1200 Subject: [PATCH 09/87] balances key -> canonical addr --- src/contract.rs | 116 +++++++++++++++++++++++++------------ src/state.rs | 12 ++-- src/transaction_history.rs | 39 +++++++------ 3 files changed, 104 insertions(+), 63 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 88d0bb78..2724a7be 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,8 +1,7 @@ /// This contract implements SNIP-20 standard: /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md use cosmwasm_std::{ - entry_point, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, - MessageInfo, Response, StdError, StdResult, Storage, Uint128, + entry_point, to_binary, Addr, BankMsg, Binary, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 }; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; @@ -72,7 +71,7 @@ pub fn instantiate( let initial_balances = msg.initial_balances.unwrap_or_default(); for balance in initial_balances { let amount = balance.amount.u128(); - let balance_address = deps.api.addr_validate(balance.address.as_str())?; + let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?; // Here amount is also the amount to be added because the account has no prior balance BalancesStore::update_balance( deps.storage, @@ -93,7 +92,7 @@ pub fn instantiate( store_mint( deps.storage, deps.api, - admin.clone(), + deps.api.addr_canonicalize(admin.as_str())?, balance_address, balance.amount, Some("Initial Balance".to_string()), @@ -564,7 +563,7 @@ pub fn query_transactions( page: u32, page_size: u32, ) -> StdResult { - // Notice that if query_transactions() was called by a viewking-key call, the address of + // Notice that if query_transactions() was called by a viewing-key call, the address of // 'account' has already been validated. // The address of 'account' should not be validated if query_transactions() was called by a // permit call, for compatibility with non-Secret addresses. @@ -612,7 +611,7 @@ pub fn query_balance(deps: Deps, account: String) -> StdResult { // call, for compatibility with non-Secret addresses. let account = Addr::unchecked(account); - let amount = Uint128::new(BalancesStore::load(deps.storage, &account)); + let amount = Uint128::new(BalancesStore::load(deps.storage, &deps.api.addr_canonicalize(account.as_str())?)); let response = QueryAnswer::Balance { amount }; to_binary(&response) } @@ -702,10 +701,12 @@ fn try_mint_impl( block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let raw_amount = amount.u128(); + let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; + let raw_minter = deps.api.addr_canonicalize(minter.as_str())?; BalancesStore::update_balance( deps.storage, - &recipient, + &raw_recipient, raw_amount, true, "", @@ -714,8 +715,8 @@ fn try_mint_impl( store_mint( deps.storage, deps.api, - minter, - recipient, + raw_minter, + raw_recipient, amount, memo, block, @@ -972,11 +973,11 @@ fn try_deposit( raw_amount = safe_add(&mut total_supply, raw_amount); TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - let sender_address = &info.sender; + let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; BalancesStore::update_balance( deps.storage, - sender_address, + &sender_address, raw_amount, true, "", @@ -984,7 +985,7 @@ fn try_deposit( store_deposit( deps.storage, - sender_address, + &sender_address, Uint128::new(raw_amount), //"uscrt".to_string(), &env.block, @@ -1024,12 +1025,12 @@ fn try_redeem( )); }; - let sender_address = &info.sender; + let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; let amount_raw = amount.u128(); BalancesStore::update_balance( deps.storage, - sender_address, + &sender_address, amount_raw, false, "redeem", @@ -1061,7 +1062,7 @@ fn try_redeem( store_redeem( deps.storage, - sender_address, + &sender_address, amount, &env.block, )?; @@ -1085,19 +1086,23 @@ fn try_transfer_impl( memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { + let raw_sender = deps.api.addr_canonicalize(sender.as_str())?; + let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; + perform_transfer( deps.storage, - sender, - recipient, + rng, + &raw_sender, + &raw_recipient, amount.u128(), )?; store_transfer( deps.storage, deps.api, - sender, - sender, - recipient, + raw_sender, + raw_sender, + raw_recipient, amount, memo, block, @@ -1341,6 +1346,7 @@ fn use_allowance( #[allow(clippy::too_many_arguments)] fn try_transfer_from_impl( deps: &mut DepsMut, + rng: &mut ContractPrng, env: &Env, spender: &Addr, owner: &Addr, @@ -1349,22 +1355,26 @@ fn try_transfer_from_impl( memo: Option, ) -> StdResult<()> { let raw_amount = amount.u128(); + let raw_spender = deps.api.addr_canonicalize(spender.as_str())?; + let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; + let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; use_allowance(deps.storage, env, owner, spender, raw_amount)?; perform_transfer( deps.storage, - owner, - recipient, + rng, + &raw_owner, + &raw_recipient, raw_amount, )?; store_transfer( deps.storage, deps.api, - owner, - spender, - recipient, + raw_owner, + raw_spender, + raw_recipient, amount, memo, &env.block, @@ -1383,10 +1393,14 @@ fn try_transfer_from( amount: Uint128, memo: Option, ) -> StdResult { + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; try_transfer_from_impl( &mut deps, + &mut rng, env, &info.sender, &owner, @@ -1404,11 +1418,15 @@ fn try_batch_transfer_from( info: MessageInfo, actions: Vec, ) -> StdResult { + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_transfer_from_impl( &mut deps, + &mut rng, env, &info.sender, &owner, @@ -1438,9 +1456,13 @@ fn try_send_from_impl( memo: Option, msg: Option, ) -> StdResult<()> { + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + let spender = info.sender.clone(); try_transfer_from_impl( deps, + &mut rng, &env, &spender, &owner, @@ -1539,6 +1561,7 @@ fn try_burn_from( memo: Option, ) -> StdResult { let owner = deps.api.addr_validate(owner.as_str())?; + let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; let constants = CONFIG.load(deps.storage)?; if !constants.burn_is_enabled { return Err(StdError::generic_err( @@ -1551,7 +1574,7 @@ fn try_burn_from( BalancesStore::update_balance( deps.storage, - &owner, + &raw_owner, raw_amount, false, "burn", @@ -1572,8 +1595,8 @@ fn try_burn_from( store_burn( deps.storage, deps.api, - owner, - info.sender, + raw_owner, + deps.api.addr_canonicalize(info.sender.as_str())?, amount, memo, &env.block, @@ -1595,17 +1618,18 @@ fn try_batch_burn_from( )); } - let spender = info.sender; + let raw_spender = deps.api.addr_canonicalize(info.sender.as_str())?; let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; + let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; let amount = action.amount.u128(); - use_allowance(deps.storage, env, &owner, &spender, amount)?; + use_allowance(deps.storage, env, &owner, &info.sender, amount)?; BalancesStore::update_balance( deps.storage, - &owner, + &raw_owner, amount, false, "burn", @@ -1623,8 +1647,8 @@ fn try_batch_burn_from( store_burn( deps.storage, deps.api, - owner, - spender.clone(), + raw_owner, + raw_spender, action.amount, action.memo, &env.block, @@ -1805,10 +1829,11 @@ fn try_burn( } let raw_amount = amount.u128(); + let raw_burn_address = deps.api.addr_canonicalize(info.sender.as_str())?; BalancesStore::update_balance( deps.storage, - &info.sender, + &raw_burn_address, raw_amount, false, "burn", @@ -1827,8 +1852,8 @@ fn try_burn( store_burn( deps.storage, deps.api, - info.sender.clone(), - info.sender, + raw_burn_address.clone(), + raw_burn_address, amount, memo, &env.block, @@ -1839,16 +1864,31 @@ fn try_burn( fn perform_transfer( store: &mut dyn Storage, - from: &Addr, - to: &Addr, + rng: &mut ContractPrng, + from: &CanonicalAddr, + to: &CanonicalAddr, amount: u128, ) -> StdResult<()> { // load delayed write buffer let dwb = DWB.load(store)?; + // check if `from` is in the dwb + let mut from_in_dwb = false; + for element in dwb.elements { + if element.recipient == *from { + + } + } + // check have enough funds and withdraw `amount` from `from` account BalancesStore::update_balance(store, from, amount, false, "transfer")?; + if dwb.saturated() { + + } else { + + } + // TODO: BalancesStore::update_balance( store, diff --git a/src/state.rs b/src/state.rs index 7056bd79..19b93933 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, StdError, StdResult, Storage}; +use cosmwasm_std::{Addr, CanonicalAddr, StdError, StdResult, Storage}; use secret_toolkit::serialization::Json; use secret_toolkit::storage::{Item, Keymap, Keyset}; use secret_toolkit_crypto::SHA256_HASH_SIZE; @@ -122,19 +122,19 @@ pub fn safe_add(balance: &mut u128, amount: u128) -> u128 { pub static BALANCES: Item = Item::new(PREFIX_BALANCES); pub struct BalancesStore {} impl BalancesStore { - fn save(store: &mut dyn Storage, account: &Addr, amount: u128) -> StdResult<()> { - let balances = BALANCES.add_suffix(account.as_str().as_bytes()); + fn save(store: &mut dyn Storage, account: &CanonicalAddr, amount: u128) -> StdResult<()> { + let balances = BALANCES.add_suffix(account.as_slice()); balances.save(store, &amount) } - pub fn load(store: &dyn Storage, account: &Addr) -> u128 { - let balances = BALANCES.add_suffix(account.as_str().as_bytes()); + pub fn load(store: &dyn Storage, account: &CanonicalAddr) -> u128 { + let balances = BALANCES.add_suffix(account.as_slice()); balances.load(store).unwrap_or_default() } pub fn update_balance( store: &mut dyn Storage, - account: &Addr, + account: &CanonicalAddr, amount_to_be_updated: u128, should_add: bool, operation_name: &str, diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 64db070e..2719a669 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -225,7 +225,6 @@ impl StoredTx { block: &cosmwasm_std::BlockInfo, ) -> Self { Self { - id, action, amount: amount.u128(), memo, @@ -234,9 +233,9 @@ impl StoredTx { } } - fn into_humanized(self, api: &dyn Api) -> StdResult { + fn into_humanized(self, api: &dyn Api, id: u64) -> StdResult { Ok(Tx { - id: self.id, + id, action: self.action.into_tx_action(api)?, amount: Uint128::from(self.amount), memo: self.memo, @@ -245,6 +244,7 @@ impl StoredTx { }) } +/* fn append_tx( store: &mut dyn Storage, tx: &StoredTx, @@ -278,6 +278,7 @@ impl StoredTx { .collect(); txs.map(|txs| (txs, len)) } +*/ } // Storage functions: @@ -313,17 +314,17 @@ fn append_new_stored_tx( pub fn store_transfer( store: &mut dyn Storage, api: &dyn Api, - owner: &Addr, - sender: &Addr, - receiver: &Addr, + owner: CanonicalAddr, + sender: CanonicalAddr, + receiver: CanonicalAddr, amount: Uint128, memo: Option, block: &BlockInfo, ) -> StdResult<()> { let action = StoredTxAction::transfer( - api.addr_canonicalize(owner.as_str())?, - api.addr_canonicalize(sender.as_str())?, - api.addr_canonicalize(receiver.as_str())? + owner, + sender, + receiver ); let id = append_new_stored_tx(store, &action, amount, memo, block)?; @@ -348,15 +349,15 @@ pub fn store_transfer( pub fn store_mint( store: &mut dyn Storage, api: &dyn Api, - minter: Addr, - recipient: Addr, + minter: CanonicalAddr, + recipient: CanonicalAddr, amount: Uint128, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let action = StoredTxAction::mint( - api.addr_canonicalize(minter.as_str())?, - api.addr_canonicalize(recipient.as_str())? + minter, + recipient ); let id = append_new_stored_tx(store, &action, amount, memo, block)?; @@ -373,15 +374,15 @@ pub fn store_mint( pub fn store_burn( store: &mut dyn Storage, api: &dyn Api, - owner: Addr, - burner: Addr, + owner: CanonicalAddr, + burner: CanonicalAddr, amount: Uint128, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let action = StoredTxAction::burn( - api.addr_canonicalize(owner.as_str())?, - api.addr_canonicalize(burner.as_str())? + owner, + burner ); let id = append_new_stored_tx(store, &action, amount, memo, block)?; @@ -396,7 +397,7 @@ pub fn store_burn( pub fn store_deposit( store: &mut dyn Storage, - recipient: &Addr, + recipient: &CanonicalAddr, amount: Uint128, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { @@ -410,7 +411,7 @@ pub fn store_deposit( pub fn store_redeem( store: &mut dyn Storage, - redeemer: &Addr, + redeemer: &CanonicalAddr, amount: Uint128, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { From a24d2058ce02c849149a1126e461d67fd728661e Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 19 May 2024 21:06:23 +1200 Subject: [PATCH 10/87] dev: settling owner in dwb --- src/contract.rs | 203 +++++++++++++++++++++++++++---------- src/dwb.rs | 145 ++++++++++++++++++++++++-- src/state.rs | 2 +- src/transaction_history.rs | 60 ++++++----- 4 files changed, 325 insertions(+), 85 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 2724a7be..95123f7e 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,7 +1,7 @@ /// This contract implements SNIP-20 standard: /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md use cosmwasm_std::{ - entry_point, to_binary, Addr, BankMsg, Binary, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 + entry_point, to_binary, Addr, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 }; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; @@ -9,7 +9,7 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{DelayedWriteBuffer, DWB, DWB_LEN}; +use crate::dwb::{AccountTxsStore, DelayedWriteBuffer, DelayedWriteBufferElement, TxNode, DWB, DWB_LEN, TX_NODES}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -21,7 +21,7 @@ use crate::state::{ }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredTx, TxAction, + append_new_stored_tx, store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredTx, StoredTxAction, TxAction }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -94,7 +94,7 @@ pub fn instantiate( deps.api, deps.api.addr_canonicalize(admin.as_str())?, balance_address, - balance.amount, + balance.amount.u128(), Some("Initial Balance".to_string()), &env.block, )?; @@ -568,7 +568,7 @@ pub fn query_transactions( // The address of 'account' should not be validated if query_transactions() was called by a // permit call, for compatibility with non-Secret addresses. let account = Addr::unchecked(account); - +/* let (txs, total) = StoredTx::get_txs( deps.storage, @@ -596,10 +596,12 @@ pub fn query_transactions( block_time: tx.block_time, } }).collect(); - +*/ let result = QueryAnswer::TransactionHistory { - txs, - total: Some(total), + //txs, + txs: vec![], // TODO update + //total: Some(total), + total: None, }; to_binary(&result) } @@ -717,7 +719,7 @@ fn try_mint_impl( deps.api, raw_minter, raw_recipient, - amount, + amount.u128(), memo, block, )?; @@ -986,7 +988,7 @@ fn try_deposit( store_deposit( deps.storage, &sender_address, - Uint128::new(raw_amount), + raw_amount, //"uscrt".to_string(), &env.block, )?; @@ -1063,7 +1065,7 @@ fn try_redeem( store_redeem( deps.storage, &sender_address, - amount, + amount.u128(), &env.block, )?; @@ -1094,16 +1096,8 @@ fn try_transfer_impl( rng, &raw_sender, &raw_recipient, + &raw_sender, amount.u128(), - )?; - - store_transfer( - deps.storage, - deps.api, - raw_sender, - raw_sender, - raw_recipient, - amount, memo, block, )?; @@ -1366,16 +1360,8 @@ fn try_transfer_from_impl( rng, &raw_owner, &raw_recipient, + &raw_spender, raw_amount, - )?; - - store_transfer( - deps.storage, - deps.api, - raw_owner, - raw_spender, - raw_recipient, - amount, memo, &env.block, )?; @@ -1597,7 +1583,7 @@ fn try_burn_from( deps.api, raw_owner, deps.api.addr_canonicalize(info.sender.as_str())?, - amount, + amount.u128(), memo, &env.block, )?; @@ -1648,8 +1634,8 @@ fn try_batch_burn_from( deps.storage, deps.api, raw_owner, - raw_spender, - action.amount, + raw_spender.clone(), + action.amount.u128(), action.memo, &env.block, )?; @@ -1854,7 +1840,7 @@ fn try_burn( deps.api, raw_burn_address.clone(), raw_burn_address, - amount, + amount.u128(), memo, &env.block, )?; @@ -1867,25 +1853,104 @@ fn perform_transfer( rng: &mut ContractPrng, from: &CanonicalAddr, to: &CanonicalAddr, + sender: &CanonicalAddr, amount: u128, + memo: Option, + block: &BlockInfo, ) -> StdResult<()> { + // first store the tx information in the global append list of txs and get the new tx id + let action = StoredTxAction::transfer( + from.clone(), + sender.clone(), + to.clone() + ); + let tx_id = append_new_stored_tx(store, &action, amount, memo, block)?; + // load delayed write buffer - let dwb = DWB.load(store)?; + let mut dwb = DWB.load(store)?; - // check if `from` is in the dwb - let mut from_in_dwb = false; - for element in dwb.elements { + // check if `from` is already in the delayed write buffer + let mut from_element_in_dwb = None; + let mut from_index_in_dwb = None; + for (idx, element) in dwb.elements.iter().enumerate() { if element.recipient == *from { + from_element_in_dwb = Some(element.clone()); + from_index_in_dwb = Some(idx); + } + } + let mut from_should_add = false; + let mut from_amount = amount; + // if `from` is in dwb, settle the transfer in buffer first + if let Some(mut from_element) = from_element_in_dwb { + // replace with random address in the buffer + let random_addr = CanonicalAddr::from(&rng.rand_bytes()[0..20]); + dwb.elements[from_index_in_dwb.unwrap()] = DelayedWriteBufferElement { + recipient: random_addr, + amount: 0, + list_len: 0, + head_node: None, + }; + + // figure out how much to subtract or add + if from_element.amount > amount { // change from subtract to add + from_amount = from_element.amount - amount; + from_should_add = true; + } else { + from_amount = amount - from_element.amount; } + + // now add this tx at the head before adding new tx bundle for account history + from_element.add_tx_node(store, tx_id, None)?; + let head_node = from_element.head_node.ok_or_else(|| return StdError::generic_err("Corrupted DWB element"))?; + + AccountTxsStore::append_bundle( + store, + from, + head_node, + from_element.list_len + )?; + } else { // not in dwb, so we just create a single node and push it as a bundle in the account tx history + let tx_node = TxNode { + tx_id, + next: None, + }; + TX_NODES.push(store, &tx_node)?; + let head_node = TX_NODES.get_len(store)? - 1; + AccountTxsStore::append_bundle( + store, + from, + head_node, + 1 + )?; } - // check have enough funds and withdraw `amount` from `from` account - BalancesStore::update_balance(store, from, amount, false, "transfer")?; + // TODO: now check if we have to settle anything for `sender` (if different from `from`) + if sender != from { + + } + + // check have enough funds and withdraw `amount` from `from` account, + // or in case there are more received tokens in the delayed write buffer, + // then add the difference. + BalancesStore::update_balance(store, from, from_amount, from_should_add, "transfer")?; + + // check if `to` is already in the delayed write buffer + let mut to_element_in_dwb = None; + let mut to_index_in_dwb = None; + for (idx, element) in dwb.elements.iter().enumerate() { + if element.recipient == *to { + to_element_in_dwb = Some(element.clone()); + to_index_in_dwb = Some(idx); + } + } + + // if it is in the delayed write buffer if dwb.saturated() { } else { + // we want to simply add to the buffer without settling anything } @@ -1947,7 +2012,7 @@ fn is_valid_symbol(symbol: &str) -> bool { mod tests { use std::any::Any; - use cosmwasm_std::testing::*; + use cosmwasm_std::{testing::*, Api}; use cosmwasm_std::{ from_binary, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, WasmMsg, @@ -2235,8 +2300,14 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - let bob_addr = Addr::unchecked("bob".to_string()); - let alice_addr = Addr::unchecked("alice".to_string()); + let bob_addr = deps + .api + .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str()) + .unwrap(); + let alice_addr = deps + .api + .addr_canonicalize(Addr::unchecked("alice".to_string()).as_str()) + .unwrap(); assert_eq!(5000 - 1000, BalancesStore::load(&deps.storage, &bob_addr)); assert_eq!(1000, BalancesStore::load(&deps.storage, &alice_addr)); @@ -2745,8 +2816,14 @@ mod tests { "handle() failed: {}", handle_result.err().unwrap() ); - let bob_canonical = Addr::unchecked("bob".to_string()); - let alice_canonical = Addr::unchecked("alice".to_string()); + let bob_canonical = deps + .api + .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str()) + .unwrap(); + let alice_canonical = deps + .api + .addr_canonicalize(Addr::unchecked("alice".to_string()).as_str()) + .unwrap(); let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); let alice_balance = BalancesStore::load(&deps.storage, &alice_canonical); @@ -2881,8 +2958,16 @@ mod tests { ) .unwrap() )); - let bob_canonical = Addr::unchecked("bob".to_string()); - let contract_canonical = Addr::unchecked("contract".to_string()); + + let bob_canonical = deps + .api + .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str()) + .unwrap(); + let contract_canonical = deps + .api + .addr_canonicalize(Addr::unchecked("contract".to_string()).as_str()) + .unwrap(); + let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); let contract_balance = BalancesStore::load(&deps.storage, &contract_canonical); assert_eq!(bob_balance, 5000 - 2000); @@ -3010,7 +3095,11 @@ mod tests { "handle() failed: {}", handle_result.err().unwrap() ); - let bob_canonical = Addr::unchecked("bob".to_string()); + let bob_canonical = deps + .api + .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str()) + .unwrap(); + let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); assert_eq!(bob_balance, 10000 - 2000); let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); @@ -3156,7 +3245,10 @@ mod tests { handle_result.err().unwrap() ); for (name, amount) in &[("bob", 200_u128), ("jerry", 300), ("mike", 400)] { - let name_canon = Addr::unchecked(name.to_string()); + let name_canon = deps + .api + .addr_canonicalize(Addr::unchecked(name.to_string()).as_str()) + .unwrap(); let balance = BalancesStore::load(&deps.storage, &name_canon); assert_eq!(balance, 10000 - amount); } @@ -3187,7 +3279,10 @@ mod tests { handle_result.err().unwrap() ); for name in &["bob", "jerry", "mike"] { - let name_canon = Addr::unchecked(name.to_string()); + let name_canon = deps + .api + .addr_canonicalize(Addr::unchecked(name.to_string()).as_str()) + .unwrap(); let balance = BalancesStore::load(&deps.storage, &name_canon); assert_eq!(balance, 10000 - allowance_size); } @@ -3534,7 +3629,10 @@ mod tests { handle_result.err().unwrap() ); - let canonical = Addr::unchecked("butler".to_string()); + let canonical = deps + .api + .addr_canonicalize(Addr::unchecked("butler".to_string()).as_str()) + .unwrap(); assert_eq!(BalancesStore::load(&deps.storage, &canonical), 3000) } @@ -3602,7 +3700,10 @@ mod tests { handle_result.err().unwrap() ); - let canonical = Addr::unchecked("lebron".to_string()); + let canonical = deps + .api + .addr_canonicalize(Addr::unchecked("lebron".to_string()).as_str()) + .unwrap(); assert_eq!(BalancesStore::load(&deps.storage, &canonical), 6000) } diff --git a/src/dwb.rs b/src/dwb.rs index 8a2eba44..42db30fc 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,14 +1,19 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::CanonicalAddr; -use secret_toolkit::storage::Item; +use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; +use secret_toolkit::storage::{AppendStore, Item}; + +use crate::transaction_history::Tx; pub const DWB_LEN: u16 = 64; pub const KEY_DWB: &[u8] = b"dwb"; -pub const KEY_NEXT_LIST_ID: &[u8] = b"dwb-list"; +pub const KEY_TX_NODES: &[u8] = b"dwb-tx-nodes"; +pub const KEY_ACCOUNT_TXS: &[u8] = b"dwb-acc-txs"; +pub const KEY_ACCOUNT_TX_COUNT: &[u8] = b"dwb-acc-tx-cnt"; pub static DWB: Item = Item::new(KEY_DWB); -pub static NEXT_LIST_ID: Item = Item::new(KEY_NEXT_LIST_ID); +// tx nodes used in linked lists +pub static TX_NODES: AppendStore = AppendStore::new(KEY_TX_NODES); #[derive(Serialize, Deserialize, Debug)] pub struct DelayedWriteBuffer { @@ -23,9 +28,137 @@ impl DelayedWriteBuffer { } } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct DelayedWriteBufferElement { pub recipient: CanonicalAddr, + /// aggregate amount for the tx nodes pub amount: u128, - pub list_id: u64, + /// length of the tx node linked list for this element + pub list_len: u32, + /// TX_NODES idx - pointer to the head node in the linked list + /// None if the list_len == 0 + pub head_node: Option, +} + +impl DelayedWriteBufferElement { + pub fn new(recipient: CanonicalAddr) -> Self { + Self { + recipient, + amount: 0, + list_len: 0, + head_node: None, + } + } + + pub fn add_tx_node(&mut self, store: &mut dyn Storage, tx_id: u64, add_tx_amount: Option) -> StdResult<()> { + if let Some(head_node) = self.head_node { + let tx_node = TxNode { + tx_id, + next: Some(head_node), + }; + TX_NODES.push(store, &tx_node)?; + let new_head_node = TX_NODES.get_len(store)? - 1; + self.head_node = Some(new_head_node); + self.list_len = self.list_len + 1; + } else { + let tx_node = TxNode { + tx_id, + next: None, + }; + TX_NODES.push(store, &tx_node)?; + let head_node = TX_NODES.get_len(store)? - 1; + self.head_node = Some(head_node); + self.list_len = 1; + } + if let Some(add_tx_amount) = add_tx_amount { + self.amount = self.amount.saturating_add(add_tx_amount); + } + + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TxNode { + /// transaction id in the TRANSACTIONS list + pub tx_id: u64, + /// TX_NODES idx - pointer to the next node in the linked list + pub next: Option, +} + + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TxBundle { + /// TX_NODES idx - pointer to the head tx node in the linked list + pub head_node: u32, + /// length of the tx node linked list for this element + pub list_len: u32, + /// offset of the first tx of this bundle in the history of txs for the account (for pagination) + pub offset: u32, } + +/// A tx bundle is 1 or more tx nodes added to an account's history. +/// The bundle points to a linked list of transaction nodes, which each reference +/// a transaction record by its global id. +/// used with add_suffix(canonical addr of account) +pub static ACCOUNT_TXS: AppendStore = AppendStore::new(KEY_ACCOUNT_TXS); + +/// Keeps track of the total count of txs for an account (not tx bundles) +/// used with add_suffix(canonical addr of account) +pub static ACCOUNT_TX_COUNT: Item = Item::new(KEY_ACCOUNT_TX_COUNT); + +pub struct AccountTxsStore {} +impl AccountTxsStore { + /// appends a new tx bundle for an account when tx occurs or is settled. + pub fn append_bundle(store: &mut dyn Storage, account: &CanonicalAddr, head_node: u32, list_len: u32) -> StdResult<()> { + let account_txs_store = ACCOUNT_TXS.add_suffix(account.as_slice()); + let account_txs_len = account_txs_store.get_len(store)?; + let tx_bundle; + if account_txs_len > 0 { + // peek at the last tx bundle added + let last_tx_bundle = account_txs_store.get_at(store, account_txs_len - 1)?; + tx_bundle = TxBundle { + head_node, + list_len, + offset: last_tx_bundle.offset + last_tx_bundle.list_len, + }; + } else { // this is the first bundle for the account + tx_bundle = TxBundle { + head_node, + list_len, + offset: 0, + }; + } + + // update the total count of txs for account + let account_tx_count_store = ACCOUNT_TX_COUNT.add_suffix(account.as_slice()); + let account_tx_count = account_tx_count_store.may_load(store)?.unwrap_or_default(); + account_tx_count_store.save(store, &(account_tx_count.saturating_add(list_len)))?; + + account_txs_store.push(store, &tx_bundle) + } + + /// Does a binary search on the append store to find the bundle where the `start_idx` tx can be found. + /// For a paginated search `start_idx` = `page` * `page_size`. + pub fn find_start_bundle(store: &dyn Storage, account: CanonicalAddr, start_idx: u32) -> StdResult> { + let account_txs_store = ACCOUNT_TXS.add_suffix(account.as_slice()); + + let mut left = 0u32; + let mut right = account_txs_store.get_len(store)?; + + while left <= right { + let mid = (left + right) / 2; + let mid_bundle = account_txs_store.get_at(store, mid)?; + if start_idx >= mid_bundle.offset && start_idx < mid_bundle.offset + mid_bundle.list_len { + // we have the correct bundle + return Ok(Some((mid, mid_bundle))); + } else if start_idx < mid_bundle.offset { + right = mid - 1; + } else { + left = mid + 1; + } + } + + Ok(None) + } +} \ No newline at end of file diff --git a/src/state.rs b/src/state.rs index 19b93933..34393ed4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -108,7 +108,7 @@ impl MintersStore { // To avoid balance guessing attacks based on balance overflow we need to perform safe addition and don't expose overflows to the caller. // Assuming that max of u128 is probably an unreachable balance, we want the addition to be bounded the max of u128 -// Currently the logic here is very straight forward yet the existence of the function is mendatory for future changes if needed. +// Currently the logic here is very straight forward yet the existence of the function is mandatory for future changes if needed. pub fn safe_add(balance: &mut u128, amount: u128) -> u128 { // Note that new_amount can be equal to base after this operation. // Currently we do nothing maybe on other implementations we will have something to add here diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 2719a669..c5746d6d 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -104,7 +104,7 @@ impl TxCode { #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "snake_case")] -struct StoredTxAction { +pub struct StoredTxAction { tx_type: u8, address1: Option, address2: Option, @@ -112,7 +112,7 @@ struct StoredTxAction { } impl StoredTxAction { - fn transfer(from: CanonicalAddr, sender: CanonicalAddr, recipient: CanonicalAddr) -> Self { + pub fn transfer(from: CanonicalAddr, sender: CanonicalAddr, recipient: CanonicalAddr) -> Self { Self { tx_type: TxCode::Transfer.to_u8(), address1: Some(from), @@ -120,7 +120,7 @@ impl StoredTxAction { address3: Some(recipient), } } - fn mint(minter: CanonicalAddr, recipient: CanonicalAddr) -> Self { + pub fn mint(minter: CanonicalAddr, recipient: CanonicalAddr) -> Self { Self { tx_type: TxCode::Mint.to_u8(), address1: Some(minter), @@ -128,7 +128,7 @@ impl StoredTxAction { address3: None, } } - fn burn(owner: CanonicalAddr, burner: CanonicalAddr) -> Self { + pub fn burn(owner: CanonicalAddr, burner: CanonicalAddr) -> Self { Self { tx_type: TxCode::Burn.to_u8(), address1: Some(burner), @@ -136,7 +136,7 @@ impl StoredTxAction { address3: None, } } - fn deposit() -> Self { + pub fn deposit() -> Self { Self { tx_type: TxCode::Deposit.to_u8(), address1: None, @@ -144,7 +144,7 @@ impl StoredTxAction { address3: None, } } - fn redeem() -> Self { + pub fn redeem() -> Self { Self { tx_type: TxCode::Redeem.to_u8(), address1: None, @@ -153,7 +153,7 @@ impl StoredTxAction { } } - fn into_tx_action(self, api: &dyn Api) -> StdResult { + pub fn into_tx_action(self, api: &dyn Api) -> StdResult { let transfer_addr_err = || { StdError::generic_err( "Missing address in stored Transfer transaction. Storage is corrupt", @@ -289,17 +289,17 @@ fn increment_tx_count(store: &mut dyn Storage) -> StdResult { Ok(id) } -fn append_new_stored_tx( +pub fn append_new_stored_tx( store: &mut dyn Storage, action: &StoredTxAction, - amount: Uint128, + amount: u128, memo: Option, block: &BlockInfo, ) -> StdResult { let id = TX_COUNT.load(store).unwrap_or_default(); let stored_tx = StoredTx { action: action.clone(), - amount: amount.u128(), + amount, memo, block_time: block.time.seconds(), block_height: block.height, @@ -313,21 +313,20 @@ fn append_new_stored_tx( #[allow(clippy::too_many_arguments)] // We just need them pub fn store_transfer( store: &mut dyn Storage, - api: &dyn Api, - owner: CanonicalAddr, - sender: CanonicalAddr, - receiver: CanonicalAddr, - amount: Uint128, + owner: &CanonicalAddr, + sender: &CanonicalAddr, + receiver: &CanonicalAddr, + amount: u128, memo: Option, block: &BlockInfo, -) -> StdResult<()> { +) -> StdResult { let action = StoredTxAction::transfer( - owner, - sender, - receiver + owner.clone(), + sender.clone(), + receiver.clone() ); - let id = append_new_stored_tx(store, &action, amount, memo, block)?; - + append_new_stored_tx(store, &action, amount, memo, block) +/* // Write to the owners history if it's different from the other two addresses // TODO: check if we want to always write this. if owner != sender && owner != receiver { @@ -342,8 +341,7 @@ pub fn store_transfer( // Always write to the recipient's history // cosmwasm_std::debug_print("saving transaction history for receiver"); StoredTx::append_tx(store, &tx, receiver)?; - - Ok(()) +*/ } pub fn store_mint( @@ -351,7 +349,7 @@ pub fn store_mint( api: &dyn Api, minter: CanonicalAddr, recipient: CanonicalAddr, - amount: Uint128, + amount: u128, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { @@ -361,11 +359,13 @@ pub fn store_mint( ); let id = append_new_stored_tx(store, &action, amount, memo, block)?; +/* if minter != recipient { StoredTx::append_tx(store, &tx, &recipient)?; } StoredTx::append_tx(store, &tx, &minter)?; +*/ Ok(()) } @@ -376,7 +376,7 @@ pub fn store_burn( api: &dyn Api, owner: CanonicalAddr, burner: CanonicalAddr, - amount: Uint128, + amount: u128, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { @@ -386,11 +386,13 @@ pub fn store_burn( ); let id = append_new_stored_tx(store, &action, amount, memo, block)?; +/* if burner != owner { StoredTx::append_tx(store, &tx, &owner)?; } StoredTx::append_tx(store, &tx, &burner)?; +*/ Ok(()) } @@ -398,13 +400,15 @@ pub fn store_burn( pub fn store_deposit( store: &mut dyn Storage, recipient: &CanonicalAddr, - amount: Uint128, + amount: u128, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let action = StoredTxAction::deposit(); let id = append_new_stored_tx(store, &action, amount, None, block)?; +/* StoredTx::append_tx(store, &tx, recipient)?; +*/ Ok(()) } @@ -412,13 +416,15 @@ pub fn store_deposit( pub fn store_redeem( store: &mut dyn Storage, redeemer: &CanonicalAddr, - amount: Uint128, + amount: u128, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { let action = StoredTxAction::redeem(); let id = append_new_stored_tx(store, &action, amount, None, block)?; +/* StoredTx::append_tx(store, &tx, redeemer)?; +*/ Ok(()) } \ No newline at end of file From 3b538affd38906f8beb1352a642b0c149a62b838 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 22 May 2024 21:07:39 +1200 Subject: [PATCH 11/87] dwb array development --- Cargo.lock | 132 ++++++++++++++- Cargo.toml | 2 + src/contract.rs | 94 ++--------- src/dwb.rs | 333 ++++++++++++++++++++++++++++++++----- src/state.rs | 15 +- src/transaction_history.rs | 9 +- 6 files changed, 459 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19581bbc..69887e74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.14.6" @@ -306,7 +318,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -414,6 +426,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -433,6 +468,21 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -448,6 +498,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "remain" version = "0.2.14" @@ -479,6 +538,25 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +dependencies = [ + "gcc", + "libc", + "rand 0.3.23", + "rustc-serialize", + "time", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" + [[package]] name = "ryu" version = "1.0.13" @@ -686,6 +764,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "0.4.1" @@ -768,13 +855,15 @@ version = "1.0.0" dependencies = [ "base64 0.21.0", "cosmwasm-schema", - "rand", + "rand 0.8.5", + "rust-crypto", "schemars", "secret-cosmwasm-std", "secret-cosmwasm-storage", "secret-toolkit", "secret-toolkit-crypto", "serde", + "serde-big-array", ] [[package]] @@ -841,6 +930,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "typenum" version = "1.16.0" @@ -871,12 +971,40 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "zeroize" version = "1.5.7" diff --git a/Cargo.toml b/Cargo.toml index 6a53632c..575f95ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,9 @@ secret-toolkit-crypto = { version = "0.10.0", features = ["rand", "hash"] } schemars = "0.8.12" serde = { version = "1.0.158", default-features = false, features = ["derive"] } +serde-big-array = "0.5.1" base64 = "0.21.0" +rust-crypto = "0.2.36" [dev-dependencies] cosmwasm-schema = { version = "1.1.8" } diff --git a/src/contract.rs b/src/contract.rs index 95123f7e..e61addc1 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -3,13 +3,14 @@ use cosmwasm_std::{ entry_point, to_binary, Addr, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 }; +use crypto::buffer; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{AccountTxsStore, DelayedWriteBuffer, DelayedWriteBufferElement, TxNode, DWB, DWB_LEN, TX_NODES}; +use crate::dwb::{AccountTxsStore, DelayedWriteBuffer, DelayedWriteBufferEntry, TxNode, DWB, DWB_LEN, TX_NODES}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -63,10 +64,7 @@ pub fn instantiate( PrngStore::save(deps.storage, prng_seed_hashed)?; // initialize the delay write buffer - DWB.save(deps.storage, &DelayedWriteBuffer{ - empty_space_counter: DWB_LEN, - elements: vec![], - })?; + DWB.save(deps.storage, &DelayedWriteBuffer::new()?)?; let initial_balances = msg.initial_balances.unwrap_or_default(); for balance in initial_balances { @@ -1869,80 +1867,24 @@ fn perform_transfer( // load delayed write buffer let mut dwb = DWB.load(store)?; - // check if `from` is already in the delayed write buffer - let mut from_element_in_dwb = None; - let mut from_index_in_dwb = None; - for (idx, element) in dwb.elements.iter().enumerate() { - if element.recipient == *from { - from_element_in_dwb = Some(element.clone()); - from_index_in_dwb = Some(idx); - } - } - - let mut from_should_add = false; - let mut from_amount = amount; - // if `from` is in dwb, settle the transfer in buffer first - if let Some(mut from_element) = from_element_in_dwb { - // replace with random address in the buffer - let random_addr = CanonicalAddr::from(&rng.rand_bytes()[0..20]); - dwb.elements[from_index_in_dwb.unwrap()] = DelayedWriteBufferElement { - recipient: random_addr, - amount: 0, - list_len: 0, - head_node: None, - }; - - // figure out how much to subtract or add - if from_element.amount > amount { // change from subtract to add - from_amount = from_element.amount - amount; - from_should_add = true; - } else { - from_amount = amount - from_element.amount; - } - - // now add this tx at the head before adding new tx bundle for account history - from_element.add_tx_node(store, tx_id, None)?; - let head_node = from_element.head_node.ok_or_else(|| return StdError::generic_err("Corrupted DWB element"))?; - - AccountTxsStore::append_bundle( - store, - from, - head_node, - from_element.list_len - )?; - } else { // not in dwb, so we just create a single node and push it as a bundle in the account tx history - let tx_node = TxNode { - tx_id, - next: None, - }; - TX_NODES.push(store, &tx_node)?; - let head_node = TX_NODES.get_len(store)? - 1; - AccountTxsStore::append_bundle( - store, - from, - head_node, - 1 - )?; - } - - // TODO: now check if we have to settle anything for `sender` (if different from `from`) + // settle the owner's account + dwb.settle_account(store, rng, from, tx_id, amount)?; + // if this is a *_from action, settle the sender's account, too if sender != from { - + dwb.settle_account(store, rng, sender, tx_id, 0)?; } - // check have enough funds and withdraw `amount` from `from` account, - // or in case there are more received tokens in the delayed write buffer, - // then add the difference. - BalancesStore::update_balance(store, from, from_amount, from_should_add, "transfer")?; - - // check if `to` is already in the delayed write buffer - let mut to_element_in_dwb = None; - let mut to_index_in_dwb = None; - for (idx, element) in dwb.elements.iter().enumerate() { - if element.recipient == *to { - to_element_in_dwb = Some(element.clone()); - to_index_in_dwb = Some(idx); - } + // check if `to` is already a recipient in the delayed write buffer + let buffer_match = dwb.recipient_match(&to); + if let Some(idx) = buffer_match { + // if it is, update the amount for the entry and add a new tx node at the head + let mut entry = dwb.entries[idx]; + entry.add_tx_node(store, tx_id, Some(amount))?; + dwb.entries[idx] = entry; + } else { + // create a new entry + let mut new_entry = DelayedWriteBufferEntry::new(to.clone())?; + new_entry.add_tx_node(store, tx_id, Some(amount))?; } // if it is in the delayed write buffer diff --git a/src/dwb.rs b/src/dwb.rs index 42db30fc..ef5a62ad 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,77 +1,324 @@ +use std::collections::{HashMap, HashSet}; + +use crypto::util::fixed_time_eq; use schemars::JsonSchema; +use secret_toolkit_crypto::{sha_256, ContractPrng}; use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; use secret_toolkit::storage::{AppendStore, Item}; -use crate::transaction_history::Tx; +use crate::{state::{safe_add, safe_add_u64, BalancesStore, BALANCES}, transaction_history::Tx}; -pub const DWB_LEN: u16 = 64; pub const KEY_DWB: &[u8] = b"dwb"; +pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt"; pub const KEY_TX_NODES: &[u8] = b"dwb-tx-nodes"; pub const KEY_ACCOUNT_TXS: &[u8] = b"dwb-acc-txs"; pub const KEY_ACCOUNT_TX_COUNT: &[u8] = b"dwb-acc-tx-cnt"; pub static DWB: Item = Item::new(KEY_DWB); -// tx nodes used in linked lists -pub static TX_NODES: AppendStore = AppendStore::new(KEY_TX_NODES); +// use with add_suffix tx id (u64) +// does not need to be an AppendStore because we never need to iterate over global list of txs +pub static TX_NODES: Item = Item::new(KEY_TX_NODES); +pub static TX_NODES_COUNT: Item = Item::new(KEY_TX_NODES_COUNT); + +fn store_new_tx_node(store: &mut dyn Storage, tx_node: TxNode) -> StdResult { + // tx nodes ids serialized start at 1 + let tx_nodes_serial_id = TX_NODES_COUNT.load(store).unwrap_or_default() + 1; + TX_NODES.add_suffix(&tx_nodes_serial_id.to_be_bytes()).save(store, &tx_node)?; + TX_NODES_COUNT.save(store,&(tx_nodes_serial_id))?; + Ok(tx_nodes_serial_id) +} + +pub const ZERO_ADDR: [u8; 20] = [0u8; 20]; +// 64 entries + 1 "dummy" entry prepended (idx: 0 in DelayedWriteBufferEntry array) +pub const DWB_LEN: u16 = 65; #[derive(Serialize, Deserialize, Debug)] pub struct DelayedWriteBuffer { pub empty_space_counter: u16, - pub elements: Vec, + #[serde(with = "BigArray")] + pub entries: [DelayedWriteBufferEntry; DWB_LEN as usize], +} + +#[inline] +fn random_addr(rng: &mut ContractPrng) -> CanonicalAddr { + CanonicalAddr::from(&rng.rand_bytes()[0..20]) } impl DelayedWriteBuffer { + pub fn new() -> StdResult { + Ok(Self { + empty_space_counter: DWB_LEN - 1, + // first entry is a dummy entry for constant-time writing + entries: [ + DelayedWriteBufferEntry::new(CanonicalAddr::from(&ZERO_ADDR))?; DWB_LEN as usize + ] + }) + } + #[inline] pub fn saturated(&self) -> bool { self.empty_space_counter == 0 } -} -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct DelayedWriteBufferElement { - pub recipient: CanonicalAddr, - /// aggregate amount for the tx nodes - pub amount: u128, - /// length of the tx node linked list for this element - pub list_len: u32, - /// TX_NODES idx - pointer to the head node in the linked list - /// None if the list_len == 0 - pub head_node: Option, + /// settles a participant's account who may or may not have an entry in the buffer + pub fn settle_account( + &mut self, + store: &mut dyn Storage, + rng: &mut ContractPrng, + address: &CanonicalAddr, + tx_id: u64, + amount_spent: u128, + ) -> StdResult<()> { + // release the address from the buffer + let (balance, entry_opt) = self.constant_time_release( + store, + rng, + address + )?; + + if let Some(mut entry) = entry_opt { + // add tx at the head before adding new tx bundle for account history + entry.add_tx_node(store, tx_id, None)?; + AccountTxsStore::append_bundle( + store, + address, + entry.head_node()?, + entry.list_len() + )?; + } else { + // no entry in dwb, so we just create a single node and + // push it as a bundle in the account tx history + let tx_node = TxNode { + tx_id, + next: None, + }; + + let head_node = store_new_tx_node(store, tx_node)?; + AccountTxsStore::append_bundle( + store, + address, + head_node, + 1 + )?; + } + + let new_balance = if let Some(balance_after_sub) = balance.checked_sub(amount_spent) { + balance_after_sub + } else { + return Err(StdError::generic_err(format!( + "insufficient funds to transfer: balance={balance}, required={amount_spent}", + ))); + }; + BalancesStore::save(store, address, new_balance)?; + + Ok(()) + } + + /// "releases" a given recipient from the buffer, removing their entry if one exists, in constant-time + pub fn constant_time_release( + &mut self, + store: &mut dyn Storage, + rng: &mut ContractPrng, + address: &CanonicalAddr + ) -> StdResult<(u128, Option)> { + // get the address' stored balance + let mut balance = BalancesStore::load(store, address); + + // locate the position of the entry in the buffer + let matched_entry = self.recipient_match(address); + + // produce a new random address + let mut replacement_address = random_addr(rng); + // ensure random addr is not already in dwb (extremely unlikely!!) + while self.recipient_match(&replacement_address).is_some() { + replacement_address = random_addr(rng); + } + let replacement_entry = DelayedWriteBufferEntry::new(replacement_address)?; + let return_entry; + // overwrite the buffer entry in memory to this random address, if address was in dwb. + // otherwise, write to the dummy entry at beginning of vec + if let Some(entry_idx) = matched_entry { + let entry = self.entries[entry_idx]; + return_entry = Some(entry.clone()); + safe_add(&mut balance, entry.amount()? as u128); + self.entries[entry_idx] = replacement_entry; + } else { + return_entry = None; + safe_add(&mut balance, 0); + // write it to dummy entry + self.write_dummy_entry(replacement_entry); + } + + Ok((balance, return_entry)) + } + + // returns matched index for a given address + // trailing zeros will be 128 if not found + pub fn recipient_match(&self, address: &CanonicalAddr) -> Option { + // for a dwb > 128 entries in size use a u256 + let mut matched_index: u128 = 0; + let address = address.as_slice(); + for (idx, entry) in self.entries.iter().enumerate().skip(1) { + let equals = fixed_time_eq(address, entry.recipient_slice()) as u128; + matched_index |= equals << idx; + } + match matched_index.trailing_zeros() { + 128 => None, + x => Some(x as usize) + } + } + + #[inline] + pub fn write_dummy_entry(&mut self, entry: DelayedWriteBufferEntry) { + self.entries[0] = entry; + } } -impl DelayedWriteBufferElement { - pub fn new(recipient: CanonicalAddr) -> Self { - Self { - recipient, - amount: 0, - list_len: 0, - head_node: None, +const U64_BYTES: usize = 8; + +const DWB_RECIPIENT_BYTES: usize = 20; +const DWB_AMOUNT_BYTES: usize = 8; // Max 16 (u128) +const DWB_HEAD_NODE_BYTES: usize = 5; // Max 8 (u64) +const DWB_LIST_LEN_BYTES: usize = 1; + +const DWB_ENTRY_BYTES: usize = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES + DWB_LIST_LEN_BYTES; + +/// A delayed write buffer entry consists of the following bytes in this order: +/// +/// // recipient canonical address +/// recipient - 20 bytes +/// // for sscrt w/ 6 decimals u64 is good for > 18 trillion tokens, far exceeding supply +/// // change to 16 bytes (u128) or other size for tokens with more decimals/higher supply +/// amount - 8 bytes (u64) +/// // global id for head of linked list of transaction nodes +/// // 40 bits allows for over 1 trillion transactions +/// head_node - 5 bytes +/// // length of list (limited to 255) +/// list_len - 1 byte +/// +/// total: 34 bytes +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +pub struct DelayedWriteBufferEntry( + #[serde(with = "BigArray")] + [u8; DWB_ENTRY_BYTES] +); + +impl DelayedWriteBufferEntry { + pub fn new(recipient: CanonicalAddr) -> StdResult { + let recipient = recipient.as_slice(); + if recipient.len() != DWB_RECIPIENT_BYTES { + return Err(StdError::generic_err("dwb: invalid recipient length")); + } + let mut result = [0u8;DWB_ENTRY_BYTES]; + result[..DWB_RECIPIENT_BYTES].copy_from_slice(recipient); + Ok(Self { + 0: result + }) + } + + fn recipient_slice(&self) -> &[u8] { + &self.0[..DWB_RECIPIENT_BYTES] + } + + fn recipient(&self) -> StdResult { + let result = CanonicalAddr::try_from(self.recipient_slice()) + .or(Err(StdError::generic_err("Get dwb recipient error")))?; + Ok(result) + } + + fn set_recipient(&mut self, val: &CanonicalAddr) -> StdResult<()> { + let val_slice = val.as_slice(); + if val_slice.len() != DWB_RECIPIENT_BYTES { + return Err(StdError::generic_err("Set dwb recipient error")); + } + self.0[..DWB_RECIPIENT_BYTES].copy_from_slice(val_slice); + Ok(()) + } + + fn amount(&self) -> StdResult { + let start = DWB_RECIPIENT_BYTES; + let end = start + DWB_AMOUNT_BYTES; + let amount_slice = &self.0[start..end]; + let result = amount_slice + .try_into() + .or(Err(StdError::generic_err("Get dwb amount error")))?; + Ok(u64::from_be_bytes(result)) + } + + fn set_amount(&mut self, val: u64) -> StdResult<()> { + let start = DWB_RECIPIENT_BYTES; + let end = start + DWB_AMOUNT_BYTES; + if DWB_AMOUNT_BYTES != U64_BYTES { + return Err(StdError::generic_err("Set dwb amount error")); } + self.0[start..end].copy_from_slice(&val.to_be_bytes()); + Ok(()) + } + + fn head_node(&self) -> StdResult { + let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES; + let end = start + DWB_HEAD_NODE_BYTES; + let head_node_slice = &self.0[start..end]; + let mut result = [0u8; U64_BYTES]; + if DWB_HEAD_NODE_BYTES > U64_BYTES { + return Err(StdError::generic_err("Get dwb head node error")); + } + result[U64_BYTES - DWB_HEAD_NODE_BYTES..].copy_from_slice(head_node_slice); + Ok(u64::from_be_bytes(result)) + } + + fn set_head_node(&mut self, val: u64) -> StdResult<()> { + let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES; + let end = start + DWB_HEAD_NODE_BYTES; + let val_bytes = &val.to_be_bytes()[U64_BYTES - DWB_HEAD_NODE_BYTES..]; + if val_bytes.len() != DWB_HEAD_NODE_BYTES { + return Err(StdError::generic_err("Set dwb head node error")); + } + self.0[start..end].copy_from_slice(val_bytes); + Ok(()) + } + + fn list_len(&self) -> u8 { + let pos = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES; + self.0[pos] + } + + fn set_list_len(&mut self, val: u8) { + let pos = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES; + self.0[pos] = val; } pub fn add_tx_node(&mut self, store: &mut dyn Storage, tx_id: u64, add_tx_amount: Option) -> StdResult<()> { - if let Some(head_node) = self.head_node { - let tx_node = TxNode { + let tx_node; + let head_node = self.head_node()?; + if head_node > 0 { + tx_node = TxNode { tx_id, next: Some(head_node), }; - TX_NODES.push(store, &tx_node)?; - let new_head_node = TX_NODES.get_len(store)? - 1; - self.head_node = Some(new_head_node); - self.list_len = self.list_len + 1; } else { - let tx_node = TxNode { + tx_node = TxNode { tx_id, next: None, }; - TX_NODES.push(store, &tx_node)?; - let head_node = TX_NODES.get_len(store)? - 1; - self.head_node = Some(head_node); - self.list_len = 1; } + + // store the new node on chain + let new_node = store_new_tx_node(store, tx_node)?; + // set the head node to the new node id + self.set_head_node(new_node)?; + // increment the node list length + self.set_list_len(self.list_len() + 1); if let Some(add_tx_amount) = add_tx_amount { - self.amount = self.amount.saturating_add(add_tx_amount); + // change this to safe_add if your coin needs to store amount in buffer as u128 (e.g. 18 decimals) + let mut amount = self.amount()?; + let add_tx_amount_u64 = add_tx_amount + .try_into() + .or_else(|_| return Err(StdError::generic_err("dwb: deposit overflow")))?; + safe_add_u64(&mut amount, add_tx_amount_u64); + self.set_amount(amount)?; } Ok(()) @@ -83,16 +330,16 @@ pub struct TxNode { /// transaction id in the TRANSACTIONS list pub tx_id: u64, /// TX_NODES idx - pointer to the next node in the linked list - pub next: Option, + pub next: Option, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TxBundle { /// TX_NODES idx - pointer to the head tx node in the linked list - pub head_node: u32, + pub head_node: u64, /// length of the tx node linked list for this element - pub list_len: u32, + pub list_len: u8, /// offset of the first tx of this bundle in the history of txs for the account (for pagination) pub offset: u32, } @@ -109,8 +356,8 @@ pub static ACCOUNT_TX_COUNT: Item = Item::new(KEY_ACCOUNT_TX_COUNT); pub struct AccountTxsStore {} impl AccountTxsStore { - /// appends a new tx bundle for an account when tx occurs or is settled. - pub fn append_bundle(store: &mut dyn Storage, account: &CanonicalAddr, head_node: u32, list_len: u32) -> StdResult<()> { + /// appends a new tx bundle for an account, called when non-transfer tx occurs or is settled. + pub fn append_bundle(store: &mut dyn Storage, account: &CanonicalAddr, head_node: u64, list_len: u8) -> StdResult<()> { let account_txs_store = ACCOUNT_TXS.add_suffix(account.as_slice()); let account_txs_len = account_txs_store.get_len(store)?; let tx_bundle; @@ -120,7 +367,7 @@ impl AccountTxsStore { tx_bundle = TxBundle { head_node, list_len, - offset: last_tx_bundle.offset + last_tx_bundle.list_len, + offset: last_tx_bundle.offset + u32::from(last_tx_bundle.list_len), }; } else { // this is the first bundle for the account tx_bundle = TxBundle { @@ -133,7 +380,7 @@ impl AccountTxsStore { // update the total count of txs for account let account_tx_count_store = ACCOUNT_TX_COUNT.add_suffix(account.as_slice()); let account_tx_count = account_tx_count_store.may_load(store)?.unwrap_or_default(); - account_tx_count_store.save(store, &(account_tx_count.saturating_add(list_len)))?; + account_tx_count_store.save(store, &(account_tx_count.saturating_add(u32::from(list_len))))?; account_txs_store.push(store, &tx_bundle) } @@ -149,7 +396,7 @@ impl AccountTxsStore { while left <= right { let mid = (left + right) / 2; let mid_bundle = account_txs_store.get_at(store, mid)?; - if start_idx >= mid_bundle.offset && start_idx < mid_bundle.offset + mid_bundle.list_len { + if start_idx >= mid_bundle.offset && start_idx < mid_bundle.offset + u32::from(mid_bundle.list_len) { // we have the correct bundle return Ok(Some((mid, mid_bundle))); } else if start_idx < mid_bundle.offset { diff --git a/src/state.rs b/src/state.rs index 34393ed4..09abb2de 100644 --- a/src/state.rs +++ b/src/state.rs @@ -119,10 +119,23 @@ pub fn safe_add(balance: &mut u128, amount: u128) -> u128 { *balance - prev_balance } +// To avoid balance guessing attacks based on balance overflow we need to perform safe addition and don't expose overflows to the caller. +// Assuming that max of u64 is probably an unreachable balance, we want the addition to be bounded the max of u64 +// Currently the logic here is very straight forward yet the existence of the function is mandatory for future changes if needed. +pub fn safe_add_u64(balance: &mut u64, amount: u64) -> u64 { + // Note that new_amount can be equal to base after this operation. + // Currently we do nothing maybe on other implementations we will have something to add here + let prev_balance: u64 = *balance; + *balance = balance.saturating_add(amount); + + // Won't underflow as the minimal value possible is 0 + *balance - prev_balance +} + pub static BALANCES: Item = Item::new(PREFIX_BALANCES); pub struct BalancesStore {} impl BalancesStore { - fn save(store: &mut dyn Storage, account: &CanonicalAddr, amount: u128) -> StdResult<()> { + pub fn save(store: &mut dyn Storage, account: &CanonicalAddr, amount: u128) -> StdResult<()> { let balances = BALANCES.add_suffix(account.as_slice()); balances.save(store, &amount) } diff --git a/src/transaction_history.rs b/src/transaction_history.rs index c5746d6d..403f98f0 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -296,7 +296,8 @@ pub fn append_new_stored_tx( memo: Option, block: &BlockInfo, ) -> StdResult { - let id = TX_COUNT.load(store).unwrap_or_default(); + // tx ids are serialized starting at 1 + let serial_id = TX_COUNT.load(store).unwrap_or_default() + 1; let stored_tx = StoredTx { action: action.clone(), amount, @@ -305,9 +306,9 @@ pub fn append_new_stored_tx( block_height: block.height, }; - TRANSACTIONS.add_suffix(&id.to_be_bytes()).save(store, &stored_tx)?; - TX_COUNT.save(store, &(id+1))?; - Ok(id) + TRANSACTIONS.add_suffix(&serial_id.to_be_bytes()).save(store, &stored_tx)?; + TX_COUNT.save(store, &(serial_id))?; + Ok(serial_id) } #[allow(clippy::too_many_arguments)] // We just need them From e84160cf097488c67723a5aa1ec18f55a2967bb4 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 23 May 2024 19:20:57 +1200 Subject: [PATCH 12/87] remove condition in constant_time_release --- src/dwb.rs | 73 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/src/dwb.rs b/src/dwb.rs index ef5a62ad..6610429c 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -117,6 +117,7 @@ impl DelayedWriteBuffer { } /// "releases" a given recipient from the buffer, removing their entry if one exists, in constant-time + /// returns the new balance and the buffer entry pub fn constant_time_release( &mut self, store: &mut dyn Storage, @@ -127,36 +128,34 @@ impl DelayedWriteBuffer { let mut balance = BalancesStore::load(store, address); // locate the position of the entry in the buffer - let matched_entry = self.recipient_match(address); + let matched_entry_idx = self.recipient_match(address); // produce a new random address let mut replacement_address = random_addr(rng); // ensure random addr is not already in dwb (extremely unlikely!!) - while self.recipient_match(&replacement_address).is_some() { + while self.recipient_match(&replacement_address) > 0 { replacement_address = random_addr(rng); } let replacement_entry = DelayedWriteBufferEntry::new(replacement_address)?; - let return_entry; - // overwrite the buffer entry in memory to this random address, if address was in dwb. - // otherwise, write to the dummy entry at beginning of vec - if let Some(entry_idx) = matched_entry { - let entry = self.entries[entry_idx]; - return_entry = Some(entry.clone()); - safe_add(&mut balance, entry.amount()? as u128); - self.entries[entry_idx] = replacement_entry; - } else { - return_entry = None; - safe_add(&mut balance, 0); - // write it to dummy entry - self.write_dummy_entry(replacement_entry); - } - Ok((balance, return_entry)) + // get the current entry at the matched index (0 if dummy) + let entry = self.entries[matched_entry_idx]; + // add entry amount to the stored balance for the address (will be 0 if dummy) + safe_add(&mut balance, entry.amount()? as u128); + // overwrite the entry idx with random addr replacement + self.entries[matched_entry_idx] = replacement_entry; + + match matched_entry_idx { + // no match + 0 => Ok((balance, None)), + // otherwise return the updated balance and entry + x => Ok((balance, Some(entry))) + } } // returns matched index for a given address - // trailing zeros will be 128 if not found - pub fn recipient_match(&self, address: &CanonicalAddr) -> Option { + // trailing zeros will be 128 if not found, so return 0 (dummy entry) + pub fn recipient_match(&self, address: &CanonicalAddr) -> usize { // for a dwb > 128 entries in size use a u256 let mut matched_index: u128 = 0; let address = address.as_slice(); @@ -165,15 +164,11 @@ impl DelayedWriteBuffer { matched_index |= equals << idx; } match matched_index.trailing_zeros() { - 128 => None, - x => Some(x as usize) + 128 => 0, + x => x as usize } } - #[inline] - pub fn write_dummy_entry(&mut self, entry: DelayedWriteBufferEntry) { - self.entries[0] = entry; - } } const U64_BYTES: usize = 8; @@ -200,6 +195,7 @@ const DWB_ENTRY_BYTES: usize = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD /// /// total: 34 bytes #[derive(Serialize, Deserialize, Clone, Copy, Debug)] +#[cfg_attr(test, derive(Eq, PartialEq))] pub struct DelayedWriteBufferEntry( #[serde(with = "BigArray")] [u8; DWB_ENTRY_BYTES] @@ -211,7 +207,7 @@ impl DelayedWriteBufferEntry { if recipient.len() != DWB_RECIPIENT_BYTES { return Err(StdError::generic_err("dwb: invalid recipient length")); } - let mut result = [0u8;DWB_ENTRY_BYTES]; + let mut result = [0u8; DWB_ENTRY_BYTES]; result[..DWB_RECIPIENT_BYTES].copy_from_slice(recipient); Ok(Self { 0: result @@ -408,4 +404,29 @@ impl AccountTxsStore { Ok(None) } +} + + +#[cfg(test)] +mod tests { + use std::any::Any; + + use cosmwasm_std::{testing::*, Api}; + use cosmwasm_std::{ + from_binary, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, + SubMsg, Timestamp, TransactionInfo, WasmMsg, + }; + use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; + + use crate::msg::ResponseStatus; + use crate::msg::{InitConfig, InitialBalance}; + + use super::*; + + #[test] + fn test_dwb_entry_setters_getters() { + let recipient = CanonicalAddr::from(ZERO_ADDR); + let dwb_entry = DelayedWriteBufferEntry::new(recipient).unwrap(); + assert_eq!(dwb_entry, DelayedWriteBufferEntry([0u8; DWB_ENTRY_BYTES])); + } } \ No newline at end of file From d396dcd026f2530884380e2584df7c43dc74a927 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 15:08:37 +1200 Subject: [PATCH 13/87] fix constant time operations for saturated dwb --- src/contract.rs | 59 +++++++++++------- src/dwb.rs | 155 ++++++++++++++++++++++++++---------------------- 2 files changed, 124 insertions(+), 90 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index e61addc1..bc6baa66 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -10,7 +10,7 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{AccountTxsStore, DelayedWriteBuffer, DelayedWriteBufferEntry, TxNode, DWB, DWB_LEN, TX_NODES}; +use crate::dwb::{random_in_range, AccountTxsStore, DelayedWriteBuffer, DelayedWriteBufferEntry, TxNode, DWB, DWB_LEN, TX_NODES}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -18,7 +18,7 @@ use crate::msg::{ }; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, PRNG, TOTAL_SUPPLY + safe_add, safe_add_u64, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, PRNG, TOTAL_SUPPLY }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ @@ -1868,34 +1868,53 @@ fn perform_transfer( let mut dwb = DWB.load(store)?; // settle the owner's account - dwb.settle_account(store, rng, from, tx_id, amount)?; + dwb.settle_sender_or_owner_account(store, rng, from, tx_id, amount)?; // if this is a *_from action, settle the sender's account, too if sender != from { - dwb.settle_account(store, rng, sender, tx_id, 0)?; + dwb.settle_sender_or_owner_account(store, rng, sender, tx_id, 0)?; } - // check if `to` is already a recipient in the delayed write buffer - let buffer_match = dwb.recipient_match(&to); - if let Some(idx) = buffer_match { - // if it is, update the amount for the entry and add a new tx node at the head - let mut entry = dwb.entries[idx]; - entry.add_tx_node(store, tx_id, Some(amount))?; - dwb.entries[idx] = entry; - } else { - // create a new entry - let mut new_entry = DelayedWriteBufferEntry::new(to.clone())?; - new_entry.add_tx_node(store, tx_id, Some(amount))?; - } + if dwb.saturated() { + // check if `to` is already a recipient in the delayed write buffer + let recipient_index = dwb.recipient_match(&to); - // if it is in the delayed write buffer + // this will either be a prior entry for the recipient or the dummy entry + let mut new_entry = dwb.entries[recipient_index].clone(); + new_entry.set_recipient(&to)?; + new_entry.add_tx_node(store, tx_id)?; + new_entry.add_amount(amount)?; - if dwb.saturated() { + // if recipient is in the buffer (non-zero index), set this value to 1, otherwise 0, in constant-time + // casting will never overflow, so long as dwb length is limited to a u16 value + let zero_or_one = (((recipient_index as isize | -(recipient_index as isize)) >> 31) & 1) as usize; - } else { - // we want to simply add to the buffer without settling anything + // randomly pick an entry to exclude in case the recipient is not in the buffer + let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; + + // index of entry to exclude from selection + let exclude_index = (recipient_index as usize * zero_or_one) + (random_exclude_index * (1 - zero_or_one)); + + // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) + let settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; + // settle the entry + dwb.settle_entry(store, settle_index)?; + + // replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer + let replacement_entry = dwb.unique_random_entry(rng)?; + dwb.entries[settle_index] = replacement_entry; + + // pick the index to where the recipient's entry should be written + let write_index = (recipient_index * zero_or_one) + (settle_index * (1 - zero_or_one)); + + // either updates the existing recipient entry, or overwrites the random replacement entry in the settled index + dwb.entries[write_index] = new_entry; + } else { + // TODO: } + + // TODO: BalancesStore::update_balance( store, diff --git a/src/dwb.rs b/src/dwb.rs index 6610429c..bdce4869 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; use crypto::util::fixed_time_eq; +use rand::RngCore; use schemars::JsonSchema; use secret_toolkit_crypto::{sha_256, ContractPrng}; use serde::{Deserialize, Serialize}; @@ -32,6 +33,7 @@ fn store_new_tx_node(store: &mut dyn Storage, tx_node: TxNode) -> StdResult pub const ZERO_ADDR: [u8; 20] = [0u8; 20]; // 64 entries + 1 "dummy" entry prepended (idx: 0 in DelayedWriteBufferEntry array) +// minimum allowable size: 3 pub const DWB_LEN: u16 = 65; #[derive(Serialize, Deserialize, Debug)] @@ -46,6 +48,15 @@ fn random_addr(rng: &mut ContractPrng) -> CanonicalAddr { CanonicalAddr::from(&rng.rand_bytes()[0..20]) } +pub fn random_in_range(rng: &mut ContractPrng, a: u32, b: u32) -> StdResult { + if b <= a { + return Err(StdError::generic_err("invalid range")); + } + let range_size = b - a + 1; + let random_u32 = rng.next_u32() % range_size; + Ok(random_u32 + a) +} + impl DelayedWriteBuffer { pub fn new() -> StdResult { Ok(Self { @@ -62,8 +73,31 @@ impl DelayedWriteBuffer { self.empty_space_counter == 0 } + /// settles an entry at a given index in the buffer + pub fn settle_entry( + &mut self, + store: &mut dyn Storage, + index: usize, + ) -> StdResult<()> { + let entry = self.entries[index]; + let account = entry.recipient()?; + + AccountTxsStore::append_bundle( + store, + &account, + entry.head_node()?, + entry.list_len() + ); + + // get the address' stored balance + let mut balance = BalancesStore::load(store, &account); + safe_add(&mut balance, entry.amount()? as u128); + // add the amount from entry to the stored balance + BalancesStore::save(store, &account, balance) + } + /// settles a participant's account who may or may not have an entry in the buffer - pub fn settle_account( + pub fn settle_sender_or_owner_account( &mut self, store: &mut dyn Storage, rng: &mut ContractPrng, @@ -72,37 +106,20 @@ impl DelayedWriteBuffer { amount_spent: u128, ) -> StdResult<()> { // release the address from the buffer - let (balance, entry_opt) = self.constant_time_release( + let (balance, mut entry) = self.constant_time_release( store, rng, address )?; - - if let Some(mut entry) = entry_opt { - // add tx at the head before adding new tx bundle for account history - entry.add_tx_node(store, tx_id, None)?; - AccountTxsStore::append_bundle( - store, - address, - entry.head_node()?, - entry.list_len() - )?; - } else { - // no entry in dwb, so we just create a single node and - // push it as a bundle in the account tx history - let tx_node = TxNode { - tx_id, - next: None, - }; - let head_node = store_new_tx_node(store, tx_node)?; - AccountTxsStore::append_bundle( - store, - address, - head_node, - 1 - )?; - } + let head_node = entry.add_tx_node(store, tx_id)?; + + AccountTxsStore::append_bundle( + store, + address, + head_node, + entry.list_len(), + )?; let new_balance = if let Some(balance_after_sub) = balance.checked_sub(amount_spent) { balance_after_sub @@ -123,20 +140,14 @@ impl DelayedWriteBuffer { store: &mut dyn Storage, rng: &mut ContractPrng, address: &CanonicalAddr - ) -> StdResult<(u128, Option)> { + ) -> StdResult<(u128, DelayedWriteBufferEntry)> { // get the address' stored balance let mut balance = BalancesStore::load(store, address); // locate the position of the entry in the buffer let matched_entry_idx = self.recipient_match(address); - // produce a new random address - let mut replacement_address = random_addr(rng); - // ensure random addr is not already in dwb (extremely unlikely!!) - while self.recipient_match(&replacement_address) > 0 { - replacement_address = random_addr(rng); - } - let replacement_entry = DelayedWriteBufferEntry::new(replacement_address)?; + let replacement_entry = self.unique_random_entry(rng)?; // get the current entry at the matched index (0 if dummy) let entry = self.entries[matched_entry_idx]; @@ -145,12 +156,17 @@ impl DelayedWriteBuffer { // overwrite the entry idx with random addr replacement self.entries[matched_entry_idx] = replacement_entry; - match matched_entry_idx { - // no match - 0 => Ok((balance, None)), - // otherwise return the updated balance and entry - x => Ok((balance, Some(entry))) + Ok((balance, entry)) + } + + pub fn unique_random_entry(&self, rng: &mut ContractPrng) -> StdResult { + // produce a new random address + let mut replacement_address = random_addr(rng); + // ensure random addr is not already in dwb (extremely unlikely!!) + while self.recipient_match(&replacement_address) > 0 { + replacement_address = random_addr(rng); } + DelayedWriteBufferEntry::new(replacement_address) } // returns matched index for a given address @@ -224,7 +240,7 @@ impl DelayedWriteBufferEntry { Ok(result) } - fn set_recipient(&mut self, val: &CanonicalAddr) -> StdResult<()> { + pub fn set_recipient(&mut self, val: &CanonicalAddr) -> StdResult<()> { let val_slice = val.as_slice(); if val_slice.len() != DWB_RECIPIENT_BYTES { return Err(StdError::generic_err("Set dwb recipient error")); @@ -233,7 +249,7 @@ impl DelayedWriteBufferEntry { Ok(()) } - fn amount(&self) -> StdResult { + pub fn amount(&self) -> StdResult { let start = DWB_RECIPIENT_BYTES; let end = start + DWB_AMOUNT_BYTES; let amount_slice = &self.0[start..end]; @@ -253,7 +269,7 @@ impl DelayedWriteBufferEntry { Ok(()) } - fn head_node(&self) -> StdResult { + pub fn head_node(&self) -> StdResult { let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES; let end = start + DWB_HEAD_NODE_BYTES; let head_node_slice = &self.0[start..end]; @@ -276,7 +292,7 @@ impl DelayedWriteBufferEntry { Ok(()) } - fn list_len(&self) -> u8 { + pub fn list_len(&self) -> u8 { let pos = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES; self.0[pos] } @@ -286,20 +302,13 @@ impl DelayedWriteBufferEntry { self.0[pos] = val; } - pub fn add_tx_node(&mut self, store: &mut dyn Storage, tx_id: u64, add_tx_amount: Option) -> StdResult<()> { - let tx_node; - let head_node = self.head_node()?; - if head_node > 0 { - tx_node = TxNode { - tx_id, - next: Some(head_node), - }; - } else { - tx_node = TxNode { - tx_id, - next: None, - }; - } + /// adds a tx node to the linked list + /// returns: the new head node + pub fn add_tx_node(&mut self, store: &mut dyn Storage, tx_id: u64) -> StdResult { + let tx_node = TxNode { + tx_id, + next: self.head_node()?, + }; // store the new node on chain let new_node = store_new_tx_node(store, tx_node)?; @@ -307,18 +316,23 @@ impl DelayedWriteBufferEntry { self.set_head_node(new_node)?; // increment the node list length self.set_list_len(self.list_len() + 1); - if let Some(add_tx_amount) = add_tx_amount { - // change this to safe_add if your coin needs to store amount in buffer as u128 (e.g. 18 decimals) - let mut amount = self.amount()?; - let add_tx_amount_u64 = add_tx_amount - .try_into() - .or_else(|_| return Err(StdError::generic_err("dwb: deposit overflow")))?; - safe_add_u64(&mut amount, add_tx_amount_u64); - self.set_amount(amount)?; - } + + Ok(new_node) + } - Ok(()) - } + // adds some amount to the total amount for all txs in the entry linked list + // returns: the new amount + pub fn add_amount(&mut self, add_tx_amount: u128) -> StdResult { + // change this to safe_add if your coin needs to store amount in buffer as u128 (e.g. 18 decimals) + let mut amount = self.amount()?; + let add_tx_amount_u64 = add_tx_amount + .try_into() + .or_else(|_| return Err(StdError::generic_err("dwb: deposit overflow")))?; + safe_add_u64(&mut amount, add_tx_amount_u64); + self.set_amount(amount)?; + + Ok(amount) + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -326,7 +340,8 @@ pub struct TxNode { /// transaction id in the TRANSACTIONS list pub tx_id: u64, /// TX_NODES idx - pointer to the next node in the linked list - pub next: Option, + /// 0 if next is null + pub next: u64, } From 7fd21bbce11a7ca09955e3e965df663c6a40779a Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 15:11:21 +1200 Subject: [PATCH 14/87] cleanup --- src/contract.rs | 10 ++++------ src/dwb.rs | 9 +++------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index bc6baa66..3dde43dc 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -3,26 +3,24 @@ use cosmwasm_std::{ entry_point, to_binary, Addr, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 }; -use crypto::buffer; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{random_in_range, AccountTxsStore, DelayedWriteBuffer, DelayedWriteBufferEntry, TxNode, DWB, DWB_LEN, TX_NODES}; +use crate::dwb::{random_in_range, DelayedWriteBuffer, DWB, DWB_LEN}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, - TxWithCoins, }; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, safe_add_u64, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, PRNG, TOTAL_SUPPLY + safe_add, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, PRNG, TOTAL_SUPPLY }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - append_new_stored_tx, store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredTx, StoredTxAction, TxAction + append_new_stored_tx, store_burn, store_deposit, store_mint, store_redeem, StoredTxAction, }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -1980,7 +1978,7 @@ mod tests { }; use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; - use crate::msg::ResponseStatus; + use crate::msg::{ResponseStatus, TxWithCoins}; use crate::msg::{InitConfig, InitialBalance}; use super::*; diff --git a/src/dwb.rs b/src/dwb.rs index bdce4869..5d6e4689 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,15 +1,12 @@ -use std::collections::{HashMap, HashSet}; - use crypto::util::fixed_time_eq; use rand::RngCore; -use schemars::JsonSchema; -use secret_toolkit_crypto::{sha_256, ContractPrng}; +use secret_toolkit_crypto::ContractPrng; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; use secret_toolkit::storage::{AppendStore, Item}; -use crate::{state::{safe_add, safe_add_u64, BalancesStore, BALANCES}, transaction_history::Tx}; +use crate::state::{safe_add, safe_add_u64, BalancesStore,}; pub const KEY_DWB: &[u8] = b"dwb"; pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt"; @@ -87,7 +84,7 @@ impl DelayedWriteBuffer { &account, entry.head_node()?, entry.list_len() - ); + )?; // get the address' stored balance let mut balance = BalancesStore::load(store, &account); From 18a0540fb21c21aa5f222be9a05198f62dfaf152 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 16:07:15 +1200 Subject: [PATCH 15/87] fix random range --- src/contract.rs | 4 ++-- src/dwb.rs | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 3dde43dc..ec118263 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1876,14 +1876,14 @@ fn perform_transfer( // check if `to` is already a recipient in the delayed write buffer let recipient_index = dwb.recipient_match(&to); - // this will either be a prior entry for the recipient or the dummy entry + // the new entry will either derive from a prior entry for the recipient or the dummy entry let mut new_entry = dwb.entries[recipient_index].clone(); new_entry.set_recipient(&to)?; new_entry.add_tx_node(store, tx_id)?; new_entry.add_amount(amount)?; // if recipient is in the buffer (non-zero index), set this value to 1, otherwise 0, in constant-time - // casting will never overflow, so long as dwb length is limited to a u16 value + // casting to isize will never overflow, so long as dwb length is limited to a u16 value let zero_or_one = (((recipient_index as isize | -(recipient_index as isize)) >> 31) & 1) as usize; // randomly pick an entry to exclude in case the recipient is not in the buffer diff --git a/src/dwb.rs b/src/dwb.rs index 5d6e4689..0bb492e4 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -49,7 +49,7 @@ pub fn random_in_range(rng: &mut ContractPrng, a: u32, b: u32) -> StdResult if b <= a { return Err(StdError::generic_err("invalid range")); } - let range_size = b - a + 1; + let range_size = b - a; let random_u32 = rng.next_u32() % range_size; Ok(random_u32 + a) } @@ -167,19 +167,14 @@ impl DelayedWriteBuffer { } // returns matched index for a given address - // trailing zeros will be 128 if not found, so return 0 (dummy entry) pub fn recipient_match(&self, address: &CanonicalAddr) -> usize { - // for a dwb > 128 entries in size use a u256 - let mut matched_index: u128 = 0; + let mut matched_index: usize = 0; let address = address.as_slice(); for (idx, entry) in self.entries.iter().enumerate().skip(1) { - let equals = fixed_time_eq(address, entry.recipient_slice()) as u128; - matched_index |= equals << idx; - } - match matched_index.trailing_zeros() { - 128 => 0, - x => x as usize + let equals = fixed_time_eq(address, entry.recipient_slice()) as usize; + matched_index |= idx * equals; } + matched_index } } From 448a626e6e55e5d350812d8cdb8ff78e5e44ab53 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 16:34:07 +1200 Subject: [PATCH 16/87] fix random range for modulo bias --- src/dwb.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/dwb.rs b/src/dwb.rs index 0bb492e4..b8a492bc 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -49,9 +49,16 @@ pub fn random_in_range(rng: &mut ContractPrng, a: u32, b: u32) -> StdResult if b <= a { return Err(StdError::generic_err("invalid range")); } - let range_size = b - a; - let random_u32 = rng.next_u32() % range_size; - Ok(random_u32 + a) + let range_size = (b - a) as u64; + // need to make sure random is below threshold to prevent modulo bias + let threshold = u64::MAX - range_size; + loop { + // this loop will almost always run only once since range_size << u64::MAX + let random_u64 = rng.next_u64(); + if random_u64 < threshold { + return Ok((random_u64 % range_size) as u32 + a) + } + } } impl DelayedWriteBuffer { From 9b56d0a3283b3144c793e5a15a99bbe7e7cc356f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 18:20:17 +1200 Subject: [PATCH 17/87] make list len u16 and handle full list --- src/contract.rs | 26 +++++++++++++++++--------- src/dwb.rs | 40 ++++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index ec118263..c61f342b 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1872,10 +1872,9 @@ fn perform_transfer( dwb.settle_sender_or_owner_account(store, rng, sender, tx_id, 0)?; } + // check if `to` is already a recipient in the delayed write buffer + let recipient_index = dwb.recipient_match(&to); if dwb.saturated() { - // check if `to` is already a recipient in the delayed write buffer - let recipient_index = dwb.recipient_match(&to); - // the new entry will either derive from a prior entry for the recipient or the dummy entry let mut new_entry = dwb.entries[recipient_index].clone(); new_entry.set_recipient(&to)?; @@ -1884,31 +1883,40 @@ fn perform_transfer( // if recipient is in the buffer (non-zero index), set this value to 1, otherwise 0, in constant-time // casting to isize will never overflow, so long as dwb length is limited to a u16 value - let zero_or_one = (((recipient_index as isize | -(recipient_index as isize)) >> 31) & 1) as usize; + let recipient_in_buffer = (((recipient_index as isize | -(recipient_index as isize)) >> 31) & 1) as usize; // randomly pick an entry to exclude in case the recipient is not in the buffer let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; // index of entry to exclude from selection - let exclude_index = (recipient_index as usize * zero_or_one) + (random_exclude_index * (1 - zero_or_one)); + let exclude_index = (recipient_index as usize * recipient_in_buffer) + (random_exclude_index * (1 - recipient_in_buffer)); // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) - let settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; + let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; + + // check if we have any open slots in the linked list + let open_slots = (u16::MAX - dwb.entries[recipient_index].list_len()?) as i32; + let list_can_grow = (((open_slots | -open_slots) >> 31) & 1) as usize; + + // if we would overflow the list, just settle recipient + // TODO: see docs for attack analysis + let actual_settle_index = (random_settle_index as usize * list_can_grow) + (recipient_index * (1 - list_can_grow)); // settle the entry - dwb.settle_entry(store, settle_index)?; + dwb.settle_entry(store, actual_settle_index)?; // replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer let replacement_entry = dwb.unique_random_entry(rng)?; - dwb.entries[settle_index] = replacement_entry; + dwb.entries[actual_settle_index] = replacement_entry; // pick the index to where the recipient's entry should be written - let write_index = (recipient_index * zero_or_one) + (settle_index * (1 - zero_or_one)); + let write_index = (recipient_index * recipient_in_buffer) + (actual_settle_index * (1 - recipient_in_buffer)); // either updates the existing recipient entry, or overwrites the random replacement entry in the settled index dwb.entries[write_index] = new_entry; } else { // TODO: + } diff --git a/src/dwb.rs b/src/dwb.rs index b8a492bc..5ec436cb 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -90,7 +90,7 @@ impl DelayedWriteBuffer { store, &account, entry.head_node()?, - entry.list_len() + entry.list_len()?, )?; // get the address' stored balance @@ -122,7 +122,7 @@ impl DelayedWriteBuffer { store, address, head_node, - entry.list_len(), + entry.list_len()?, )?; let new_balance = if let Some(balance_after_sub) = balance.checked_sub(amount_spent) { @@ -179,6 +179,7 @@ impl DelayedWriteBuffer { let address = address.as_slice(); for (idx, entry) in self.entries.iter().enumerate().skip(1) { let equals = fixed_time_eq(address, entry.recipient_slice()) as usize; + // an address can only occur once in the buffer matched_index |= idx * equals; } matched_index @@ -186,12 +187,13 @@ impl DelayedWriteBuffer { } +const U16_BYTES: usize = 2; const U64_BYTES: usize = 8; const DWB_RECIPIENT_BYTES: usize = 20; const DWB_AMOUNT_BYTES: usize = 8; // Max 16 (u128) const DWB_HEAD_NODE_BYTES: usize = 5; // Max 8 (u64) -const DWB_LIST_LEN_BYTES: usize = 1; +const DWB_LIST_LEN_BYTES: usize = 2; // u16 const DWB_ENTRY_BYTES: usize = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES + DWB_LIST_LEN_BYTES; @@ -206,9 +208,9 @@ const DWB_ENTRY_BYTES: usize = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD /// // 40 bits allows for over 1 trillion transactions /// head_node - 5 bytes /// // length of list (limited to 255) -/// list_len - 1 byte +/// list_len - 2 byte /// -/// total: 34 bytes +/// total: 35 bytes #[derive(Serialize, Deserialize, Clone, Copy, Debug)] #[cfg_attr(test, derive(Eq, PartialEq))] pub struct DelayedWriteBufferEntry( @@ -291,14 +293,24 @@ impl DelayedWriteBufferEntry { Ok(()) } - pub fn list_len(&self) -> u8 { - let pos = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES; - self.0[pos] + pub fn list_len(&self) -> StdResult { + let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES; + let end = start + DWB_LIST_LEN_BYTES; + let list_len_slice = &self.0[start..end]; + let result = list_len_slice + .try_into() + .or(Err(StdError::generic_err("Get dwb list len error")))?; + Ok(u16::from_be_bytes(result)) } - fn set_list_len(&mut self, val: u8) { - let pos = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES; - self.0[pos] = val; + fn set_list_len(&mut self, val: u16) -> StdResult<()> { + let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES; + let end = start + DWB_LIST_LEN_BYTES; + if DWB_LIST_LEN_BYTES != U16_BYTES { + return Err(StdError::generic_err("Set dwb amount error")); + } + self.0[start..end].copy_from_slice(&val.to_be_bytes()); + Ok(()) } /// adds a tx node to the linked list @@ -314,7 +326,7 @@ impl DelayedWriteBufferEntry { // set the head node to the new node id self.set_head_node(new_node)?; // increment the node list length - self.set_list_len(self.list_len() + 1); + self.set_list_len(self.list_len()? + 1)?; Ok(new_node) } @@ -349,7 +361,7 @@ pub struct TxBundle { /// TX_NODES idx - pointer to the head tx node in the linked list pub head_node: u64, /// length of the tx node linked list for this element - pub list_len: u8, + pub list_len: u16, /// offset of the first tx of this bundle in the history of txs for the account (for pagination) pub offset: u32, } @@ -367,7 +379,7 @@ pub static ACCOUNT_TX_COUNT: Item = Item::new(KEY_ACCOUNT_TX_COUNT); pub struct AccountTxsStore {} impl AccountTxsStore { /// appends a new tx bundle for an account, called when non-transfer tx occurs or is settled. - pub fn append_bundle(store: &mut dyn Storage, account: &CanonicalAddr, head_node: u64, list_len: u8) -> StdResult<()> { + pub fn append_bundle(store: &mut dyn Storage, account: &CanonicalAddr, head_node: u64, list_len: u16) -> StdResult<()> { let account_txs_store = ACCOUNT_TXS.add_suffix(account.as_slice()); let account_txs_len = account_txs_store.get_len(store)?; let tx_bundle; From 338fc1a58d30b57dd6fffa6d559d40f4168a5031 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 26 May 2024 09:13:26 +1200 Subject: [PATCH 18/87] dev: unsaturated buffer handling --- src/contract.rs | 43 ++++++++++++++++++++++++------------------- src/dwb.rs | 27 ++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index c61f342b..6af9ea0f 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1874,17 +1874,18 @@ fn perform_transfer( // check if `to` is already a recipient in the delayed write buffer let recipient_index = dwb.recipient_match(&to); - if dwb.saturated() { - // the new entry will either derive from a prior entry for the recipient or the dummy entry - let mut new_entry = dwb.entries[recipient_index].clone(); - new_entry.set_recipient(&to)?; - new_entry.add_tx_node(store, tx_id)?; - new_entry.add_amount(amount)?; - // if recipient is in the buffer (non-zero index), set this value to 1, otherwise 0, in constant-time - // casting to isize will never overflow, so long as dwb length is limited to a u16 value - let recipient_in_buffer = (((recipient_index as isize | -(recipient_index as isize)) >> 31) & 1) as usize; + // the new entry will either derive from a prior entry for the recipient or the dummy entry + let mut new_entry = dwb.entries[recipient_index].clone(); + new_entry.set_recipient(&to)?; + new_entry.add_tx_node(store, tx_id)?; + new_entry.add_amount(amount)?; + + // if recipient is in the buffer (non-zero index), set this value to 1, otherwise 0, in constant-time + // casting to isize will never overflow, so long as dwb length is limited to a u16 value + let recipient_in_buffer = (((recipient_index as isize | -(recipient_index as isize)) >> 31) & 1) as usize; + if dwb.saturated() { // randomly pick an entry to exclude in case the recipient is not in the buffer let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; @@ -1915,20 +1916,24 @@ fn perform_transfer( // either updates the existing recipient entry, or overwrites the random replacement entry in the settled index dwb.entries[write_index] = new_entry; } else { - // TODO: + // TODO: revisit contract warm up with other options, e.g saturating with random address from beginning - } + // find the next empty entry in the buffer + let next_index = (DWB_LEN - dwb.empty_space_counter) as usize; + // pick the index to where the recipient's entry should be written + let write_index = (recipient_index * recipient_in_buffer) + (next_index * (1 - recipient_in_buffer)); + // either updates the existing recipient entry, or write the entry to the next index value + dwb.entries[write_index] = new_entry; - // TODO: - BalancesStore::update_balance( - store, - to, - amount, - true, - "transfer", - )?; + let empty_space_counter_delta = (1 - recipient_in_buffer) as u16; + + // decrement empty space counter if receipient is not already in buffer + dwb.empty_space_counter -= empty_space_counter_delta; + } + + DWB.save(store, &dwb)?; Ok(()) } diff --git a/src/dwb.rs b/src/dwb.rs index 5ec436cb..f1c77f64 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -29,6 +29,11 @@ fn store_new_tx_node(store: &mut dyn Storage, tx_node: TxNode) -> StdResult } pub const ZERO_ADDR: [u8; 20] = [0u8; 20]; +pub const IMPOSSIBLE_ADDR: [u8; 20] = [ + 0x29, 0xcf, 0xc6, 0x37, 0x62, 0x55, 0xa7, 0x84, 0x51, 0xee, + 0xb4, 0xb1, 0x29, 0xed, 0x8e, 0xac, 0xff, 0xa2, 0xfe, 0xef +]; + // 64 entries + 1 "dummy" entry prepended (idx: 0 in DelayedWriteBufferEntry array) // minimum allowable size: 3 pub const DWB_LEN: u16 = 65; @@ -67,7 +72,7 @@ impl DelayedWriteBuffer { empty_space_counter: DWB_LEN - 1, // first entry is a dummy entry for constant-time writing entries: [ - DelayedWriteBufferEntry::new(CanonicalAddr::from(&ZERO_ADDR))?; DWB_LEN as usize + DelayedWriteBufferEntry::new(CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; DWB_LEN as usize ] }) } @@ -207,7 +212,7 @@ const DWB_ENTRY_BYTES: usize = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD /// // global id for head of linked list of transaction nodes /// // 40 bits allows for over 1 trillion transactions /// head_node - 5 bytes -/// // length of list (limited to 255) +/// // length of list (limited to 65535) /// list_len - 2 byte /// /// total: 35 bytes @@ -452,7 +457,23 @@ mod tests { #[test] fn test_dwb_entry_setters_getters() { let recipient = CanonicalAddr::from(ZERO_ADDR); - let dwb_entry = DelayedWriteBufferEntry::new(recipient).unwrap(); + let mut dwb_entry = DelayedWriteBufferEntry::new(recipient).unwrap(); assert_eq!(dwb_entry, DelayedWriteBufferEntry([0u8; DWB_ENTRY_BYTES])); + + assert_eq!(dwb_entry.recipient().unwrap(), CanonicalAddr::from(ZERO_ADDR)); + assert_eq!(dwb_entry.amount().unwrap(), 0u64); + assert_eq!(dwb_entry.head_node().unwrap(), 0u64); + assert_eq!(dwb_entry.list_len().unwrap(), 0u16); + + let canonical_addr = CanonicalAddr::from(&[1u8; 20]); + dwb_entry.set_recipient(&canonical_addr).unwrap(); + dwb_entry.set_amount(1).unwrap(); + dwb_entry.set_head_node(1).unwrap(); + dwb_entry.set_list_len(1).unwrap(); + + assert_eq!(dwb_entry.recipient().unwrap(), CanonicalAddr::from(&[1u8; 20])); + assert_eq!(dwb_entry.amount().unwrap(), 1u64); + assert_eq!(dwb_entry.head_node().unwrap(), 1u64); + assert_eq!(dwb_entry.list_len().unwrap(), 1u16); } } \ No newline at end of file From 100dd72f80f463553dd57586bc14cc82c5763734 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 26 May 2024 09:27:36 +1200 Subject: [PATCH 19/87] query balance include dwb --- src/contract.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 6af9ea0f..248d22ab 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -603,13 +603,20 @@ pub fn query_transactions( } pub fn query_balance(deps: Deps, account: String) -> StdResult { - // Notice that if query_balance() was called by a viewking-key call, the address of 'account' + // Notice that if query_balance() was called by a viewing-key call, the address of 'account' // has already been validated. // The address of 'account' should not be validated if query_balance() was called by a permit // call, for compatibility with non-Secret addresses. let account = Addr::unchecked(account); + let account = deps.api.addr_canonicalize(account.as_str())?; - let amount = Uint128::new(BalancesStore::load(deps.storage, &deps.api.addr_canonicalize(account.as_str())?)); + let mut amount = BalancesStore::load(deps.storage, &account); + let dwb = DWB.load(deps.storage)?; + let dwb_index = dwb.recipient_match(&account); + if dwb_index > 0 { + amount = amount.saturating_add(dwb.entries[dwb_index].amount()? as u128); + } + let amount = Uint128::new(amount); let response = QueryAnswer::Balance { amount }; to_binary(&response) } @@ -1927,9 +1934,8 @@ fn perform_transfer( // either updates the existing recipient entry, or write the entry to the next index value dwb.entries[write_index] = new_entry; - let empty_space_counter_delta = (1 - recipient_in_buffer) as u16; - // decrement empty space counter if receipient is not already in buffer + let empty_space_counter_delta = (1 - recipient_in_buffer) as u16; dwb.empty_space_counter -= empty_space_counter_delta; } From 293f8afc9c0ac0033fa4b71b73e4e98bea19735e Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 26 May 2024 10:41:29 +1200 Subject: [PATCH 20/87] start on txs query --- src/contract.rs | 20 ++++++++++++++++++-- src/dwb.rs | 30 +++++++++++++++++++++++++++--- src/transaction_history.rs | 6 +++--- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 248d22ab..090d3456 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -9,7 +9,7 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{random_in_range, DelayedWriteBuffer, DWB, DWB_LEN}; +use crate::dwb::{random_in_range, DelayedWriteBuffer, DWB, DWB_LEN, TX_NODES}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -564,6 +564,22 @@ pub fn query_transactions( // The address of 'account' should not be validated if query_transactions() was called by a // permit call, for compatibility with non-Secret addresses. let account = Addr::unchecked(account); + let account_raw = deps.api.addr_canonicalize(account.as_str())?; + + // first check if there are any transactions in dwb + let dwb = DWB.load(deps.storage)?; + let dwb_index = dwb.recipient_match(&account_raw); + let mut transactions_in_dwb = vec![]; + if dwb_index > 0 && dwb.entries[dwb_index].list_len()? > 0 { + let head_node_index = dwb.entries[dwb_index].head_node()?; + if head_node_index > 0 { + let head_node = TX_NODES.add_suffix(&head_node_index.to_be_bytes()).load(deps.storage)?; + transactions_in_dwb = head_node.to_vec(deps.storage, deps.api)?; + } + } + + // second get number of txs in account storage + /* let (txs, total) = StoredTx::get_txs( @@ -603,7 +619,7 @@ pub fn query_transactions( } pub fn query_balance(deps: Deps, account: String) -> StdResult { - // Notice that if query_balance() was called by a viewing-key call, the address of 'account' + // Notice that if query_balance() was called by a viewing key call, the address of 'account' // has already been validated. // The address of 'account' should not be validated if query_balance() was called by a permit // call, for compatibility with non-Secret addresses. diff --git a/src/dwb.rs b/src/dwb.rs index f1c77f64..58513d08 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -3,10 +3,10 @@ use rand::RngCore; use secret_toolkit_crypto::ContractPrng; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; +use cosmwasm_std::{Api, CanonicalAddr, StdError, StdResult, Storage}; use secret_toolkit::storage::{AppendStore, Item}; -use crate::state::{safe_add, safe_add_u64, BalancesStore,}; +use crate::{state::{safe_add, safe_add_u64, BalancesStore,}, transaction_history::{Tx, TRANSACTIONS}}; pub const KEY_DWB: &[u8] = b"dwb"; pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt"; @@ -351,7 +351,7 @@ impl DelayedWriteBufferEntry { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] pub struct TxNode { /// transaction id in the TRANSACTIONS list pub tx_id: u64, @@ -360,6 +360,30 @@ pub struct TxNode { pub next: u64, } +impl TxNode { + // converts this and following elements in list to a vec of Tx + pub fn to_vec(&self, store: &dyn Storage, api: &dyn Api) -> StdResult> { + let mut result = vec![]; + let mut cur_node = Some(self.to_owned()); + while cur_node.is_some() { + let node = cur_node.unwrap(); + let stored_tx = TRANSACTIONS + .add_suffix(&node.tx_id.to_be_bytes()) + .load(store)?; + let tx = stored_tx.into_humanized(api, node.tx_id)?; + result.push(tx); + if node.next > 0 { + let next_node = TX_NODES.add_suffix(&node.next.to_be_bytes()).load(store)?; + cur_node = Some(next_node); + } else { + cur_node = None; + } + } + + Ok(result) + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TxBundle { diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 403f98f0..a8fcaa99 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -202,9 +202,9 @@ impl StoredTxAction { } } -// use with add_suffix tx id (u64) +// use with add_suffix tx id (u64 to_be_bytes) // does not need to be an AppendStore because we never need to iterate over global list of txs -static TRANSACTIONS: Item = Item::new(PREFIX_TXS); +pub static TRANSACTIONS: Item = Item::new(PREFIX_TXS); #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "snake_case")] @@ -233,7 +233,7 @@ impl StoredTx { } } - fn into_humanized(self, api: &dyn Api, id: u64) -> StdResult { + pub fn into_humanized(self, api: &dyn Api, id: u64) -> StdResult { Ok(Tx { id, action: self.action.into_tx_action(api)?, From 3e273f9b2dc66ca1c8c100ca52dd43c54ec548aa Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 25 May 2024 19:44:51 -0700 Subject: [PATCH 21/87] dev: handle undersaturated dwb --- src/contract.rs | 89 +++++++++++++++++++++++++++---------------------- src/dwb.rs | 3 ++ 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 090d3456..37419f33 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -9,7 +9,7 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{random_in_range, DelayedWriteBuffer, DWB, DWB_LEN, TX_NODES}; +use crate::dwb::{random_in_range, DelayedWriteBuffer, DWB, DWB_LEN, DWB_MAX_TX_EVENTS, TX_NODES}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -1904,56 +1904,57 @@ fn perform_transfer( new_entry.add_tx_node(store, tx_id)?; new_entry.add_amount(amount)?; - // if recipient is in the buffer (non-zero index), set this value to 1, otherwise 0, in constant-time - // casting to isize will never overflow, so long as dwb length is limited to a u16 value - let recipient_in_buffer = (((recipient_index as isize | -(recipient_index as isize)) >> 31) & 1) as usize; - if dwb.saturated() { - // randomly pick an entry to exclude in case the recipient is not in the buffer - let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; - - // index of entry to exclude from selection - let exclude_index = (recipient_index as usize * recipient_in_buffer) + (random_exclude_index * (1 - recipient_in_buffer)); + // whether or not recipient is in the buffer (non-zero index) + // casting to i32 will never overflow, so long as dwb length is limited to a u16 value + let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32); - // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) - let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; + // randomly pick an entry to exclude in case the recipient is not in the buffer + let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; - // check if we have any open slots in the linked list - let open_slots = (u16::MAX - dwb.entries[recipient_index].list_len()?) as i32; - let list_can_grow = (((open_slots | -open_slots) >> 31) & 1) as usize; + // index of entry to exclude from selection + let exclude_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, random_exclude_index); - // if we would overflow the list, just settle recipient - // TODO: see docs for attack analysis - let actual_settle_index = (random_settle_index as usize * list_can_grow) + (recipient_index * (1 - list_can_grow)); + // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) + let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; - // settle the entry - dwb.settle_entry(store, actual_settle_index)?; - // replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer - let replacement_entry = dwb.unique_random_entry(rng)?; - dwb.entries[actual_settle_index] = replacement_entry; + // whether or not the buffer is fully saturated yet + let if_undersaturated = constant_time_is_not_zero(dwb.empty_space_counter as i32); - // pick the index to where the recipient's entry should be written - let write_index = (recipient_index * recipient_in_buffer) + (actual_settle_index * (1 - recipient_in_buffer)); + // find the next empty entry in the buffer + let next_empty_index = (DWB_LEN - dwb.empty_space_counter) as usize; - // either updates the existing recipient entry, or overwrites the random replacement entry in the settled index - dwb.entries[write_index] = new_entry; - } else { - // TODO: revisit contract warm up with other options, e.g saturating with random address from beginning + // if buffer is not yet saturated, settle the address at the next empty index + let bounded_settle_index = constant_time_if_else(if_undersaturated, next_empty_index, random_settle_index); - // find the next empty entry in the buffer - let next_index = (DWB_LEN - dwb.empty_space_counter) as usize; - // pick the index to where the recipient's entry should be written - let write_index = (recipient_index * recipient_in_buffer) + (next_index * (1 - recipient_in_buffer)); + // check if we have any open slots in the linked list + let if_list_can_grow = constant_time_is_not_zero((DWB_MAX_TX_EVENTS - dwb.entries[recipient_index].list_len()?) as i32); - // either updates the existing recipient entry, or write the entry to the next index value - dwb.entries[write_index] = new_entry; + // if we would overflow the list, just settle recipient + // TODO: see docs for attack analysis + let actual_settle_index = constant_time_if_else(if_list_can_grow, bounded_settle_index, recipient_index); - // decrement empty space counter if receipient is not already in buffer - let empty_space_counter_delta = (1 - recipient_in_buffer) as u16; - dwb.empty_space_counter -= empty_space_counter_delta; - } + // settle the entry + dwb.settle_entry(store, actual_settle_index)?; + + // replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer + let replacement_entry = dwb.unique_random_entry(rng)?; + dwb.entries[actual_settle_index] = replacement_entry; + + // pick the index to where the recipient's entry should be written + let write_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, actual_settle_index); + + // either updates the existing recipient entry, or overwrites the random replacement entry in the settled index + dwb.entries[write_index] = new_entry; + + // decrement empty space counter if it is undersaturated and the recipient was not already in the buffer + dwb.empty_space_counter -= constant_time_if_else( + if_undersaturated, + constant_time_if_else(if_recipient_in_buffer, 0usize, 1usize), + 0usize + ) as u16; DWB.save(store, &dwb)?; @@ -1993,6 +1994,16 @@ fn is_valid_symbol(symbol: &str) -> bool { len_is_valid && symbol.bytes().all(|byte| byte.is_ascii_alphabetic()) } +#[inline] +fn constant_time_is_not_zero(value: i32) -> u32 { + return (((value | -value) >> 31) & 1) as u32; +} + +#[inline] +fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { + return (then * condition as usize) | (els * (1 - condition as usize)); +} + // pub fn migrate( // _deps: DepsMut, // _env: Env, diff --git a/src/dwb.rs b/src/dwb.rs index 58513d08..4fe39a89 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -38,6 +38,9 @@ pub const IMPOSSIBLE_ADDR: [u8; 20] = [ // minimum allowable size: 3 pub const DWB_LEN: u16 = 65; +// maximum number of tx events allowed in an entry's linked list +pub const DWB_MAX_TX_EVENTS: u16 = u16::MAX; + #[derive(Serialize, Deserialize, Debug)] pub struct DelayedWriteBuffer { pub empty_space_counter: u16, From be5d6e975c06fb5930d973d6628a09aa9d82b00f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 26 May 2024 19:21:36 +1200 Subject: [PATCH 22/87] query_transactions dev --- src/contract.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 090d3456..e821d50a 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -9,7 +9,7 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{random_in_range, DelayedWriteBuffer, DWB, DWB_LEN, TX_NODES}; +use crate::dwb::{random_in_range, DelayedWriteBuffer, ACCOUNT_TXS, DWB, DWB_LEN, TX_NODES}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -566,20 +566,26 @@ pub fn query_transactions( let account = Addr::unchecked(account); let account_raw = deps.api.addr_canonicalize(account.as_str())?; + let start = page * page_size; + // first check if there are any transactions in dwb let dwb = DWB.load(deps.storage)?; let dwb_index = dwb.recipient_match(&account_raw); - let mut transactions_in_dwb = vec![]; - if dwb_index > 0 && dwb.entries[dwb_index].list_len()? > 0 { + let mut txs_in_dwb = vec![]; + let txs_in_dwb_count = dwb.entries[dwb_index].list_len()?; + if dwb_index > 0 && txs_in_dwb_count > 0 && start < txs_in_dwb_count as u32 { // skip if start is after buffer entries let head_node_index = dwb.entries[dwb_index].head_node()?; if head_node_index > 0 { let head_node = TX_NODES.add_suffix(&head_node_index.to_be_bytes()).load(deps.storage)?; - transactions_in_dwb = head_node.to_vec(deps.storage, deps.api)?; + txs_in_dwb = head_node.to_vec(deps.storage, deps.api)?; } } // second get number of txs in account storage - + let addr_store = ACCOUNT_TXS.add_suffix(account_raw.as_slice()); + let len = addr_store.get_len(deps.storage)? as u64; + + /* let (txs, total) = StoredTx::get_txs( From ecee6c5b34dcce02e650cf863cb1c0c4625ea12f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 4 Jun 2024 21:55:17 +1200 Subject: [PATCH 23/87] query transactions w/ binary search --- src/contract.rs | 110 +++++++++++++++++++++++++++++++++++++++++++----- src/dwb.rs | 75 +++++++++++++++++++++++++++------ 2 files changed, 162 insertions(+), 23 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 5acbcdbf..a5c683ad 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -9,7 +9,8 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{random_in_range, DelayedWriteBuffer, DWB, DWB_LEN, DWB_MAX_TX_EVENTS, TX_NODES}; +use crate::dwb::{random_in_range, AccountTxsStore, DelayedWriteBuffer, ACCOUNT_TXS, ACCOUNT_TX_COUNT, DWB, DWB_LEN, DWB_MAX_TX_EVENTS, TX_NODES}; +use crate::msg::TxWithCoins; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -20,7 +21,7 @@ use crate::state::{ }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - append_new_stored_tx, store_burn, store_deposit, store_mint, store_redeem, StoredTxAction, + append_new_stored_tx, store_burn, store_deposit, store_mint, store_redeem, StoredTxAction, Tx, TxAction, }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -559,6 +560,10 @@ pub fn query_transactions( page: u32, page_size: u32, ) -> StdResult { + if page_size == 0 { + return Err(StdError::generic_err("invalid page size")); + } + // Notice that if query_transactions() was called by a viewing-key call, the address of // 'account' has already been validated. // The address of 'account' should not be validated if query_transactions() was called by a @@ -567,6 +572,7 @@ pub fn query_transactions( let account_raw = deps.api.addr_canonicalize(account.as_str())?; let start = page * page_size; + let mut end = start + page_size; // one more than end index // first check if there are any transactions in dwb let dwb = DWB.load(deps.storage)?; @@ -581,10 +587,94 @@ pub fn query_transactions( } } - // second get number of txs in account storage - let addr_store = ACCOUNT_TXS.add_suffix(account_raw.as_slice()); - let len = addr_store.get_len(deps.storage)? as u64; + let account_slice = account_raw.as_slice(); + let settled_tx_count = ACCOUNT_TX_COUNT.add_suffix(account_slice).load(deps.storage)?; + let total = txs_in_dwb_count as u32 + settled_tx_count as u32; + if end > total { + end = total; + } + let mut txs: Vec = vec![]; + + let txs_in_dwb_count = txs_in_dwb_count as u32; + if start < txs_in_dwb_count && end < txs_in_dwb_count { + // option 1, start and end are both in dwb + txs = txs_in_dwb[start as usize..end as usize].to_vec(); // reverse chronological + } else if start < txs_in_dwb_count && end >= txs_in_dwb_count { + // option 2, start is in dwb and end is in settled txs + // in this case, we do not need to search for txs, just begin at last bundle and move backwards + txs = txs_in_dwb[start as usize..].to_vec(); // reverse chronological + let mut txs_left = (end - start).saturating_sub(txs.len() as u32); + let tx_bundles_store = ACCOUNT_TXS.add_suffix(account_slice); + let tx_bundles_idx_len = tx_bundles_store.get_len(deps.storage)?; + if tx_bundles_idx_len > 0 { + let mut bundle_idx = tx_bundles_idx_len - 1; + loop { + let tx_bundle = tx_bundles_store.get_at(deps.storage, bundle_idx.clone())?; + let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; + let list_len = tx_bundle.list_len as u32; + if txs_left <= list_len { + txs.extend_from_slice(&head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize]); + break; + } + txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs_left = txs_left.saturating_sub(list_len); + if bundle_idx > 0 { + bundle_idx -= 1; + } else { + break; + } + } + } + } else if start >= txs_in_dwb_count { + // option 3, start is not in dwb + // in this case, search for where the beginning bundle is using binary search + + // bundle tx offsets are chronological, but we need reverse chronological + // so get the settled start index as if order is reversed + let settled_start = settled_tx_count.saturating_sub(start - txs_in_dwb_count); + + if let Some((bundle_idx, tx_bundle, start_at)) = AccountTxsStore::find_start_bundle( + deps.storage, + &account_raw, + settled_start + )? { + let mut txs_left = end - start; + + let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; + let list_len = tx_bundle.list_len as u32; + if start_at + txs_left <= list_len { + // this first bundle has all the txs we need + txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..(start_at + txs_left) as usize].to_vec(); + } else { + // get the rest of the txs in this bundle and then go back through history + txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); + txs_left = txs_left.saturating_sub(list_len - start_at); + + if bundle_idx > 0 && txs_left > 0 { + // get the next earlier bundle + let mut bundle_idx = bundle_idx - 1; + let tx_bundles_store = ACCOUNT_TXS.add_suffix(account_slice); + loop { + let tx_bundle = tx_bundles_store.get_at(deps.storage, bundle_idx.clone())?; + let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; + let list_len = tx_bundle.list_len as u32; + if txs_left <= list_len { + txs.extend_from_slice(&head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize]); + break; + } + txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs_left = txs_left.saturating_sub(list_len); + if bundle_idx > 0 { + bundle_idx -= 1; + } else { + break; + } + } + } + } + } + } /* let (txs, total) = @@ -595,7 +685,7 @@ pub fn query_transactions( page, page_size )?; - +*/ let symbol = CONFIG.load(deps.storage)?.symbol; let txs = txs.iter().map(|tx| { let denom = match tx.action { @@ -614,12 +704,10 @@ pub fn query_transactions( block_time: tx.block_time, } }).collect(); -*/ + let result = QueryAnswer::TransactionHistory { - //txs, - txs: vec![], // TODO update - //total: Some(total), - total: None, + txs, + total: Some(total as u64), }; to_binary(&result) } diff --git a/src/dwb.rs b/src/dwb.rs index 4fe39a89..c5cf0841 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -29,11 +29,6 @@ fn store_new_tx_node(store: &mut dyn Storage, tx_node: TxNode) -> StdResult } pub const ZERO_ADDR: [u8; 20] = [0u8; 20]; -pub const IMPOSSIBLE_ADDR: [u8; 20] = [ - 0x29, 0xcf, 0xc6, 0x37, 0x62, 0x55, 0xa7, 0x84, 0x51, 0xee, - 0xb4, 0xb1, 0x29, 0xed, 0x8e, 0xac, 0xff, 0xa2, 0xfe, 0xef -]; - // 64 entries + 1 "dummy" entry prepended (idx: 0 in DelayedWriteBufferEntry array) // minimum allowable size: 3 pub const DWB_LEN: u16 = 65; @@ -75,7 +70,7 @@ impl DelayedWriteBuffer { empty_space_counter: DWB_LEN - 1, // first entry is a dummy entry for constant-time writing entries: [ - DelayedWriteBufferEntry::new(CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; DWB_LEN as usize + DelayedWriteBufferEntry::new(CanonicalAddr::from(&ZERO_ADDR))?; DWB_LEN as usize ] }) } @@ -441,7 +436,8 @@ impl AccountTxsStore { /// Does a binary search on the append store to find the bundle where the `start_idx` tx can be found. /// For a paginated search `start_idx` = `page` * `page_size`. - pub fn find_start_bundle(store: &dyn Storage, account: CanonicalAddr, start_idx: u32) -> StdResult> { + /// Returns the bundle index, the bundle, and the index in the bundle list to start at + pub fn find_start_bundle(store: &dyn Storage, account: &CanonicalAddr, start_idx: u32) -> StdResult> { let account_txs_store = ACCOUNT_TXS.add_suffix(account.as_slice()); let mut left = 0u32; @@ -450,9 +446,11 @@ impl AccountTxsStore { while left <= right { let mid = (left + right) / 2; let mid_bundle = account_txs_store.get_at(store, mid)?; - if start_idx >= mid_bundle.offset && start_idx < mid_bundle.offset + u32::from(mid_bundle.list_len) { + if start_idx >= mid_bundle.offset && start_idx < mid_bundle.offset + (mid_bundle.list_len as u32) { // we have the correct bundle - return Ok(Some((mid, mid_bundle))); + // which index in list to start at? + let start_at = (mid_bundle.list_len as u32) - (start_idx - mid_bundle.offset) - 1; + return Ok(Some((mid, mid_bundle, start_at))); } else if start_idx < mid_bundle.offset { right = mid - 1; } else { @@ -469,20 +467,58 @@ impl AccountTxsStore { mod tests { use std::any::Any; - use cosmwasm_std::{testing::*, Api}; + use cosmwasm_std::{testing::*, Api, Binary, Response, Uint128}; use cosmwasm_std::{ from_binary, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, WasmMsg, }; use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; - use crate::msg::ResponseStatus; + use crate::contract::instantiate; + use crate::msg::{InstantiateMsg, ResponseStatus}; use crate::msg::{InitConfig, InitialBalance}; + use crate::transaction_history::{append_new_stored_tx, StoredTxAction}; use super::*; + fn init_helper( + initial_balances: Vec, + ) -> ( + StdResult, + OwnedDeps, + ) { + let mut deps = mock_dependencies_with_balance(&[]); + let env = mock_env(); + let info = mock_info("instantiator", &[]); + + let init_msg = InstantiateMsg { + name: "sec-sec".to_string(), + admin: Some("admin".to_string()), + symbol: "SECSEC".to_string(), + decimals: 8, + initial_balances: Some(initial_balances), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: None, + supported_denoms: None, + }; + + (instantiate(deps.as_mut(), env, info, init_msg), deps) + } + #[test] - fn test_dwb_entry_setters_getters() { + fn test_dwb_entry() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let env = mock_env(); + let info = mock_info("bob", &[]); + let recipient = CanonicalAddr::from(ZERO_ADDR); let mut dwb_entry = DelayedWriteBufferEntry::new(recipient).unwrap(); assert_eq!(dwb_entry, DelayedWriteBufferEntry([0u8; DWB_ENTRY_BYTES])); @@ -502,5 +538,20 @@ mod tests { assert_eq!(dwb_entry.amount().unwrap(), 1u64); assert_eq!(dwb_entry.head_node().unwrap(), 1u64); assert_eq!(dwb_entry.list_len().unwrap(), 1u16); + + // first store the tx information in the global append list of txs and get the new tx id + let mut storage = deps.as_mut().storage; + let from = CanonicalAddr::from(&[2u8; 20]); + let sender = CanonicalAddr::from(&[2u8; 20]); + let to = CanonicalAddr::from(&[1u8;20]); + let action = StoredTxAction::transfer( + from.clone(), + sender.clone(), + to.clone() + ); + let tx_id = append_new_stored_tx(storage, &action, 1000u128, Some("memo".to_string()), &env.block).unwrap(); + + let result = dwb_entry.add_tx_node(storage, tx_id).unwrap(); + assert_eq!(dwb_entry.head_node().unwrap(), result); } } \ No newline at end of file From 069259e0eeaeb0841ce6a5c4e20df4aef7fbc2fe Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 4 Jun 2024 21:57:49 +1200 Subject: [PATCH 24/87] cleanup --- src/contract.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index a5c683ad..9bf0d58c 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -676,16 +676,7 @@ pub fn query_transactions( } } -/* - let (txs, total) = - StoredTx::get_txs( - deps.storage, - deps.api, - account, - page, - page_size - )?; -*/ + // TODO handle deposit denom let symbol = CONFIG.load(deps.storage)?.symbol; let txs = txs.iter().map(|tx| { let denom = match tx.action { From 32a42fd2b4d1e31d8372c049b2071320c9707fed Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 8 Jun 2024 21:11:05 +1200 Subject: [PATCH 25/87] tx history, plus transfer testing --- Cargo.lock | 10 + Cargo.toml | 7 +- src/contract.rs | 666 +++++++++++++++++++++++++++++++++++++++++++++++- src/dwb.rs | 11 +- src/strings.rs | 2 +- 5 files changed, 681 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69887e74..54d83868 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,6 +347,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + [[package]] name = "hmac" version = "0.12.1" @@ -855,15 +861,19 @@ version = "1.0.0" dependencies = [ "base64 0.21.0", "cosmwasm-schema", + "hex", + "hex-literal", "rand 0.8.5", "rust-crypto", "schemars", + "secret-cosmwasm-crypto", "secret-cosmwasm-std", "secret-cosmwasm-storage", "secret-toolkit", "secret-toolkit-crypto", "serde", "serde-big-array", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 575f95ec..59290f14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,9 +34,10 @@ backtraces = ["cosmwasm-std/backtraces"] [dependencies] cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } +cosmwasm-crypto = { package = "secret-cosmwasm-crypto", version = "1.1.11" } rand = { version = "0.8.5", default-features = false } secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] } -secret-toolkit-crypto = { version = "0.10.0", features = ["rand", "hash"] } +secret-toolkit-crypto = { version = "0.10.0", features = ["rand", "hash", "ecc-secp256k1"] } schemars = "0.8.12" serde = { version = "1.0.158", default-features = false, features = ["derive"] } @@ -44,5 +45,9 @@ serde-big-array = "0.5.1" base64 = "0.21.0" rust-crypto = "0.2.36" +hex = "0.4" +hex-literal = "0.3.1" +thiserror = "1.0.13" + [dev-dependencies] cosmwasm-schema = { version = "1.1.8" } diff --git a/src/contract.rs b/src/contract.rs index 9bf0d58c..91ee7efd 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -599,10 +599,12 @@ pub fn query_transactions( let txs_in_dwb_count = txs_in_dwb_count as u32; if start < txs_in_dwb_count && end < txs_in_dwb_count { // option 1, start and end are both in dwb + println!("OPTION 1"); txs = txs_in_dwb[start as usize..end as usize].to_vec(); // reverse chronological } else if start < txs_in_dwb_count && end >= txs_in_dwb_count { // option 2, start is in dwb and end is in settled txs // in this case, we do not need to search for txs, just begin at last bundle and move backwards + println!("OPTION 2"); txs = txs_in_dwb[start as usize..].to_vec(); // reverse chronological let mut txs_left = (end - start).saturating_sub(txs.len() as u32); let tx_bundles_store = ACCOUNT_TXS.add_suffix(account_slice); @@ -632,7 +634,11 @@ pub fn query_transactions( // bundle tx offsets are chronological, but we need reverse chronological // so get the settled start index as if order is reversed - let settled_start = settled_tx_count.saturating_sub(start - txs_in_dwb_count); + println!("OPTION 3"); + println!("start: {start}"); + println!("txs_in_dwb_count: {txs_in_dwb_count}"); + let settled_start = settled_tx_count.saturating_sub(start - txs_in_dwb_count).saturating_sub(1); + println!("settled_start: {settled_start}"); if let Some((bundle_idx, tx_bundle, start_at)) = AccountTxsStore::find_start_bundle( deps.storage, @@ -640,6 +646,7 @@ pub fn query_transactions( settled_start )? { let mut txs_left = end - start; + println!("txs_left: {txs_left}"); let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; let list_len = tx_bundle.list_len as u32; @@ -650,6 +657,7 @@ pub fn query_transactions( // get the rest of the txs in this bundle and then go back through history txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); txs_left = txs_left.saturating_sub(list_len - start_at); + println!("txs_left: {txs_left}"); if bundle_idx > 0 && txs_left > 0 { // get the next earlier bundle @@ -1989,20 +1997,20 @@ fn perform_transfer( new_entry.add_tx_node(store, tx_id)?; new_entry.add_amount(amount)?; - // whether or not recipient is in the buffer (non-zero index) // casting to i32 will never overflow, so long as dwb length is limited to a u16 value let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32); // randomly pick an entry to exclude in case the recipient is not in the buffer let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; + println!("random_exclude_index: {random_exclude_index}"); // index of entry to exclude from selection let exclude_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, random_exclude_index); // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; - + println!("random_settle_index: {random_settle_index}"); // whether or not the buffer is fully saturated yet let if_undersaturated = constant_time_is_not_zero(dwb.empty_space_counter as i32); @@ -2013,7 +2021,6 @@ fn perform_transfer( // if buffer is not yet saturated, settle the address at the next empty index let bounded_settle_index = constant_time_if_else(if_undersaturated, next_empty_index, random_settle_index); - // check if we have any open slots in the linked list let if_list_can_grow = constant_time_is_not_zero((DWB_MAX_TX_EVENTS - dwb.entries[recipient_index].list_len()?) as i32); @@ -2037,8 +2044,8 @@ fn perform_transfer( // decrement empty space counter if it is undersaturated and the recipient was not already in the buffer dwb.empty_space_counter -= constant_time_if_else( if_undersaturated, - constant_time_if_else(if_recipient_in_buffer, 0usize, 1usize), - 0usize + constant_time_if_else(if_recipient_in_buffer, 0, 1), + 0 ) as u16; DWB.save(store, &dwb)?; @@ -2109,8 +2116,10 @@ mod tests { }; use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; + use crate::dwb::TX_NODES_COUNT; use crate::msg::{ResponseStatus, TxWithCoins}; use crate::msg::{InitConfig, InitialBalance}; + use crate::state::TX_COUNT; use super::*; @@ -2378,6 +2387,11 @@ mod tests { init_result.err().unwrap() ); + let tx_nodes_count = TX_NODES_COUNT.load(&deps.storage).unwrap_or_default(); + assert_eq!(0, tx_nodes_count); + let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); + assert_eq!(1, tx_count); // due to mint + let handle_msg = ExecuteMsg::Transfer { recipient: "alice".to_string(), amount: Uint128::new(1000), @@ -2385,23 +2399,653 @@ mod tests { padding: None, }; let info = mock_info("bob", &[]); - - let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[0u8; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); let result = handle_result.unwrap(); assert!(ensure_success(result)); let bob_addr = deps .api - .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str()) + .addr_canonicalize(Addr::unchecked("bob").as_str()) .unwrap(); let alice_addr = deps .api - .addr_canonicalize(Addr::unchecked("alice".to_string()).as_str()) + .addr_canonicalize(Addr::unchecked("alice").as_str()) .unwrap(); assert_eq!(5000 - 1000, BalancesStore::load(&deps.storage, &bob_addr)); - assert_eq!(1000, BalancesStore::load(&deps.storage, &alice_addr)); + // alice has not been settled yet + assert_ne!(1000, BalancesStore::load(&deps.storage, &alice_addr)); + + let dwb = DWB.load(&deps.storage).unwrap(); + println!("DWB: {dwb:?}"); + // assert we have decremented empty_space_counter + assert_eq!(63, dwb.empty_space_counter); + // assert first entry has correct information for alice + let alice_entry = dwb.entries[1]; + assert_eq!(1, alice_entry.list_len().unwrap()); + assert_eq!(1000, alice_entry.amount().unwrap()); + // the id of the head_node + assert_eq!(2, alice_entry.head_node().unwrap()); + let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); + assert_eq!(2, tx_count); + + let tx_node1 = TX_NODES.add_suffix(&1u64.to_be_bytes()).load(&deps.storage).unwrap(); + println!("tx node 1: {tx_node1:?}"); + let tx_node2 = TX_NODES.add_suffix(&2u64.to_be_bytes()).load(&deps.storage).unwrap(); + println!("tx node 2: {tx_node2:?}"); + + // now send 100 to charlie from bob + let handle_msg = ExecuteMsg::Transfer { + recipient: "charlie".to_string(), + amount: Uint128::new(100), + memo: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[1u8; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let charlie_addr = deps + .api + .addr_canonicalize(Addr::unchecked("charlie").as_str()) + .unwrap(); + + assert_eq!(5000 - 1000 - 100, BalancesStore::load(&deps.storage, &bob_addr)); + // alice has not been settled yet + assert_ne!(1000, BalancesStore::load(&deps.storage, &alice_addr)); + // charlie has not been settled yet + assert_ne!(100, BalancesStore::load(&deps.storage, &charlie_addr)); + + let dwb = DWB.load(&deps.storage).unwrap(); + println!("DWB: {dwb:?}"); + // assert we have decremented empty_space_counter + assert_eq!(62, dwb.empty_space_counter); + // assert entry has correct information for alice + let charlie_entry = dwb.entries[2]; + assert_eq!(1, charlie_entry.list_len().unwrap()); + assert_eq!(100, charlie_entry.amount().unwrap()); + // the id of the head_node + assert_eq!(4, charlie_entry.head_node().unwrap()); + let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); + assert_eq!(3, tx_count); + + // send another 500 to alice from bob + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(500), + memo: None, + padding: None, + }; + let info = mock_info("bob", &[]); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[2u8; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + assert_eq!(5000 - 1000 - 100 - 500, BalancesStore::load(&deps.storage, &bob_addr)); + // make sure alice has not been settled yet + assert_ne!(1500, BalancesStore::load(&deps.storage, &alice_addr)); + + let dwb = DWB.load(&deps.storage).unwrap(); + println!("DWB: {dwb:?}"); + // assert we have not decremented empty_space_counter + assert_eq!(62, dwb.empty_space_counter); + // assert entry has correct information for alice + let alice_entry = dwb.entries[1]; + assert_eq!(2, alice_entry.list_len().unwrap()); + assert_eq!(1500, alice_entry.amount().unwrap()); + // the id of the head_node + assert_eq!(6, alice_entry.head_node().unwrap()); + let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); + assert_eq!(4, tx_count); + + // convert head_node to vec + let alice_nodes = TX_NODES.add_suffix( + &alice_entry + .head_node().unwrap() + .to_be_bytes()).load(&deps.storage).unwrap() + .to_vec(&deps.storage, &deps.api).unwrap(); + + let expected_alice_nodes: Vec = vec![ + Tx { + id: 4, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + amount: Uint128::from(500_u128), + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + Tx { + id: 2, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + amount: Uint128::from(1000_u128), + memo: None, + block_time: 1571797419, + block_height: 12345 + } + ]; + assert_eq!(alice_nodes, expected_alice_nodes); + + // now send 200 to ernie from bob + let handle_msg = ExecuteMsg::Transfer { + recipient: "ernie".to_string(), + amount: Uint128::new(200), + memo: None, + padding: None, + }; + let info = mock_info("bob", &[]); + + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[3u8; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let ernie_addr = deps + .api + .addr_canonicalize(Addr::unchecked("ernie").as_str()) + .unwrap(); + + assert_eq!(5000 - 1000 - 100 - 500 - 200, BalancesStore::load(&deps.storage, &bob_addr)); + // alice has not been settled yet + assert_ne!(1500, BalancesStore::load(&deps.storage, &alice_addr)); + // charlie has not been settled yet + assert_ne!(100, BalancesStore::load(&deps.storage, &charlie_addr)); + // ernie has not been settled yet + assert_ne!(200, BalancesStore::load(&deps.storage, &ernie_addr)); + + let dwb = DWB.load(&deps.storage).unwrap(); + println!("DWB: {dwb:?}"); + + // assert we have decremented empty_space_counter + assert_eq!(61, dwb.empty_space_counter); + // assert entry has correct information for ernie + let ernie_entry = dwb.entries[3]; + assert_eq!(1, ernie_entry.list_len().unwrap()); + assert_eq!(200, ernie_entry.amount().unwrap()); + // the id of the head_node + assert_eq!(8, ernie_entry.head_node().unwrap()); + let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); + assert_eq!(5, tx_count); + + // now alice sends 50 to dora + // this should settle alice and create entry for dora + let handle_msg = ExecuteMsg::Transfer { + recipient: "dora".to_string(), + amount: Uint128::new(50), + memo: None, + padding: None, + }; + let info = mock_info("alice", &[]); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[4u8; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + let dora_addr = deps + .api + .addr_canonicalize(Addr::unchecked("dora").as_str()) + .unwrap(); + + // alice has been settled + assert_eq!(1500 - 50, BalancesStore::load(&deps.storage, &alice_addr)); + // dora has not been settled + assert_ne!(50, BalancesStore::load(&deps.storage, &dora_addr)); + + let dwb = DWB.load(&deps.storage).unwrap(); + println!("DWB: {dwb:?}"); + + // assert we have decremented empty_space_counter + assert_eq!(60, dwb.empty_space_counter); + // assert entry has correct information for ernie + let dora_entry = dwb.entries[4]; + assert_eq!(1, dora_entry.list_len().unwrap()); + assert_eq!(50, dora_entry.amount().unwrap()); + // the id of the head_node + assert_eq!(10, dora_entry.head_node().unwrap()); + let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); + assert_eq!(6, tx_count); + + // now we will send to 60 more addresses to fill up the buffer + for i in 1..=60 { + let recipient = format!("receipient{i}"); + // now send 1 to recipient from bob + let handle_msg = ExecuteMsg::Transfer { + recipient, + amount: Uint128::new(1), + memo: None, + padding: None, + }; + let info = mock_info("bob", &[]); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[255-i; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + } + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 60, BalancesStore::load(&deps.storage, &bob_addr)); + + let dwb = DWB.load(&deps.storage).unwrap(); + println!("DWB: {dwb:?}"); + + // assert we have filled the buffer + assert_eq!(0, dwb.empty_space_counter); + + let recipient = format!("receipient_over"); + // now send 1 to recipient from bob + let handle_msg = ExecuteMsg::Transfer { + recipient, + amount: Uint128::new(1), + memo: None, + padding: None, + }; + let info = mock_info("bob", &[]); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[50; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 60 - 1, BalancesStore::load(&deps.storage, &bob_addr)); + + let dwb = DWB.load(&deps.storage).unwrap(); + println!("DWB: {dwb:?}"); + + let recipient = format!("receipient_over_2"); + // now send 1 to recipient from bob + let handle_msg = ExecuteMsg::Transfer { + recipient, + amount: Uint128::new(1), + memo: None, + padding: None, + }; + let info = mock_info("bob", &[]); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[12; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 60 - 1 - 1, BalancesStore::load(&deps.storage, &bob_addr)); + + let dwb = DWB.load(&deps.storage).unwrap(); + println!("DWB: {dwb:?}"); + + // now we send 50 transactions to alice from bob + for i in 1..=50 { + // send 1 to alice from bob + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(i.into()), + memo: None, + padding: None, + }; + + let info = mock_info("bob", &[]); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[125-i; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + // alice should not settle + assert_eq!(1500 - 50, BalancesStore::load(&deps.storage, &alice_addr)); + } + + // alice sends 1 to dora to settle + // this should settle alice and create entry for dora + let handle_msg = ExecuteMsg::Transfer { + recipient: "dora".to_string(), + amount: Uint128::new(1), + memo: None, + padding: None, + }; + let info = mock_info("alice", &[]); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[61; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + assert_eq!(2724, BalancesStore::load(&deps.storage, &alice_addr)); + + // now we send 50 more transactions to alice from bob + for i in 1..=50 { + // send 1 to alice from bob + let handle_msg = ExecuteMsg::Transfer { + recipient: "alice".to_string(), + amount: Uint128::new(i.into()), + memo: None, + padding: None, + }; + + let info = mock_info("bob", &[]); + let mut env = mock_env(); + env.block.random = Some(Binary::from(&[200-i; 32])); + let handle_result = execute(deps.as_mut(), env, info, handle_msg); + + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + // alice should not settle + assert_eq!(2724, BalancesStore::load(&deps.storage, &alice_addr)); + } + + // now we use alice to check query transaction history pagination works + let handle_msg = ExecuteMsg::SetViewingKey { + key: "key".to_string(), + padding: None, + }; + let info = mock_info("alice", &[]); + + let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg); + let result = handle_result.unwrap(); + assert!(ensure_success(result)); + + // + // check last 3 transactions for alice (all in dwb) + // + let query_msg = QueryMsg::TransactionHistory { + address: "alice".to_string(), + key: "key".to_string(), + page: None, + page_size: 3, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + println!("transfers: {transfers:?}"); + let expected_transfers = vec![ + TxWithCoins { + id: 169, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(50u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 168, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(49u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, TxWithCoins { + id: 167, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(48u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + ]; + assert_eq!(transfers, expected_transfers); + + // + // check 6 transactions for alice that span over end of the 50 in dwb and settled + // page: 8, page size: 6 + // start is index 48 + // + let query_msg = QueryMsg::TransactionHistory { + address: "alice".to_string(), + key: "key".to_string(), + page: Some(8), + page_size: 6, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + println!("transfers: {transfers:?}"); + let expected_transfers = vec![ + TxWithCoins { + id: 121, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(2u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 120, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(1u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 119, + action: TxAction::Transfer { + from: Addr::unchecked("alice"), + sender: Addr::unchecked("alice"), + recipient: Addr::unchecked("dora") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(1u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 118, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(50u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 117, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(49u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 116, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(48u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + } + ]; + assert_eq!(transfers, expected_transfers); + + // + // check transactions for alice, starting in settled across different bundles with `end` past the last transaction + // there are 104 transactions total for alice + // page: 3, page size: 99 + // start is index 99 (100th tx) + // + let query_msg = QueryMsg::TransactionHistory { + address: "alice".to_string(), + key: "key".to_string(), + page: Some(3), + page_size: 33, + //page: None, + //page_size: 500, + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let transfers = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::TransactionHistory { txs, .. } => txs, + other => panic!("Unexpected: {:?}", other), + }; + println!("transfers: {transfers:?}"); + let expected_transfers = vec![ + TxWithCoins { + id: 70, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(2u128) + }, + memo: None, + block_time: + 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 69, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(1u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 6, + action: TxAction::Transfer { + from: Addr::unchecked("alice"), + sender: Addr::unchecked("alice"), + recipient: Addr::unchecked("dora") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(50u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 4, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(500u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + }, + TxWithCoins { + id: 2, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice") + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(1000u128) + }, + memo: None, + block_time: 1571797419, + block_height: 12345 + } + ]; + let transfers_len = transfers.len(); + println!("transfers.len(): {transfers_len}"); + assert_eq!(transfers, expected_transfers); + + + // + // + // + // + // now try invalid transfer let handle_msg = ExecuteMsg::Transfer { recipient: "alice".to_string(), amount: Uint128::new(10000), diff --git a/src/dwb.rs b/src/dwb.rs index c5cf0841..53fc5cbd 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -28,7 +28,6 @@ fn store_new_tx_node(store: &mut dyn Storage, tx_node: TxNode) -> StdResult Ok(tx_nodes_serial_id) } -pub const ZERO_ADDR: [u8; 20] = [0u8; 20]; // 64 entries + 1 "dummy" entry prepended (idx: 0 in DelayedWriteBufferEntry array) // minimum allowable size: 3 pub const DWB_LEN: u16 = 65; @@ -45,7 +44,10 @@ pub struct DelayedWriteBuffer { #[inline] fn random_addr(rng: &mut ContractPrng) -> CanonicalAddr { - CanonicalAddr::from(&rng.rand_bytes()[0..20]) + #[cfg(test)] + return CanonicalAddr::from(&[rng.rand_bytes(), rng.rand_bytes()].concat()[0..DWB_RECIPIENT_BYTES]); // because mock canonical addr is 54 bytes + #[cfg(not(test))] + CanonicalAddr::from(&rng.rand_bytes()[0..DWB_RECIPIENT_BYTES]) // canonical addr is 20 bytes (less than 32) } pub fn random_in_range(rng: &mut ContractPrng, a: u32, b: u32) -> StdResult { @@ -193,6 +195,9 @@ impl DelayedWriteBuffer { const U16_BYTES: usize = 2; const U64_BYTES: usize = 8; +#[cfg(test)] +const DWB_RECIPIENT_BYTES: usize = 54; // because mock_api creates rando canonical addr that is 54 bytes long +#[cfg(not(test))] const DWB_RECIPIENT_BYTES: usize = 20; const DWB_AMOUNT_BYTES: usize = 8; // Max 16 (u128) const DWB_HEAD_NODE_BYTES: usize = 5; // Max 8 (u64) @@ -200,6 +205,8 @@ const DWB_LIST_LEN_BYTES: usize = 2; // u16 const DWB_ENTRY_BYTES: usize = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES + DWB_LIST_LEN_BYTES; +pub const ZERO_ADDR: [u8; DWB_RECIPIENT_BYTES] = [0u8; DWB_RECIPIENT_BYTES]; + /// A delayed write buffer entry consists of the following bytes in this order: /// /// // recipient canonical address diff --git a/src/strings.rs b/src/strings.rs index 554c48e0..39c87bc5 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1 +1 @@ -pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str = "`transfer_history` query is now UNSUPPORTED. Use `transaction_history` instead."; \ No newline at end of file +pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str = "`transfer_history` query is UNSUPPORTED. Use `transaction_history` instead."; \ No newline at end of file From 925f06cde41ab2fe7a9a36f25254134e850471b4 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 8 Jun 2024 22:04:32 +1200 Subject: [PATCH 26/87] add query balance to test --- src/contract.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 91ee7efd..a984c1e8 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -2753,7 +2753,6 @@ mod tests { assert_eq!(2724, BalancesStore::load(&deps.storage, &alice_addr)); } - // now we use alice to check query transaction history pagination works let handle_msg = ExecuteMsg::SetViewingKey { key: "key".to_string(), padding: None, @@ -2764,6 +2763,23 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); + // check that alice's balance when queried is correct (includes both settled and dwb amounts) + // settled = 2724 + // dwb = 1275 + // total should be = 3999 + let query_msg = QueryMsg::Balance { + address: "alice".to_string(), + key: "key".to_string(), + }; + let query_result = query(deps.as_ref(), mock_env(), query_msg); + let balance = match from_binary(&query_result.unwrap()).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected"), + }; + assert_eq!(balance, Uint128::new(3999)); + + // now we use alice to check query transaction history pagination works + // // check last 3 transactions for alice (all in dwb) // @@ -3039,7 +3055,6 @@ mod tests { println!("transfers.len(): {transfers_len}"); assert_eq!(transfers, expected_transfers); - // // // From 554de7947b3d3409bf0a815f10ee9a2ee39862f1 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 11 Jun 2024 17:25:08 +1200 Subject: [PATCH 27/87] move add recipient to dwb impl and perform mint --- src/contract.rs | 345 ++++++++++++++++--------------------- src/dwb.rs | 84 ++++++++- src/transaction_history.rs | 60 ++----- 3 files changed, 246 insertions(+), 243 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index a984c1e8..07f1d95b 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -21,7 +21,7 @@ use crate::state::{ }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - append_new_stored_tx, store_burn, store_deposit, store_mint, store_redeem, StoredTxAction, Tx, TxAction, + store_burn_action, store_deposit, store_mint_action, store_redeem, store_transfer_action, Tx, TxAction }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -66,16 +66,21 @@ pub fn instantiate( DWB.save(deps.storage, &DelayedWriteBuffer::new()?)?; let initial_balances = msg.initial_balances.unwrap_or_default(); + let raw_admin = deps.api.addr_canonicalize(admin.as_str())?; + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &prng_seed_hashed); for balance in initial_balances { let amount = balance.amount.u128(); let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?; - // Here amount is also the amount to be added because the account has no prior balance - BalancesStore::update_balance( - deps.storage, - &balance_address, - amount, - true, - "", + + perform_mint( + deps.storage, + &mut rng, + &raw_admin, + &balance_address, + amount, + Some("Initial Balance".to_string()), + &env.block )?; if let Some(new_total_supply) = total_supply.checked_add(amount) { @@ -85,16 +90,6 @@ pub fn instantiate( "The sum of all initial balances exceeds the maximum possible total supply", )); } - - store_mint( - deps.storage, - deps.api, - deps.api.addr_canonicalize(admin.as_str())?, - balance_address, - balance.amount.u128(), - Some("Initial Balance".to_string()), - &env.block, - )?; } let supported_denoms = match msg.supported_denoms { @@ -135,6 +130,9 @@ pub fn instantiate( #[entry_point] pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + let seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + let contract_status = CONTRACT_STATUS.load(deps.storage)?; match contract_status { @@ -180,6 +178,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S deps, env, info, + &mut rng, recipient, amount, memo, @@ -195,6 +194,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S deps, env, info, + &mut rng, recipient, recipient_code_hash, amount, @@ -202,16 +202,16 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S msg, ), ExecuteMsg::BatchTransfer { actions, .. } => { - try_batch_transfer(deps, env, info, actions) + try_batch_transfer(deps, env, info, &mut rng, actions) } ExecuteMsg::BatchSend { actions, .. } => { - try_batch_send(deps, env, info, actions) + try_batch_send(deps, env, info, &mut rng, actions) } ExecuteMsg::Burn { amount, memo, .. - } => try_burn(deps, env, info, amount, memo), + } => try_burn(deps, env, info, &mut rng, amount, memo), ExecuteMsg::RegisterReceive { code_hash, .. } => { try_register_receive(deps, info, code_hash) } @@ -241,6 +241,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S deps, &env, info, + &mut rng, owner, recipient, amount, @@ -258,6 +259,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S deps, env, &info, + &mut rng, owner, recipient, recipient_code_hash, @@ -266,10 +268,10 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S msg, ), ExecuteMsg::BatchTransferFrom { actions, .. } => { - try_batch_transfer_from(deps, &env, info, actions) + try_batch_transfer_from(deps, &env, info, &mut rng, actions) } ExecuteMsg::BatchSendFrom { actions, .. } => { - try_batch_send_from(deps, env, &info, actions) + try_batch_send_from(deps, env, &info, &mut rng, actions) } ExecuteMsg::BurnFrom { owner, @@ -280,12 +282,13 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S deps, &env, info, + &mut rng, owner, amount, memo, ), ExecuteMsg::BatchBurnFrom { actions, .. } => { - try_batch_burn_from(deps, &env, info, actions) + try_batch_burn_from(deps, &env, info, &mut rng, actions) } // Mint @@ -298,12 +301,13 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S deps, env, info, + &mut rng, recipient, amount, memo, ), ExecuteMsg::BatchMint { actions, .. } => { - try_batch_mint(deps, env, info, actions) + try_batch_mint(deps, env, info, &mut rng, actions) } // Other @@ -808,6 +812,7 @@ fn remove_supported_denoms( #[allow(clippy::too_many_arguments)] fn try_mint_impl( deps: &mut DepsMut, + rng: &mut ContractPrng, minter: Addr, recipient: Addr, amount: Uint128, @@ -818,23 +823,19 @@ fn try_mint_impl( let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; let raw_minter = deps.api.addr_canonicalize(minter.as_str())?; - BalancesStore::update_balance( - deps.storage, - &raw_recipient, - raw_amount, - true, - "", - )?; + perform_mint(deps.storage, rng, &raw_minter, &raw_recipient, raw_amount, memo, block)?; - store_mint( - deps.storage, - deps.api, - raw_minter, - raw_recipient, - amount.u128(), - memo, - block, - )?; + // remove from supply + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + + if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err( + "You're trying to burn more than is available in the total supply", + )); + } + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; Ok(()) } @@ -844,6 +845,7 @@ fn try_mint( mut deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, recipient: String, amount: Uint128, memo: Option, @@ -872,6 +874,7 @@ fn try_mint( // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency try_mint_impl( &mut deps, + rng, info.sender, recipient, Uint128::new(minted_amount), @@ -886,6 +889,7 @@ fn try_batch_mint( mut deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, actions: Vec, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; @@ -912,6 +916,7 @@ fn try_batch_mint( let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_mint_impl( &mut deps, + rng, info.sender.clone(), recipient, Uint128::new(actual_amount), @@ -1222,18 +1227,16 @@ fn try_transfer( mut deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, recipient: String, amount: Uint128, memo: Option, ) -> StdResult { - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); - let recipient: Addr = deps.api.addr_validate(recipient.as_str())?; try_transfer_impl( &mut deps, - &mut rng, + rng, &info.sender, &recipient, amount, @@ -1248,16 +1251,14 @@ fn try_batch_transfer( mut deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, actions: Vec, ) -> StdResult { - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); - for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_transfer_impl( &mut deps, - &mut rng, + rng, &info.sender, &recipient, action.amount, @@ -1346,21 +1347,19 @@ fn try_send( mut deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, recipient: String, recipient_code_hash: Option, amount: Uint128, memo: Option, msg: Option, ) -> StdResult { - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); - let recipient = deps.api.addr_validate(recipient.as_str())?; let mut messages = vec![]; try_send_impl( &mut deps, - &mut rng, + rng, &mut messages, info.sender, recipient, @@ -1380,17 +1379,15 @@ fn try_batch_send( mut deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, actions: Vec, ) -> StdResult { - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); - let mut messages = vec![]; for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_send_impl( &mut deps, - &mut rng, + rng, &mut messages, info.sender.clone(), recipient, @@ -1486,19 +1483,17 @@ fn try_transfer_from( mut deps: DepsMut, env: &Env, info: MessageInfo, + rng: &mut ContractPrng, owner: String, recipient: String, amount: Uint128, memo: Option, ) -> StdResult { - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); - let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; try_transfer_from_impl( &mut deps, - &mut rng, + rng, env, &info.sender, &owner, @@ -1514,17 +1509,15 @@ fn try_batch_transfer_from( mut deps: DepsMut, env: &Env, info: MessageInfo, + rng: &mut ContractPrng, actions: Vec, ) -> StdResult { - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); - for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_transfer_from_impl( &mut deps, - &mut rng, + rng, env, &info.sender, &owner, @@ -1546,6 +1539,7 @@ fn try_send_from_impl( deps: &mut DepsMut, env: Env, info: &MessageInfo, + rng: &mut ContractPrng, messages: &mut Vec, owner: Addr, recipient: Addr, @@ -1554,13 +1548,10 @@ fn try_send_from_impl( memo: Option, msg: Option, ) -> StdResult<()> { - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); - let spender = info.sender.clone(); try_transfer_from_impl( deps, - &mut rng, + rng, &env, &spender, &owner, @@ -1589,6 +1580,7 @@ fn try_send_from( mut deps: DepsMut, env: Env, info: &MessageInfo, + rng: &mut ContractPrng, owner: String, recipient: String, recipient_code_hash: Option, @@ -1603,6 +1595,7 @@ fn try_send_from( &mut deps, env, info, + rng, &mut messages, owner, recipient, @@ -1621,6 +1614,7 @@ fn try_batch_send_from( mut deps: DepsMut, env: Env, info: &MessageInfo, + rng: &mut ContractPrng, actions: Vec, ) -> StdResult { let mut messages = vec![]; @@ -1632,6 +1626,7 @@ fn try_batch_send_from( &mut deps, env.clone(), info, + rng, &mut messages, owner, recipient, @@ -1654,6 +1649,7 @@ fn try_burn_from( deps: DepsMut, env: &Env, info: MessageInfo, + rng: &mut ContractPrng, owner: String, amount: Uint128, memo: Option, @@ -1669,15 +1665,28 @@ fn try_burn_from( let raw_amount = amount.u128(); use_allowance(deps.storage, env, &owner, &info.sender, raw_amount)?; - - BalancesStore::update_balance( - deps.storage, - &raw_owner, - raw_amount, - false, - "burn", + let raw_burner = deps.api.addr_canonicalize(info.sender.as_str())?; + + let tx_id = store_burn_action( + deps.storage, + raw_owner.clone(), + raw_burner.clone(), + raw_amount, + memo, + &env.block )?; + // load delayed write buffer + let mut dwb = DWB.load(deps.storage)?; + + // settle the owner's account in buffer + dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_owner, tx_id, raw_amount, "burn")?; + if raw_burner != raw_owner { // also settle sender's account + dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_burner, tx_id, 0, "burn")?; + } + + DWB.save(deps.storage, &dwb)?; + // remove from supply let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; @@ -1690,16 +1699,6 @@ fn try_burn_from( } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - store_burn( - deps.storage, - deps.api, - raw_owner, - deps.api.addr_canonicalize(info.sender.as_str())?, - amount.u128(), - memo, - &env.block, - )?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?)) } @@ -1707,6 +1706,7 @@ fn try_batch_burn_from( deps: DepsMut, env: &Env, info: MessageInfo, + rng: &mut ContractPrng, actions: Vec, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; @@ -1725,13 +1725,25 @@ fn try_batch_burn_from( let amount = action.amount.u128(); use_allowance(deps.storage, env, &owner, &info.sender, amount)?; - BalancesStore::update_balance( - deps.storage, - &raw_owner, - amount, - false, - "burn", + let tx_id = store_burn_action( + deps.storage, + raw_owner.clone(), + raw_spender.clone(), + amount, + action.memo.clone(), + &env.block )?; + + // load delayed write buffer + let mut dwb = DWB.load(deps.storage)?; + + // settle the owner's account in buffer + dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_owner, tx_id, amount, "burn")?; + if raw_spender != raw_owner { + dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_spender, tx_id, 0, "burn")?; + } + + DWB.save(deps.storage, &dwb)?; // remove from supply if let Some(new_total_supply) = total_supply.checked_sub(amount) { @@ -1741,16 +1753,6 @@ fn try_batch_burn_from( "You're trying to burn more than is available in the total supply: {action:?}", ))); } - - store_burn( - deps.storage, - deps.api, - raw_owner, - raw_spender.clone(), - action.amount.u128(), - action.memo, - &env.block, - )?; } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; @@ -1916,6 +1918,7 @@ fn try_burn( deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, amount: Uint128, memo: Option, ) -> StdResult { @@ -1929,14 +1932,23 @@ fn try_burn( let raw_amount = amount.u128(); let raw_burn_address = deps.api.addr_canonicalize(info.sender.as_str())?; - BalancesStore::update_balance( - deps.storage, - &raw_burn_address, - raw_amount, - false, - "burn", + let tx_id = store_burn_action( + deps.storage, + raw_burn_address.clone(), + raw_burn_address.clone(), + raw_amount, + memo, + &env.block )?; + // load delayed write buffer + let mut dwb = DWB.load(deps.storage)?; + + // settle the signer's account in buffer + dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_burn_address, tx_id, raw_amount, "burn")?; + + DWB.save(deps.storage, &dwb)?; + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { total_supply = new_total_supply; @@ -1947,16 +1959,6 @@ fn try_burn( } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - store_burn( - deps.storage, - deps.api, - raw_burn_address.clone(), - raw_burn_address, - amount.u128(), - memo, - &env.block, - )?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?)) } @@ -1971,82 +1973,49 @@ fn perform_transfer( block: &BlockInfo, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id - let action = StoredTxAction::transfer( - from.clone(), - sender.clone(), - to.clone() - ); - let tx_id = append_new_stored_tx(store, &action, amount, memo, block)?; + let tx_id = store_transfer_action(store, from, sender, to, amount, memo, block)?; // load delayed write buffer let mut dwb = DWB.load(store)?; + let transfer_str = "transfer"; // settle the owner's account - dwb.settle_sender_or_owner_account(store, rng, from, tx_id, amount)?; + dwb.settle_sender_or_owner_account(store, rng, from, tx_id, amount, transfer_str)?; // if this is a *_from action, settle the sender's account, too if sender != from { - dwb.settle_sender_or_owner_account(store, rng, sender, tx_id, 0)?; + dwb.settle_sender_or_owner_account(store, rng, sender, tx_id, 0, transfer_str)?; } - // check if `to` is already a recipient in the delayed write buffer - let recipient_index = dwb.recipient_match(&to); - - // the new entry will either derive from a prior entry for the recipient or the dummy entry - let mut new_entry = dwb.entries[recipient_index].clone(); - new_entry.set_recipient(&to)?; - new_entry.add_tx_node(store, tx_id)?; - new_entry.add_amount(amount)?; - - // whether or not recipient is in the buffer (non-zero index) - // casting to i32 will never overflow, so long as dwb length is limited to a u16 value - let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32); - - // randomly pick an entry to exclude in case the recipient is not in the buffer - let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; - println!("random_exclude_index: {random_exclude_index}"); - - // index of entry to exclude from selection - let exclude_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, random_exclude_index); - - // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) - let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; - println!("random_settle_index: {random_settle_index}"); - - // whether or not the buffer is fully saturated yet - let if_undersaturated = constant_time_is_not_zero(dwb.empty_space_counter as i32); - - // find the next empty entry in the buffer - let next_empty_index = (DWB_LEN - dwb.empty_space_counter) as usize; - - // if buffer is not yet saturated, settle the address at the next empty index - let bounded_settle_index = constant_time_if_else(if_undersaturated, next_empty_index, random_settle_index); + // add the tx info for the recipient to the buffer + dwb.add_recipient(store, rng, to, tx_id, amount)?; - // check if we have any open slots in the linked list - let if_list_can_grow = constant_time_is_not_zero((DWB_MAX_TX_EVENTS - dwb.entries[recipient_index].list_len()?) as i32); - - // if we would overflow the list, just settle recipient - // TODO: see docs for attack analysis - let actual_settle_index = constant_time_if_else(if_list_can_grow, bounded_settle_index, recipient_index); + DWB.save(store, &dwb)?; - // settle the entry - dwb.settle_entry(store, actual_settle_index)?; + Ok(()) +} - // replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer - let replacement_entry = dwb.unique_random_entry(rng)?; - dwb.entries[actual_settle_index] = replacement_entry; +fn perform_mint( + store: &mut dyn Storage, + rng: &mut ContractPrng, + minter: &CanonicalAddr, + to: &CanonicalAddr, + amount: u128, + memo: Option, + block: &BlockInfo, +) -> StdResult<()> { + // first store the tx information in the global append list of txs and get the new tx id + let tx_id = store_mint_action(store, minter, to, amount, memo, block)?; - // pick the index to where the recipient's entry should be written - let write_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, actual_settle_index); + // load delayed write buffer + let mut dwb = DWB.load(store)?; - // either updates the existing recipient entry, or overwrites the random replacement entry in the settled index - dwb.entries[write_index] = new_entry; + // if minter is not recipient, settle them + if minter != to { + dwb.settle_sender_or_owner_account(store, rng, minter, tx_id, 0, "mint")?; + } - // decrement empty space counter if it is undersaturated and the recipient was not already in the buffer - dwb.empty_space_counter -= constant_time_if_else( - if_undersaturated, - constant_time_if_else(if_recipient_in_buffer, 0, 1), - 0 - ) as u16; + // add the tx info for the recipient to the buffer + dwb.add_recipient(store, rng, to, tx_id, amount)?; DWB.save(store, &dwb)?; @@ -2086,16 +2055,6 @@ fn is_valid_symbol(symbol: &str) -> bool { len_is_valid && symbol.bytes().all(|byte| byte.is_ascii_alphabetic()) } -#[inline] -fn constant_time_is_not_zero(value: i32) -> u32 { - return (((value | -value) >> 31) & 1) as u32; -} - -#[inline] -fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { - return (then * condition as usize) | (els * (1 - condition as usize)); -} - // pub fn migrate( // _deps: DepsMut, // _env: Env, @@ -2376,7 +2335,7 @@ mod tests { // Handle tests #[test] - fn test_execute_transfer() { + fn test_execute_transfer_dwb() { let (init_result, mut deps) = init_helper(vec![InitialBalance { address: "bob".to_string(), amount: Uint128::new(5000), @@ -3534,7 +3493,7 @@ mod tests { height: 12_345, time: Timestamp::from_seconds(1_571_797_420), chain_id: "cosmos-testnet-14002".to_string(), - random: None, + random: Some(Binary::from(&[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31])), }, transaction: Some(TransactionInfo { index: 3, hash: "1010".to_string()}), contract: ContractInfo { @@ -3577,7 +3536,7 @@ mod tests { let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); let alice_balance = BalancesStore::load(&deps.storage, &alice_canonical); assert_eq!(bob_balance, 5000 - 2000); - assert_eq!(alice_balance, 2000); + assert_ne!(alice_balance, 2000); let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); assert_eq!(total_supply, 5000); @@ -3720,7 +3679,7 @@ mod tests { let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); let contract_balance = BalancesStore::load(&deps.storage, &contract_canonical); assert_eq!(bob_balance, 5000 - 2000); - assert_eq!(contract_balance, 2000); + assert_ne!(contract_balance, 2000); let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); assert_eq!(total_supply, 5000); diff --git a/src/dwb.rs b/src/dwb.rs index 53fc5cbd..8ccc7b48 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -106,6 +106,7 @@ impl DelayedWriteBuffer { } /// settles a participant's account who may or may not have an entry in the buffer + /// gets balance including any amount in the buffer, and then subtracts amount spent in this tx pub fn settle_sender_or_owner_account( &mut self, store: &mut dyn Storage, @@ -113,6 +114,7 @@ impl DelayedWriteBuffer { address: &CanonicalAddr, tx_id: u64, amount_spent: u128, + op_name: &str, ) -> StdResult<()> { // release the address from the buffer let (balance, mut entry) = self.constant_time_release( @@ -134,7 +136,7 @@ impl DelayedWriteBuffer { balance_after_sub } else { return Err(StdError::generic_err(format!( - "insufficient funds to transfer: balance={balance}, required={amount_spent}", + "insufficient funds to {op_name}: balance={balance}, required={amount_spent}", ))); }; BalancesStore::save(store, address, new_balance)?; @@ -190,6 +192,77 @@ impl DelayedWriteBuffer { matched_index } + pub fn add_recipient( + &mut self, + store: &mut dyn Storage, + rng: &mut ContractPrng, + recipient: &CanonicalAddr, + tx_id: u64, + amount: u128 + ) -> StdResult<()> { + // check if `recipient` is already a recipient in the delayed write buffer + let recipient_index = self.recipient_match(recipient); + + // the new entry will either derive from a prior entry for the recipient or the dummy entry + let mut new_entry = self.entries[recipient_index].clone(); + new_entry.set_recipient(recipient)?; + new_entry.add_tx_node(store, tx_id)?; + new_entry.add_amount(amount)?; + + // whether or not recipient is in the buffer (non-zero index) + // casting to i32 will never overflow, so long as dwb length is limited to a u16 value + let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32); + + // randomly pick an entry to exclude in case the recipient is not in the buffer + let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; + println!("random_exclude_index: {random_exclude_index}"); + + // index of entry to exclude from selection + let exclude_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, random_exclude_index); + + // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) + let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; + println!("random_settle_index: {random_settle_index}"); + + // whether or not the buffer is fully saturated yet + let if_undersaturated = constant_time_is_not_zero(self.empty_space_counter as i32); + + // find the next empty entry in the buffer + let next_empty_index = (DWB_LEN - self.empty_space_counter) as usize; + + // if buffer is not yet saturated, settle the address at the next empty index + let bounded_settle_index = constant_time_if_else(if_undersaturated, next_empty_index, random_settle_index); + + // check if we have any open slots in the linked list + let if_list_can_grow = constant_time_is_not_zero((DWB_MAX_TX_EVENTS - self.entries[recipient_index].list_len()?) as i32); + + // if we would overflow the list, just settle recipient + // TODO: see docs for attack analysis + let actual_settle_index = constant_time_if_else(if_list_can_grow, bounded_settle_index, recipient_index); + + // settle the entry + self.settle_entry(store, actual_settle_index)?; + + // replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer + let replacement_entry = self.unique_random_entry(rng)?; + self.entries[actual_settle_index] = replacement_entry; + + // pick the index to where the recipient's entry should be written + let write_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, actual_settle_index); + + // either updates the existing recipient entry, or overwrites the random replacement entry in the settled index + self.entries[write_index] = new_entry; + + // decrement empty space counter if it is undersaturated and the recipient was not already in the buffer + self.empty_space_counter -= constant_time_if_else( + if_undersaturated, + constant_time_if_else(if_recipient_in_buffer, 0, 1), + 0 + ) as u16; + + Ok(()) + } + } const U16_BYTES: usize = 2; @@ -469,6 +542,15 @@ impl AccountTxsStore { } } +#[inline] +fn constant_time_is_not_zero(value: i32) -> u32 { + return (((value | -value) >> 31) & 1) as u32; +} + +#[inline] +fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { + return (then * condition as usize) | (els * (1 - condition as usize)); +} #[cfg(test)] mod tests { diff --git a/src/transaction_history.rs b/src/transaction_history.rs index a8fcaa99..e35f025e 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -312,7 +312,7 @@ pub fn append_new_stored_tx( } #[allow(clippy::too_many_arguments)] // We just need them -pub fn store_transfer( +pub fn store_transfer_action( store: &mut dyn Storage, owner: &CanonicalAddr, sender: &CanonicalAddr, @@ -327,75 +327,37 @@ pub fn store_transfer( receiver.clone() ); append_new_stored_tx(store, &action, amount, memo, block) -/* - // Write to the owners history if it's different from the other two addresses - // TODO: check if we want to always write this. - if owner != sender && owner != receiver { - // cosmwasm_std::debug_print("saving transaction history for owner"); - StoredTx::append_tx(store, &tx, owner)?; - } - // Write to the sender's history if it's different from the receiver - if sender != receiver { - // cosmwasm_std::debug_print("saving transaction history for sender"); - StoredTx::append_tx(store, &tx, sender)?; - } - // Always write to the recipient's history - // cosmwasm_std::debug_print("saving transaction history for receiver"); - StoredTx::append_tx(store, &tx, receiver)?; -*/ } -pub fn store_mint( +pub fn store_mint_action( store: &mut dyn Storage, - api: &dyn Api, - minter: CanonicalAddr, - recipient: CanonicalAddr, + minter: &CanonicalAddr, + recipient: &CanonicalAddr, amount: u128, memo: Option, block: &cosmwasm_std::BlockInfo, -) -> StdResult<()> { +) -> StdResult { let action = StoredTxAction::mint( - minter, - recipient + minter.clone(), + recipient.clone() ); - let id = append_new_stored_tx(store, &action, amount, memo, block)?; - -/* - if minter != recipient { - StoredTx::append_tx(store, &tx, &recipient)?; - } - - StoredTx::append_tx(store, &tx, &minter)?; -*/ - - Ok(()) + append_new_stored_tx(store, &action, amount, memo, block) } #[allow(clippy::too_many_arguments)] -pub fn store_burn( +pub fn store_burn_action( store: &mut dyn Storage, - api: &dyn Api, owner: CanonicalAddr, burner: CanonicalAddr, amount: u128, memo: Option, block: &cosmwasm_std::BlockInfo, -) -> StdResult<()> { +) -> StdResult { let action = StoredTxAction::burn( owner, burner ); - let id = append_new_stored_tx(store, &action, amount, memo, block)?; - -/* - if burner != owner { - StoredTx::append_tx(store, &tx, &owner)?; - } - - StoredTx::append_tx(store, &tx, &burner)?; -*/ - - Ok(()) + append_new_stored_tx(store, &action, amount, memo, block) } pub fn store_deposit( From d90727688ef7d807e2fa9e9a9513ab5e045b54ff Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 15 Jun 2024 10:36:02 +1200 Subject: [PATCH 28/87] dwb for additional tx types --- src/contract.rs | 163 ++++++++++++++++++++----------------- src/dwb.rs | 35 +++----- src/msg.rs | 18 +--- src/transaction_history.rs | 59 +++++--------- 4 files changed, 128 insertions(+), 147 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 07f1d95b..3e08fac4 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -9,8 +9,7 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{random_in_range, AccountTxsStore, DelayedWriteBuffer, ACCOUNT_TXS, ACCOUNT_TX_COUNT, DWB, DWB_LEN, DWB_MAX_TX_EVENTS, TX_NODES}; -use crate::msg::TxWithCoins; +use crate::dwb::{AccountTxsStore, DelayedWriteBuffer, ACCOUNT_TXS, ACCOUNT_TX_COUNT, DWB, TX_NODES}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -21,7 +20,7 @@ use crate::state::{ }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - store_burn_action, store_deposit, store_mint_action, store_redeem, store_transfer_action, Tx, TxAction + store_burn_action, store_deposit, store_mint_action, store_redeem, store_transfer_action, Tx, }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -78,7 +77,8 @@ pub fn instantiate( &mut rng, &raw_admin, &balance_address, - amount, + amount, + msg.symbol.clone(), Some("Initial Balance".to_string()), &env.block )?; @@ -146,7 +146,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S denom, .. } if contract_status == ContractStatusLevel::StopAllButRedeems => { - try_redeem(deps, env, info, amount, denom) + try_redeem(deps, env, info, &mut rng, amount, denom) } _ => Err(StdError::generic_err( "This contract is stopped and this action is not allowed", @@ -166,7 +166,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, denom, .. - } => try_redeem(deps, env, info, amount, denom), + } => try_redeem(deps, env, info, &mut rng, amount, denom), // Base ExecuteMsg::Transfer { @@ -688,26 +688,6 @@ pub fn query_transactions( } } - // TODO handle deposit denom - let symbol = CONFIG.load(deps.storage)?.symbol; - let txs = txs.iter().map(|tx| { - let denom = match tx.action { - TxAction::Deposit { } => "uscrt".to_string(), - _ => symbol.clone() - }; - TxWithCoins { - id: tx.id, - action: tx.action.clone(), - coins: Coin { - denom, - amount: tx.amount, - }, - memo: tx.memo.clone(), - block_height: tx.block_height, - block_time: tx.block_time, - } - }).collect(); - let result = QueryAnswer::TransactionHistory { txs, total: Some(total as u64), @@ -816,6 +796,7 @@ fn try_mint_impl( minter: Addr, recipient: Addr, amount: Uint128, + denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { @@ -823,7 +804,7 @@ fn try_mint_impl( let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; let raw_minter = deps.api.addr_canonicalize(minter.as_str())?; - perform_mint(deps.storage, rng, &raw_minter, &raw_recipient, raw_amount, memo, block)?; + perform_mint(deps.storage, rng, &raw_minter, &raw_recipient, raw_amount, denom, memo, block)?; // remove from supply let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; @@ -878,6 +859,7 @@ fn try_mint( info.sender, recipient, Uint128::new(minted_amount), + constants.symbol, memo, &env.block, )?; @@ -920,6 +902,7 @@ fn try_batch_mint( info.sender.clone(), recipient, Uint128::new(actual_amount), + constants.symbol.clone(), action.memo, &env.block, )?; @@ -1104,9 +1087,8 @@ fn try_deposit( store_deposit( deps.storage, - &sender_address, raw_amount, - //"uscrt".to_string(), + "uscrt".to_string(), &env.block, )?; @@ -1117,6 +1099,7 @@ fn try_redeem( deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, amount: Uint128, denom: Option, ) -> StdResult { @@ -1147,14 +1130,21 @@ fn try_redeem( let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; let amount_raw = amount.u128(); - BalancesStore::update_balance( + let tx_id = store_redeem( deps.storage, - &sender_address, - amount_raw, - false, - "redeem", + amount.u128(), + constants.symbol, + &env.block, )?; + // load delayed write buffer + let mut dwb = DWB.load(deps.storage)?; + + // settle the signer's account in buffer + dwb.settle_sender_or_owner_account(deps.storage, rng, &sender_address, tx_id, amount_raw, "redeem")?; + + DWB.save(deps.storage, &dwb)?; + let total_supply = TOTAL_SUPPLY.load(deps.storage)?; if let Some(total_supply) = total_supply.checked_sub(amount_raw) { TOTAL_SUPPLY.save(deps.storage, &total_supply)?; @@ -1179,13 +1169,6 @@ fn try_redeem( amount, }]; - store_redeem( - deps.storage, - &sender_address, - amount.u128(), - &env.block, - )?; - let message = CosmosMsg::Bank(BankMsg::Send { to_address: info.sender.clone().into_string(), amount: withdrawal_coins, @@ -1202,6 +1185,7 @@ fn try_transfer_impl( sender: &Addr, recipient: &Addr, amount: Uint128, + denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult<()> { @@ -1215,6 +1199,7 @@ fn try_transfer_impl( &raw_recipient, &raw_sender, amount.u128(), + denom, memo, block, )?; @@ -1234,12 +1219,14 @@ fn try_transfer( ) -> StdResult { let recipient: Addr = deps.api.addr_validate(recipient.as_str())?; + let symbol = CONFIG.load(deps.storage)?.symbol; try_transfer_impl( &mut deps, rng, &info.sender, &recipient, amount, + symbol, memo, &env.block, )?; @@ -1254,6 +1241,8 @@ fn try_batch_transfer( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { + let symbol = CONFIG.load(deps.storage)?.symbol; + for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_transfer_impl( @@ -1262,6 +1251,7 @@ fn try_batch_transfer( &info.sender, &recipient, action.amount, + symbol.clone(), action.memo, &env.block, )?; @@ -1313,6 +1303,7 @@ fn try_send_impl( recipient: Addr, recipient_code_hash: Option, amount: Uint128, + denom: String, memo: Option, msg: Option, block: &cosmwasm_std::BlockInfo, @@ -1323,6 +1314,7 @@ fn try_send_impl( &sender, &recipient, amount, + denom, memo.clone(), block, )?; @@ -1357,6 +1349,8 @@ fn try_send( let recipient = deps.api.addr_validate(recipient.as_str())?; let mut messages = vec![]; + let symbol = CONFIG.load(deps.storage)?.symbol; + try_send_impl( &mut deps, rng, @@ -1365,6 +1359,7 @@ fn try_send( recipient, recipient_code_hash, amount, + symbol, memo, msg, &env.block, @@ -1383,6 +1378,7 @@ fn try_batch_send( actions: Vec, ) -> StdResult { let mut messages = vec![]; + let symbol = CONFIG.load(deps.storage)?.symbol; for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_send_impl( @@ -1393,6 +1389,7 @@ fn try_batch_send( recipient, action.recipient_code_hash, action.amount, + symbol.clone(), action.memo, action.msg, &env.block, @@ -1455,6 +1452,7 @@ fn try_transfer_from_impl( owner: &Addr, recipient: &Addr, amount: Uint128, + denom: String, memo: Option, ) -> StdResult<()> { let raw_amount = amount.u128(); @@ -1471,6 +1469,7 @@ fn try_transfer_from_impl( &raw_recipient, &raw_spender, raw_amount, + denom, memo, &env.block, )?; @@ -1491,6 +1490,7 @@ fn try_transfer_from( ) -> StdResult { let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; + let symbol = CONFIG.load(deps.storage)?.symbol; try_transfer_from_impl( &mut deps, rng, @@ -1499,6 +1499,7 @@ fn try_transfer_from( &owner, &recipient, amount, + symbol, memo, )?; @@ -1512,6 +1513,7 @@ fn try_batch_transfer_from( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { + let symbol = CONFIG.load(deps.storage)?.symbol; for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; @@ -1523,6 +1525,7 @@ fn try_batch_transfer_from( &owner, &recipient, action.amount, + symbol.clone(), action.memo, )?; } @@ -1549,6 +1552,7 @@ fn try_send_from_impl( msg: Option, ) -> StdResult<()> { let spender = info.sender.clone(); + let symbol = CONFIG.load(deps.storage)?.symbol; try_transfer_from_impl( deps, rng, @@ -1557,6 +1561,7 @@ fn try_send_from_impl( &owner, &recipient, amount, + symbol, memo.clone(), )?; @@ -1671,7 +1676,8 @@ fn try_burn_from( deps.storage, raw_owner.clone(), raw_burner.clone(), - raw_amount, + raw_amount, + constants.symbol, memo, &env.block )?; @@ -1729,7 +1735,8 @@ fn try_batch_burn_from( deps.storage, raw_owner.clone(), raw_spender.clone(), - amount, + amount, + constants.symbol.clone(), action.memo.clone(), &env.block )?; @@ -1936,7 +1943,8 @@ fn try_burn( deps.storage, raw_burn_address.clone(), raw_burn_address.clone(), - raw_amount, + raw_amount, + constants.symbol, memo, &env.block )?; @@ -1969,11 +1977,12 @@ fn perform_transfer( to: &CanonicalAddr, sender: &CanonicalAddr, amount: u128, + denom: String, memo: Option, block: &BlockInfo, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id - let tx_id = store_transfer_action(store, from, sender, to, amount, memo, block)?; + let tx_id = store_transfer_action(store, from, sender, to, amount, denom, memo, block)?; // load delayed write buffer let mut dwb = DWB.load(store)?; @@ -2000,11 +2009,12 @@ fn perform_mint( minter: &CanonicalAddr, to: &CanonicalAddr, amount: u128, + denom: String, memo: Option, block: &BlockInfo, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id - let tx_id = store_mint_action(store, minter, to, amount, memo, block)?; + let tx_id = store_mint_action(store, minter, to, amount, denom, memo, block)?; // load delayed write buffer let mut dwb = DWB.load(store)?; @@ -2076,9 +2086,9 @@ mod tests { use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; use crate::dwb::TX_NODES_COUNT; - use crate::msg::{ResponseStatus, TxWithCoins}; - use crate::msg::{InitConfig, InitialBalance}; + use crate::msg::{InitConfig, InitialBalance, ResponseStatus}; use crate::state::TX_COUNT; + use crate::transaction_history::TxAction; use super::*; @@ -2481,7 +2491,10 @@ mod tests { sender: Addr::unchecked("bob"), recipient: Addr::unchecked("alice") }, - amount: Uint128::from(500_u128), + coins: Coin { + amount: Uint128::from(500_u128), + denom: "SECSEC".to_string(), + }, memo: None, block_time: 1571797419, block_height: 12345 @@ -2492,8 +2505,11 @@ mod tests { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), recipient: Addr::unchecked("alice") - }, - amount: Uint128::from(1000_u128), + }, + coins: Coin { + amount: Uint128::from(1000_u128), + denom: "SECSEC".to_string(), + }, memo: None, block_time: 1571797419, block_height: 12345 @@ -2755,7 +2771,7 @@ mod tests { }; println!("transfers: {transfers:?}"); let expected_transfers = vec![ - TxWithCoins { + Tx { id: 169, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2770,7 +2786,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 168, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2784,7 +2800,8 @@ mod tests { memo: None, block_time: 1571797419, block_height: 12345 - }, TxWithCoins { + }, + Tx { id: 167, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2819,7 +2836,7 @@ mod tests { }; println!("transfers: {transfers:?}"); let expected_transfers = vec![ - TxWithCoins { + Tx { id: 121, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2834,7 +2851,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 120, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2849,7 +2866,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 119, action: TxAction::Transfer { from: Addr::unchecked("alice"), @@ -2864,7 +2881,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 118, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2879,7 +2896,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 117, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2894,7 +2911,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 116, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2933,7 +2950,7 @@ mod tests { }; println!("transfers: {transfers:?}"); let expected_transfers = vec![ - TxWithCoins { + Tx { id: 70, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2949,7 +2966,7 @@ mod tests { 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 69, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2964,7 +2981,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 6, action: TxAction::Transfer { from: Addr::unchecked("alice"), @@ -2979,7 +2996,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 4, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -2994,7 +3011,7 @@ mod tests { block_time: 1571797419, block_height: 12345 }, - TxWithCoins { + Tx { id: 2, action: TxAction::Transfer { from: Addr::unchecked("bob"), @@ -6002,7 +6019,7 @@ mod tests { use crate::transaction_history::TxAction; let expected_transfers = [ - TxWithCoins { + Tx { id: 8, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -6017,7 +6034,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - TxWithCoins { + Tx { id: 7, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -6032,7 +6049,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - TxWithCoins { + Tx { id: 6, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), @@ -6047,7 +6064,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - TxWithCoins { + Tx { id: 5, action: TxAction::Deposit {}, coins: Coin { @@ -6058,7 +6075,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - TxWithCoins { + Tx { id: 4, action: TxAction::Mint { minter: Addr::unchecked("admin".to_string()), @@ -6072,7 +6089,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - TxWithCoins { + Tx { id: 3, action: TxAction::Redeem {}, coins: Coin { @@ -6083,7 +6100,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - TxWithCoins { + Tx { id: 2, action: TxAction::Burn { burner: Addr::unchecked("bob".to_string()), @@ -6097,7 +6114,7 @@ mod tests { block_time: 1571797419, block_height: 12345, }, - TxWithCoins { + Tx { id: 1, action: TxAction::Mint { minter: Addr::unchecked("admin".to_string()), diff --git a/src/dwb.rs b/src/dwb.rs index 8ccc7b48..6dc21eae 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -83,7 +83,7 @@ impl DelayedWriteBuffer { } /// settles an entry at a given index in the buffer - pub fn settle_entry( + fn settle_entry( &mut self, store: &mut dyn Storage, index: usize, @@ -146,7 +146,7 @@ impl DelayedWriteBuffer { /// "releases" a given recipient from the buffer, removing their entry if one exists, in constant-time /// returns the new balance and the buffer entry - pub fn constant_time_release( + fn constant_time_release( &mut self, store: &mut dyn Storage, rng: &mut ContractPrng, @@ -170,7 +170,7 @@ impl DelayedWriteBuffer { Ok((balance, entry)) } - pub fn unique_random_entry(&self, rng: &mut ContractPrng) -> StdResult { + fn unique_random_entry(&self, rng: &mut ContractPrng) -> StdResult { // produce a new random address let mut replacement_address = random_addr(rng); // ensure random addr is not already in dwb (extremely unlikely!!) @@ -324,7 +324,7 @@ impl DelayedWriteBufferEntry { Ok(result) } - pub fn set_recipient(&mut self, val: &CanonicalAddr) -> StdResult<()> { + fn set_recipient(&mut self, val: &CanonicalAddr) -> StdResult<()> { let val_slice = val.as_slice(); if val_slice.len() != DWB_RECIPIENT_BYTES { return Err(StdError::generic_err("Set dwb recipient error")); @@ -398,7 +398,7 @@ impl DelayedWriteBufferEntry { /// adds a tx node to the linked list /// returns: the new head node - pub fn add_tx_node(&mut self, store: &mut dyn Storage, tx_id: u64) -> StdResult { + fn add_tx_node(&mut self, store: &mut dyn Storage, tx_id: u64) -> StdResult { let tx_node = TxNode { tx_id, next: self.head_node()?, @@ -416,7 +416,7 @@ impl DelayedWriteBufferEntry { // adds some amount to the total amount for all txs in the entry linked list // returns: the new amount - pub fn add_amount(&mut self, add_tx_amount: u128) -> StdResult { + fn add_amount(&mut self, add_tx_amount: u128) -> StdResult { // change this to safe_add if your coin needs to store amount in buffer as u128 (e.g. 18 decimals) let mut amount = self.amount()?; let add_tx_amount_u64 = add_tx_amount @@ -544,28 +544,19 @@ impl AccountTxsStore { #[inline] fn constant_time_is_not_zero(value: i32) -> u32 { - return (((value | -value) >> 31) & 1) as u32; + (((value | -value) >> 31) & 1) as u32 } #[inline] fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { - return (then * condition as usize) | (els * (1 - condition as usize)); + (then * condition as usize) | (els * (1 - condition as usize)) } #[cfg(test)] mod tests { - use std::any::Any; - - use cosmwasm_std::{testing::*, Api, Binary, Response, Uint128}; - use cosmwasm_std::{ - from_binary, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, - SubMsg, Timestamp, TransactionInfo, WasmMsg, - }; - use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; - + use cosmwasm_std::{testing::*, Binary, Response, Uint128, OwnedDeps}; use crate::contract::instantiate; - use crate::msg::{InstantiateMsg, ResponseStatus}; - use crate::msg::{InitConfig, InitialBalance}; + use crate::msg::{InstantiateMsg, InitialBalance}; use crate::transaction_history::{append_new_stored_tx, StoredTxAction}; use super::*; @@ -606,7 +597,7 @@ mod tests { init_result.err().unwrap() ); let env = mock_env(); - let info = mock_info("bob", &[]); + let _info = mock_info("bob", &[]); let recipient = CanonicalAddr::from(ZERO_ADDR); let mut dwb_entry = DelayedWriteBufferEntry::new(recipient).unwrap(); @@ -629,7 +620,7 @@ mod tests { assert_eq!(dwb_entry.list_len().unwrap(), 1u16); // first store the tx information in the global append list of txs and get the new tx id - let mut storage = deps.as_mut().storage; + let storage = deps.as_mut().storage; let from = CanonicalAddr::from(&[2u8; 20]); let sender = CanonicalAddr::from(&[2u8; 20]); let to = CanonicalAddr::from(&[1u8;20]); @@ -638,7 +629,7 @@ mod tests { sender.clone(), to.clone() ); - let tx_id = append_new_stored_tx(storage, &action, 1000u128, Some("memo".to_string()), &env.block).unwrap(); + let tx_id = append_new_stored_tx(storage, &action, 1000u128, "uscrt".to_string(), Some("memo".to_string()), &env.block).unwrap(); let result = dwb_entry.add_tx_node(storage, tx_id).unwrap(); assert_eq!(dwb_entry.head_node().unwrap(), result); diff --git a/src/msg.rs b/src/msg.rs index 61c33aa7..23f1dbf2 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -3,8 +3,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{batch, transaction_history::TxAction}; -use cosmwasm_std::{Addr, Api, Binary, Coin, StdError, StdResult, Uint128}; +use crate::{batch, transaction_history::Tx}; +use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128}; use secret_toolkit::permit::Permit; #[cfg_attr(test, derive(Eq, PartialEq))] @@ -495,7 +495,7 @@ pub enum QueryAnswer { amount: Uint128, }, TransactionHistory { - txs: Vec, + txs: Vec, total: Option, }, ViewingKeyError { @@ -506,18 +506,6 @@ pub enum QueryAnswer { }, } -#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct TxWithCoins { - pub id: u64, - pub action: TxAction, - pub coins: Coin, - #[serde(skip_serializing_if = "Option::is_none")] - pub memo: Option, - pub block_time: u64, - pub block_height: u64, -} - #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] pub struct AllowanceGivenResult { pub spender: Addr, diff --git a/src/transaction_history.rs b/src/transaction_history.rs index e35f025e..2bd81136 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -30,17 +30,16 @@ pub enum TxAction { } // Note that id is a globally incrementing counter. -// Since it's 64 bits long, even at 50 tx/s it would take -// over 11 billion years for it to rollback. I'm pretty sure -// we'll have bigger issues by then. #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] #[serde(rename_all = "snake_case")] pub struct Tx { pub id: u64, pub action: TxAction, - pub amount: Uint128, + pub coins: Coin, #[serde(skip_serializing_if = "Option::is_none")] pub memo: Option, + // The block time and block height are optional so that the JSON schema + // reflects that some SNIP-20 contracts may not include this info. pub block_time: u64, pub block_height: u64, } @@ -210,7 +209,7 @@ pub static TRANSACTIONS: Item = Item::new(PREFIX_TXS); #[serde(rename_all = "snake_case")] pub struct StoredTx { action: StoredTxAction, - amount: u128, + coins: StoredCoin, memo: Option, block_time: u64, block_height: u64, @@ -218,15 +217,14 @@ pub struct StoredTx { impl StoredTx { fn new( - id: u64, action: StoredTxAction, - amount: Uint128, + coins: StoredCoin, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> Self { Self { action, - amount: amount.u128(), + coins, memo, block_time: block.time.seconds(), block_height: block.height, @@ -237,7 +235,7 @@ impl StoredTx { Ok(Tx { id, action: self.action.into_tx_action(api)?, - amount: Uint128::from(self.amount), + coins: self.coins.into(), memo: self.memo, block_time: self.block_time, block_height: self.block_height, @@ -283,24 +281,20 @@ impl StoredTx { // Storage functions: -fn increment_tx_count(store: &mut dyn Storage) -> StdResult { - let id = TX_COUNT.load(store).unwrap_or_default() + 1; - TX_COUNT.save(store, &id)?; - Ok(id) -} - pub fn append_new_stored_tx( store: &mut dyn Storage, action: &StoredTxAction, amount: u128, + denom: String, memo: Option, block: &BlockInfo, ) -> StdResult { // tx ids are serialized starting at 1 let serial_id = TX_COUNT.load(store).unwrap_or_default() + 1; + let coins = StoredCoin { denom, amount }; let stored_tx = StoredTx { action: action.clone(), - amount, + coins, memo, block_time: block.time.seconds(), block_height: block.height, @@ -318,6 +312,7 @@ pub fn store_transfer_action( sender: &CanonicalAddr, receiver: &CanonicalAddr, amount: u128, + denom: String, memo: Option, block: &BlockInfo, ) -> StdResult { @@ -326,7 +321,7 @@ pub fn store_transfer_action( sender.clone(), receiver.clone() ); - append_new_stored_tx(store, &action, amount, memo, block) + append_new_stored_tx(store, &action, amount, denom, memo, block) } pub fn store_mint_action( @@ -334,6 +329,7 @@ pub fn store_mint_action( minter: &CanonicalAddr, recipient: &CanonicalAddr, amount: u128, + denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult { @@ -341,7 +337,7 @@ pub fn store_mint_action( minter.clone(), recipient.clone() ); - append_new_stored_tx(store, &action, amount, memo, block) + append_new_stored_tx(store, &action, amount, denom, memo, block) } #[allow(clippy::too_many_arguments)] @@ -350,6 +346,7 @@ pub fn store_burn_action( owner: CanonicalAddr, burner: CanonicalAddr, amount: u128, + denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult { @@ -357,37 +354,25 @@ pub fn store_burn_action( owner, burner ); - append_new_stored_tx(store, &action, amount, memo, block) + append_new_stored_tx(store, &action, amount, denom, memo, block) } pub fn store_deposit( store: &mut dyn Storage, - recipient: &CanonicalAddr, amount: u128, + denom: String, block: &cosmwasm_std::BlockInfo, -) -> StdResult<()> { +) -> StdResult { let action = StoredTxAction::deposit(); - let id = append_new_stored_tx(store, &action, amount, None, block)?; - -/* - StoredTx::append_tx(store, &tx, recipient)?; -*/ - - Ok(()) + append_new_stored_tx(store, &action, amount, denom, None, block) } pub fn store_redeem( store: &mut dyn Storage, - redeemer: &CanonicalAddr, amount: u128, + denom: String, block: &cosmwasm_std::BlockInfo, -) -> StdResult<()> { +) -> StdResult { let action = StoredTxAction::redeem(); - let id = append_new_stored_tx(store, &action, amount, None, block)?; - -/* - StoredTx::append_tx(store, &tx, redeemer)?; -*/ - - Ok(()) + append_new_stored_tx(store, &action, amount, denom, None, block) } \ No newline at end of file From 090463f8a9ec1c6818aa6b2697075babd13fd84b Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 15 Jun 2024 11:52:23 +1200 Subject: [PATCH 29/87] update transfer test for new mint settling --- src/contract.rs | 65 +++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 3e08fac4..f7f29e24 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -2357,7 +2357,8 @@ mod tests { ); let tx_nodes_count = TX_NODES_COUNT.load(&deps.storage).unwrap_or_default(); - assert_eq!(0, tx_nodes_count); + // should be 2 because we minted 5000 to bob at initialization + assert_eq!(2, tx_nodes_count); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(1, tx_count); // due to mint @@ -2390,13 +2391,13 @@ mod tests { let dwb = DWB.load(&deps.storage).unwrap(); println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter - assert_eq!(63, dwb.empty_space_counter); + assert_eq!(62, dwb.empty_space_counter); // assert first entry has correct information for alice - let alice_entry = dwb.entries[1]; + let alice_entry = dwb.entries[2]; assert_eq!(1, alice_entry.list_len().unwrap()); assert_eq!(1000, alice_entry.amount().unwrap()); // the id of the head_node - assert_eq!(2, alice_entry.head_node().unwrap()); + assert_eq!(4, alice_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(2, tx_count); @@ -2434,13 +2435,13 @@ mod tests { let dwb = DWB.load(&deps.storage).unwrap(); println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter - assert_eq!(62, dwb.empty_space_counter); - // assert entry has correct information for alice - let charlie_entry = dwb.entries[2]; + assert_eq!(61, dwb.empty_space_counter); + // assert entry has correct information for charlie + let charlie_entry = dwb.entries[3]; assert_eq!(1, charlie_entry.list_len().unwrap()); assert_eq!(100, charlie_entry.amount().unwrap()); // the id of the head_node - assert_eq!(4, charlie_entry.head_node().unwrap()); + assert_eq!(6, charlie_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(3, tx_count); @@ -2466,13 +2467,13 @@ mod tests { let dwb = DWB.load(&deps.storage).unwrap(); println!("DWB: {dwb:?}"); // assert we have not decremented empty_space_counter - assert_eq!(62, dwb.empty_space_counter); + assert_eq!(61, dwb.empty_space_counter); // assert entry has correct information for alice - let alice_entry = dwb.entries[1]; + let alice_entry = dwb.entries[2]; assert_eq!(2, alice_entry.list_len().unwrap()); assert_eq!(1500, alice_entry.amount().unwrap()); // the id of the head_node - assert_eq!(6, alice_entry.head_node().unwrap()); + assert_eq!(8, alice_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(4, tx_count); @@ -2549,13 +2550,13 @@ mod tests { println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter - assert_eq!(61, dwb.empty_space_counter); + assert_eq!(60, dwb.empty_space_counter); // assert entry has correct information for ernie - let ernie_entry = dwb.entries[3]; + let ernie_entry = dwb.entries[4]; assert_eq!(1, ernie_entry.list_len().unwrap()); assert_eq!(200, ernie_entry.amount().unwrap()); // the id of the head_node - assert_eq!(8, ernie_entry.head_node().unwrap()); + assert_eq!(10, ernie_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(5, tx_count); @@ -2588,18 +2589,18 @@ mod tests { println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter - assert_eq!(60, dwb.empty_space_counter); + assert_eq!(59, dwb.empty_space_counter); // assert entry has correct information for ernie - let dora_entry = dwb.entries[4]; + let dora_entry = dwb.entries[5]; assert_eq!(1, dora_entry.list_len().unwrap()); assert_eq!(50, dora_entry.amount().unwrap()); // the id of the head_node - assert_eq!(10, dora_entry.head_node().unwrap()); + assert_eq!(12, dora_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(6, tx_count); // now we will send to 60 more addresses to fill up the buffer - for i in 1..=60 { + for i in 1..=59 { let recipient = format!("receipient{i}"); // now send 1 to recipient from bob let handle_msg = ExecuteMsg::Transfer { @@ -2616,7 +2617,7 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); } - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 60, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59, BalancesStore::load(&deps.storage, &bob_addr)); let dwb = DWB.load(&deps.storage).unwrap(); println!("DWB: {dwb:?}"); @@ -2640,7 +2641,7 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 60 - 1, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1, BalancesStore::load(&deps.storage, &bob_addr)); let dwb = DWB.load(&deps.storage).unwrap(); println!("DWB: {dwb:?}"); @@ -2661,7 +2662,7 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 60 - 1 - 1, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1 - 1, BalancesStore::load(&deps.storage, &bob_addr)); let dwb = DWB.load(&deps.storage).unwrap(); println!("DWB: {dwb:?}"); @@ -2772,7 +2773,7 @@ mod tests { println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { - id: 169, + id: 168, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2787,7 +2788,7 @@ mod tests { block_height: 12345 }, Tx { - id: 168, + id: 167, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2802,7 +2803,7 @@ mod tests { block_height: 12345 }, Tx { - id: 167, + id: 166, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2837,7 +2838,7 @@ mod tests { println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { - id: 121, + id: 120, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2852,7 +2853,7 @@ mod tests { block_height: 12345 }, Tx { - id: 120, + id: 119, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2867,7 +2868,7 @@ mod tests { block_height: 12345 }, Tx { - id: 119, + id: 118, action: TxAction::Transfer { from: Addr::unchecked("alice"), sender: Addr::unchecked("alice"), @@ -2882,7 +2883,7 @@ mod tests { block_height: 12345 }, Tx { - id: 118, + id: 117, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2897,7 +2898,7 @@ mod tests { block_height: 12345 }, Tx { - id: 117, + id: 116, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2912,7 +2913,7 @@ mod tests { block_height: 12345 }, Tx { - id: 116, + id: 115, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2951,7 +2952,7 @@ mod tests { println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { - id: 70, + id: 69, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -2967,7 +2968,7 @@ mod tests { block_height: 12345 }, Tx { - id: 69, + id: 68, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), From fbdafa3fb8f181a556911332a9570470ee06e991 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 15 Jun 2024 17:02:03 +1200 Subject: [PATCH 30/87] deposit and cleanup --- src/contract.rs | 93 ++++++++++++++++++++++++-------------- src/dwb.rs | 9 +--- src/transaction_history.rs | 57 ++--------------------- 3 files changed, 65 insertions(+), 94 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index f7f29e24..c257104a 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -20,7 +20,7 @@ use crate::state::{ }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - store_burn_action, store_deposit, store_mint_action, store_redeem, store_transfer_action, Tx, + store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -160,7 +160,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S let response = match msg.clone() { // Native ExecuteMsg::Deposit { .. } => { - try_deposit(deps, env, info) + try_deposit(deps, env, info, &mut rng) } ExecuteMsg::Redeem { amount, @@ -806,18 +806,6 @@ fn try_mint_impl( perform_mint(deps.storage, rng, &raw_minter, &raw_recipient, raw_amount, denom, memo, block)?; - // remove from supply - let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; - - if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { - total_supply = new_total_supply; - } else { - return Err(StdError::generic_err( - "You're trying to burn more than is available in the total supply", - )); - } - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - Ok(()) } @@ -1043,6 +1031,7 @@ fn try_deposit( deps: DepsMut, env: Env, info: MessageInfo, + rng: &mut ContractPrng, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; @@ -1077,20 +1066,7 @@ fn try_deposit( let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; - BalancesStore::update_balance( - deps.storage, - &sender_address, - raw_amount, - true, - "", - )?; - - store_deposit( - deps.storage, - raw_amount, - "uscrt".to_string(), - &env.block, - )?; + perform_deposit(deps.storage, rng, &sender_address, raw_amount, "uscrt".to_string(), &env.block)?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?)) } @@ -1130,7 +1106,7 @@ fn try_redeem( let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; let amount_raw = amount.u128(); - let tx_id = store_redeem( + let tx_id = store_redeem_action( deps.storage, amount.u128(), constants.symbol, @@ -2032,6 +2008,28 @@ fn perform_mint( Ok(()) } +fn perform_deposit( + store: &mut dyn Storage, + rng: &mut ContractPrng, + to: &CanonicalAddr, + amount: u128, + denom: String, + block: &BlockInfo, +) -> StdResult<()> { + // first store the tx information in the global append list of txs and get the new tx id + let tx_id = store_deposit_action(store, amount, denom, block)?; + + // load delayed write buffer + let mut dwb = DWB.load(store)?; + + // add the tx info for the recipient to the buffer + dwb.add_recipient(store, rng, to, tx_id, amount)?; + + DWB.save(store, &dwb)?; + + Ok(()) +} + fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdResult { RevokedPermits::revoke_permit( deps.storage, @@ -2078,8 +2076,8 @@ fn is_valid_symbol(symbol: &str) -> bool { mod tests { use std::any::Any; - use cosmwasm_std::{testing::*, Api}; use cosmwasm_std::{ + testing::*, Api, from_binary, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, WasmMsg, }; @@ -2314,10 +2312,12 @@ mod tests { } #[test] - fn test_total_supply_overflow() { + fn test_total_supply_overflow_dwb() { + // with this implementation of dwbs the max amount a user can get transferred or minted is u64::MAX + // for 18 digit coins, u128 amounts might be stored in the dwb (see `fn add_amount` in dwb.rs) let (init_result, _deps) = init_helper(vec![InitialBalance { address: "lebron".to_string(), - amount: Uint128::new(u128::max_value()), + amount: Uint128::new(u64::max_value().into()), }]); assert!( init_result.is_ok(), @@ -2325,6 +2325,7 @@ mod tests { init_result.err().unwrap() ); + /* let (init_result, _deps) = init_helper(vec![ InitialBalance { address: "lebron".to_string(), @@ -2340,6 +2341,7 @@ mod tests { error, "The sum of all initial balances exceeds the maximum possible total supply" ); + */ } // Handle tests @@ -4430,7 +4432,32 @@ mod tests { .api .addr_canonicalize(Addr::unchecked("lebron".to_string()).as_str()) .unwrap(); - assert_eq!(BalancesStore::load(&deps.storage, &canonical), 6000) + + // stored balance not updated, still in dwb + assert_ne!(BalancesStore::load(&deps.storage, &canonical), 6000); + + let create_vk_msg = ExecuteMsg::CreateViewingKey { + entropy: "34".to_string(), + padding: None, + }; + let info = mock_info("lebron", &[]); + let handle_response = execute(deps.as_mut(), mock_env(), info, create_vk_msg).unwrap(); + let vk = match from_binary(&handle_response.data.unwrap()).unwrap() { + ExecuteAnswer::CreateViewingKey { key } => key, + _ => panic!("Unexpected result from handle"), + }; + + let query_balance_msg = QueryMsg::Balance { + address: "lebron".to_string(), + key: vk, + }; + + let query_response = query(deps.as_ref(), mock_env(), query_balance_msg).unwrap(); + let balance = match from_binary(&query_response).unwrap() { + QueryAnswer::Balance { amount } => amount, + _ => panic!("Unexpected result from query"), + }; + assert_eq!(balance, Uint128::new(6000)); } #[test] diff --git a/src/dwb.rs b/src/dwb.rs index 6dc21eae..d961e636 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -77,11 +77,6 @@ impl DelayedWriteBuffer { }) } - #[inline] - pub fn saturated(&self) -> bool { - self.empty_space_counter == 0 - } - /// settles an entry at a given index in the buffer fn settle_entry( &mut self, @@ -608,13 +603,13 @@ mod tests { assert_eq!(dwb_entry.head_node().unwrap(), 0u64); assert_eq!(dwb_entry.list_len().unwrap(), 0u16); - let canonical_addr = CanonicalAddr::from(&[1u8; 20]); + let canonical_addr = CanonicalAddr::from(&[1u8; DWB_RECIPIENT_BYTES]); dwb_entry.set_recipient(&canonical_addr).unwrap(); dwb_entry.set_amount(1).unwrap(); dwb_entry.set_head_node(1).unwrap(); dwb_entry.set_list_len(1).unwrap(); - assert_eq!(dwb_entry.recipient().unwrap(), CanonicalAddr::from(&[1u8; 20])); + assert_eq!(dwb_entry.recipient().unwrap(), CanonicalAddr::from(&[1u8; DWB_RECIPIENT_BYTES])); assert_eq!(dwb_entry.amount().unwrap(), 1u64); assert_eq!(dwb_entry.head_node().unwrap(), 1u64); assert_eq!(dwb_entry.list_len().unwrap(), 1u16); diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 2bd81136..2997d8e7 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{Addr, Api, BlockInfo, CanonicalAddr, Coin, StdError, StdResult, Storage, Uint128}; -use secret_toolkit::storage::{AppendStore, Item}; +use secret_toolkit::storage::Item; use crate::state::TX_COUNT; @@ -216,21 +216,6 @@ pub struct StoredTx { } impl StoredTx { - fn new( - action: StoredTxAction, - coins: StoredCoin, - memo: Option, - block: &cosmwasm_std::BlockInfo, - ) -> Self { - Self { - action, - coins, - memo, - block_time: block.time.seconds(), - block_height: block.height, - } - } - pub fn into_humanized(self, api: &dyn Api, id: u64) -> StdResult { Ok(Tx { id, @@ -241,42 +226,6 @@ impl StoredTx { block_height: self.block_height, }) } - -/* - fn append_tx( - store: &mut dyn Storage, - tx: &StoredTx, - for_address: &Addr, - ) -> StdResult<()> { - let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes()); - current_addr_store.push(store, tx) - } - - pub fn get_txs( - storage: &dyn Storage, - api: &dyn Api, - for_address: Addr, - page: u32, - page_size: u32, - ) -> StdResult<(Vec, u64)> { - let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes()); - let len = current_addr_store.get_len(storage)? as u64; - - // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size` - // txs from the start. - let tx_iter = current_addr_store - .iter(storage)? - .rev() - .skip((page * page_size) as _) - .take(page_size as _); - - // The `and_then` here flattens the `StdResult>` to an `StdResult` - let txs: StdResult> = tx_iter - .map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x)) - .collect(); - txs.map(|txs| (txs, len)) - } -*/ } // Storage functions: @@ -357,7 +306,7 @@ pub fn store_burn_action( append_new_stored_tx(store, &action, amount, denom, memo, block) } -pub fn store_deposit( +pub fn store_deposit_action( store: &mut dyn Storage, amount: u128, denom: String, @@ -367,7 +316,7 @@ pub fn store_deposit( append_new_stored_tx(store, &action, amount, denom, None, block) } -pub fn store_redeem( +pub fn store_redeem_action( store: &mut dyn Storage, amount: u128, denom: String, From 8cae7b350b77f276665b897630b4941a0a02f498 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 15 Jun 2024 19:34:28 +1200 Subject: [PATCH 31/87] fix wasm compilation, tests --- Cargo.lock | 297 ++++++++++++++---------------------------------- Cargo.toml | 9 +- src/contract.rs | 49 ++++---- src/dwb.rs | 10 +- 4 files changed, 117 insertions(+), 248 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54d83868..d695d8d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -27,9 +27,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -73,15 +73,15 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.0.97" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "cfg-if" @@ -91,9 +91,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "cosmwasm-derive" @@ -106,9 +112,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "1.2.2" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b99f612ccf162940ae2eef9f370ee37cf2ddcf4a9a8f5ee15ec6b46a5ecd2e" +checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -119,9 +125,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.2" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92ceea61033cb69c336abf673da017ddf251fc4e26e0cdd387eaf8bedb14e49" +checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6" dependencies = [ "proc-macro2", "quote", @@ -130,9 +136,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -210,9 +216,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", @@ -221,9 +227,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" @@ -261,7 +267,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.6", + "digest 0.10.7", "ff", "generic-array", "group", @@ -288,23 +294,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -312,13 +306,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -347,26 +341,20 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-literal" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" - [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "k256" @@ -377,26 +365,26 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.6", + "sha2 0.10.8", ] [[package]] name = "libc" -version = "0.2.140" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "once_cell" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "pkcs8" @@ -416,9 +404,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -432,29 +420,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -474,21 +439,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.5.1" @@ -504,15 +454,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "remain" version = "0.2.14" @@ -521,7 +462,7 @@ checksum = "46aef80f842736de545ada6ec65b81ee91504efd6853f4b96de7414c42ae7443" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] @@ -541,39 +482,20 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.6", -] - -[[package]] -name = "rust-crypto" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" -dependencies = [ - "gcc", - "libc", - "rand 0.3.23", - "rustc-serialize", - "time", + "digest 0.10.7", ] -[[package]] -name = "rustc-serialize" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" - [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -583,14 +505,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -631,7 +553,7 @@ version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8535d61c88d0a6c222df2cebb69859d8e9ba419a299a1bc84c904b0d9c00c7b2" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -691,7 +613,7 @@ dependencies = [ "rand_core 0.6.4", "secp256k1", "secret-cosmwasm-std", - "sha2 0.10.6", + "sha2 0.10.8", ] [[package]] @@ -751,7 +673,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d89a0b69fa9b12735a612fa30e6e7e48130943982f1783b7ddd5c46ed09e921" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", "schemars", "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -763,9 +685,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.158" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -790,31 +712,31 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.158" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.66", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -836,13 +758,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -851,7 +773,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -859,21 +781,17 @@ dependencies = [ name = "snip20-reference-impl" version = "1.0.0" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", + "constant_time_eq", "cosmwasm-schema", - "hex", - "hex-literal", - "rand 0.8.5", - "rust-crypto", + "rand", "schemars", - "secret-cosmwasm-crypto", "secret-cosmwasm-std", "secret-cosmwasm-storage", "secret-toolkit", "secret-toolkit-crypto", "serde", "serde-big-array", - "thiserror", ] [[package]] @@ -894,9 +812,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -911,9 +829,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.63" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -922,40 +840,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", -] - -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "syn 2.0.66", ] [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uint" @@ -971,9 +878,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "version_check" @@ -981,42 +888,14 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "zeroize" -version = "1.5.7" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 59290f14..58b4df6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,20 +34,15 @@ backtraces = ["cosmwasm-std/backtraces"] [dependencies] cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } -cosmwasm-crypto = { package = "secret-cosmwasm-crypto", version = "1.1.11" } rand = { version = "0.8.5", default-features = false } secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] } -secret-toolkit-crypto = { version = "0.10.0", features = ["rand", "hash", "ecc-secp256k1"] } +secret-toolkit-crypto = { version = "0.10.0", default-features = false, features = ["hash"] } schemars = "0.8.12" serde = { version = "1.0.158", default-features = false, features = ["derive"] } serde-big-array = "0.5.1" base64 = "0.21.0" -rust-crypto = "0.2.36" - -hex = "0.4" -hex-literal = "0.3.1" -thiserror = "1.0.13" +constant_time_eq = "0.3.0" [dev-dependencies] cosmwasm-schema = { version = "1.1.8" } diff --git a/src/contract.rs b/src/contract.rs index c257104a..679122c5 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -603,12 +603,12 @@ pub fn query_transactions( let txs_in_dwb_count = txs_in_dwb_count as u32; if start < txs_in_dwb_count && end < txs_in_dwb_count { // option 1, start and end are both in dwb - println!("OPTION 1"); + //println!("OPTION 1"); txs = txs_in_dwb[start as usize..end as usize].to_vec(); // reverse chronological } else if start < txs_in_dwb_count && end >= txs_in_dwb_count { // option 2, start is in dwb and end is in settled txs // in this case, we do not need to search for txs, just begin at last bundle and move backwards - println!("OPTION 2"); + //println!("OPTION 2"); txs = txs_in_dwb[start as usize..].to_vec(); // reverse chronological let mut txs_left = (end - start).saturating_sub(txs.len() as u32); let tx_bundles_store = ACCOUNT_TXS.add_suffix(account_slice); @@ -638,11 +638,8 @@ pub fn query_transactions( // bundle tx offsets are chronological, but we need reverse chronological // so get the settled start index as if order is reversed - println!("OPTION 3"); - println!("start: {start}"); - println!("txs_in_dwb_count: {txs_in_dwb_count}"); + //println!("OPTION 3"); let settled_start = settled_tx_count.saturating_sub(start - txs_in_dwb_count).saturating_sub(1); - println!("settled_start: {settled_start}"); if let Some((bundle_idx, tx_bundle, start_at)) = AccountTxsStore::find_start_bundle( deps.storage, @@ -650,7 +647,6 @@ pub fn query_transactions( settled_start )? { let mut txs_left = end - start; - println!("txs_left: {txs_left}"); let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; let list_len = tx_bundle.list_len as u32; @@ -661,7 +657,6 @@ pub fn query_transactions( // get the rest of the txs in this bundle and then go back through history txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); txs_left = txs_left.saturating_sub(list_len - start_at); - println!("txs_left: {txs_left}"); if bundle_idx > 0 && txs_left > 0 { // get the next earlier bundle @@ -2391,7 +2386,7 @@ mod tests { assert_ne!(1000, BalancesStore::load(&deps.storage, &alice_addr)); let dwb = DWB.load(&deps.storage).unwrap(); - println!("DWB: {dwb:?}"); + //println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter assert_eq!(62, dwb.empty_space_counter); // assert first entry has correct information for alice @@ -2403,10 +2398,10 @@ mod tests { let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(2, tx_count); - let tx_node1 = TX_NODES.add_suffix(&1u64.to_be_bytes()).load(&deps.storage).unwrap(); - println!("tx node 1: {tx_node1:?}"); - let tx_node2 = TX_NODES.add_suffix(&2u64.to_be_bytes()).load(&deps.storage).unwrap(); - println!("tx node 2: {tx_node2:?}"); + //let tx_node1 = TX_NODES.add_suffix(&1u64.to_be_bytes()).load(&deps.storage).unwrap(); + //println!("tx node 1: {tx_node1:?}"); + //let tx_node2 = TX_NODES.add_suffix(&2u64.to_be_bytes()).load(&deps.storage).unwrap(); + //println!("tx node 2: {tx_node2:?}"); // now send 100 to charlie from bob let handle_msg = ExecuteMsg::Transfer { @@ -2435,7 +2430,7 @@ mod tests { assert_ne!(100, BalancesStore::load(&deps.storage, &charlie_addr)); let dwb = DWB.load(&deps.storage).unwrap(); - println!("DWB: {dwb:?}"); + //println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter assert_eq!(61, dwb.empty_space_counter); // assert entry has correct information for charlie @@ -2467,7 +2462,7 @@ mod tests { assert_ne!(1500, BalancesStore::load(&deps.storage, &alice_addr)); let dwb = DWB.load(&deps.storage).unwrap(); - println!("DWB: {dwb:?}"); + //println!("DWB: {dwb:?}"); // assert we have not decremented empty_space_counter assert_eq!(61, dwb.empty_space_counter); // assert entry has correct information for alice @@ -2549,7 +2544,7 @@ mod tests { assert_ne!(200, BalancesStore::load(&deps.storage, &ernie_addr)); let dwb = DWB.load(&deps.storage).unwrap(); - println!("DWB: {dwb:?}"); + //println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter assert_eq!(60, dwb.empty_space_counter); @@ -2588,7 +2583,7 @@ mod tests { assert_ne!(50, BalancesStore::load(&deps.storage, &dora_addr)); let dwb = DWB.load(&deps.storage).unwrap(); - println!("DWB: {dwb:?}"); + //println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter assert_eq!(59, dwb.empty_space_counter); @@ -2622,7 +2617,7 @@ mod tests { assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59, BalancesStore::load(&deps.storage, &bob_addr)); let dwb = DWB.load(&deps.storage).unwrap(); - println!("DWB: {dwb:?}"); + //println!("DWB: {dwb:?}"); // assert we have filled the buffer assert_eq!(0, dwb.empty_space_counter); @@ -2645,8 +2640,8 @@ mod tests { assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1, BalancesStore::load(&deps.storage, &bob_addr)); - let dwb = DWB.load(&deps.storage).unwrap(); - println!("DWB: {dwb:?}"); + //let dwb = DWB.load(&deps.storage).unwrap(); + //println!("DWB: {dwb:?}"); let recipient = format!("receipient_over_2"); // now send 1 to recipient from bob @@ -2666,8 +2661,8 @@ mod tests { assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1 - 1, BalancesStore::load(&deps.storage, &bob_addr)); - let dwb = DWB.load(&deps.storage).unwrap(); - println!("DWB: {dwb:?}"); + //let dwb = DWB.load(&deps.storage).unwrap(); + //println!("DWB: {dwb:?}"); // now we send 50 transactions to alice from bob for i in 1..=50 { @@ -2772,7 +2767,7 @@ mod tests { QueryAnswer::TransactionHistory { txs, .. } => txs, other => panic!("Unexpected: {:?}", other), }; - println!("transfers: {transfers:?}"); + //println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { id: 168, @@ -2837,7 +2832,7 @@ mod tests { QueryAnswer::TransactionHistory { txs, .. } => txs, other => panic!("Unexpected: {:?}", other), }; - println!("transfers: {transfers:?}"); + //println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { id: 120, @@ -2951,7 +2946,7 @@ mod tests { QueryAnswer::TransactionHistory { txs, .. } => txs, other => panic!("Unexpected: {:?}", other), }; - println!("transfers: {transfers:?}"); + //println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { id: 69, @@ -3030,8 +3025,8 @@ mod tests { block_height: 12345 } ]; - let transfers_len = transfers.len(); - println!("transfers.len(): {transfers_len}"); + //let transfers_len = transfers.len(); + //println!("transfers.len(): {transfers_len}"); assert_eq!(transfers, expected_transfers); // diff --git a/src/dwb.rs b/src/dwb.rs index d961e636..d6b5868c 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,7 +1,7 @@ -use crypto::util::fixed_time_eq; +use constant_time_eq::constant_time_eq; use rand::RngCore; use secret_toolkit_crypto::ContractPrng; -use serde::{Deserialize, Serialize}; +use serde::{Serialize, Deserialize,}; use serde_big_array::BigArray; use cosmwasm_std::{Api, CanonicalAddr, StdError, StdResult, Storage}; use secret_toolkit::storage::{AppendStore, Item}; @@ -180,7 +180,7 @@ impl DelayedWriteBuffer { let mut matched_index: usize = 0; let address = address.as_slice(); for (idx, entry) in self.entries.iter().enumerate().skip(1) { - let equals = fixed_time_eq(address, entry.recipient_slice()) as usize; + let equals = constant_time_eq(address, entry.recipient_slice()) as usize; // an address can only occur once in the buffer matched_index |= idx * equals; } @@ -210,14 +210,14 @@ impl DelayedWriteBuffer { // randomly pick an entry to exclude in case the recipient is not in the buffer let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; - println!("random_exclude_index: {random_exclude_index}"); + //println!("random_exclude_index: {random_exclude_index}"); // index of entry to exclude from selection let exclude_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, random_exclude_index); // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; - println!("random_settle_index: {random_settle_index}"); + //println!("random_settle_index: {random_settle_index}"); // whether or not the buffer is fully saturated yet let if_undersaturated = constant_time_is_not_zero(self.empty_space_counter as i32); From aa75d225e60c924ea21ed3293929ccd873a42369 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 15 Jun 2024 20:30:07 +1200 Subject: [PATCH 32/87] add TESTING dwb query --- Makefile | 2 +- src/contract.rs | 9 +++++++-- src/dwb.rs | 10 ++++++++-- src/msg.rs | 8 ++++++++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 48cb32ad..8db76b36 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ start-server: # CTRL+C to stop docker run -it --rm \ -p 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ -v $$(pwd):/root/code \ - --name secretdev docker pull ghcr.io/scrtlabs/localsecret:v1.13.0-rc.2 + --name secretdev docker pull ghcr.io/scrtlabs/localsecret:v1.13.1 .PHONY: schema schema: diff --git a/src/contract.rs b/src/contract.rs index 679122c5..64c2a001 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -9,7 +9,7 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{AccountTxsStore, DelayedWriteBuffer, ACCOUNT_TXS, ACCOUNT_TX_COUNT, DWB, TX_NODES}; +use crate::dwb::{log_dwb, AccountTxsStore, DelayedWriteBuffer, ACCOUNT_TXS, ACCOUNT_TX_COUNT, DWB, TX_NODES}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -336,6 +336,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::ExchangeRate {} => query_exchange_rate(deps.storage), QueryMsg::Minters { .. } => query_minters(deps), QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query), + // FOR TESTING ONLY! REMOVE + QueryMsg::Dwb { } => log_dwb(deps.storage), _ => viewing_keys_queries(deps, msg), }, RESPONSE_BLOCK_SIZE, @@ -1202,7 +1204,10 @@ fn try_transfer( &env.block, )?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?)) + Ok( + Response::new() + .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?) + ) } fn try_batch_transfer( diff --git a/src/dwb.rs b/src/dwb.rs index d6b5868c..d62cbff0 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -3,10 +3,10 @@ use rand::RngCore; use secret_toolkit_crypto::ContractPrng; use serde::{Serialize, Deserialize,}; use serde_big_array::BigArray; -use cosmwasm_std::{Api, CanonicalAddr, StdError, StdResult, Storage}; +use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, Storage}; use secret_toolkit::storage::{AppendStore, Item}; -use crate::{state::{safe_add, safe_add_u64, BalancesStore,}, transaction_history::{Tx, TRANSACTIONS}}; +use crate::{msg::QueryAnswer, state::{safe_add, safe_add_u64, BalancesStore,}, transaction_history::{Tx, TRANSACTIONS}}; pub const KEY_DWB: &[u8] = b"dwb"; pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt"; @@ -547,6 +547,12 @@ fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { (then * condition as usize) | (els * (1 - condition as usize)) } +/// FOR TESTING ONLY! REMOVE +pub fn log_dwb(storage: &dyn Storage) -> StdResult { + let dwb = DWB.load(storage)?; + to_binary(&QueryAnswer::Dwb { dwb: format!("{:?}", dwb) }) +} + #[cfg(test)] mod tests { use cosmwasm_std::{testing::*, Binary, Response, Uint128, OwnedDeps}; diff --git a/src/msg.rs b/src/msg.rs index 23f1dbf2..dd23e3d2 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -381,6 +381,9 @@ pub enum QueryMsg { permit: Permit, query: QueryWithPermit, }, + + /// FOR TESTING ONLY! REMOVE + Dwb { }, } impl QueryMsg { @@ -504,6 +507,11 @@ pub enum QueryAnswer { Minters { minters: Vec, }, + + /// FOR TESTING ONLY! REMOVE + Dwb { + dwb: String, + }, } #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] From b5093f13955b83f535afbec87bcd5b16e8b690a8 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 16 Jun 2024 08:10:29 +1200 Subject: [PATCH 33/87] update secretdev to localsecret --- tests/integration.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/integration.sh b/tests/integration.sh index dba743ee..37846613 100755 --- a/tests/integration.sh +++ b/tests/integration.sh @@ -18,7 +18,7 @@ declare -A FROM=( # In particular, it's not possible to dynamically expand aliases, but `tx_of` dynamically executes whatever # we specify in its arguments. function secretcli() { - docker exec secretdev /usr/bin/secretd "$@" + docker exec localsecret /usr/bin/secretd "$@" } # Just like `echo`, but prints to stderr @@ -587,7 +587,7 @@ function test_permit() { local expected_error="Error: query result: Generic error: Permit doesn't apply to token \"$contract_addr\", allowed tokens: [\"$wrong_contract\"]" for key in "${KEY[@]}"; do log "permit querying balance for \"$key\" with wrong permit for that contract" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$wrong_contract"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" assert_eq "$result" "$expected_error" @@ -603,7 +603,7 @@ function test_permit() { tx_hash="$(compute_execute "$contract_addr" '{"revoke_permit":{"permit_name":"to_be_revoked"}}' ${FROM[$key]} --gas 250000)" wait_for_compute_tx "$tx_hash" "waiting for revoke_permit from \"$key\" to process" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"to_be_revoked","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' expected_error="Error: query result: Generic error: Permit \"to_be_revoked\" was revoked by account \"${ADDRESS[$key]}" result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" @@ -617,7 +617,7 @@ function test_permit() { local expected_error for key in "${KEY[@]}"; do log "permit querying balance for \"$key\" with params not matching permit" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'") permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"not_blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' expected_error="Error: query result: Generic error: Failed to verify signatures for the given permit" result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" @@ -632,7 +632,7 @@ function test_permit() { local expected_error for key in "${KEY[@]}"; do log "permit querying balance for \"$key\" without the right permission" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' expected_error="Error: query result: Generic error: No permission to query balance, got permissions [History]" result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" @@ -647,7 +647,7 @@ function test_permit() { local expected_error for key in "${KEY[@]}"; do log "permit querying history for \"$key\" without the right permission" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10, "should_filter_decoys":false}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' expected_error="Error: query result: Generic error: No permission to query history, got permissions [Balance]" @@ -668,7 +668,7 @@ function test_permit() { local expected_error for key in "${KEY[@]}"; do log "permit querying allowance for \"$key\" without the right permission" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'") permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' expected_error="Error: query result: Generic error: No permission to query allowance, got permissions [History]" result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" @@ -681,7 +681,7 @@ function test_permit() { local permit_query local expected_error log "permit querying allowance without signer being the owner or spender" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$wrong_permit"') --from a") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$wrong_permit"') --from a") permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"$wrong_contract"'","spender":"'"$wrong_contract"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}' expected_error="Error: query result: Generic error: Cannot query allowance. Requires permit for either owner \"$wrong_contract\" or spender \"$wrong_contract\", got permit for \"${ADDRESS[a]}" result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" @@ -695,7 +695,7 @@ function test_permit() { local expected_output for key in "${KEY[@]}"; do log "permit querying balance for \"$key\"" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}' expected_output="{\"balance\":{\"amount\":\"0\"}}" result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)" @@ -710,7 +710,7 @@ function test_permit() { local expected_output for key in "${KEY[@]}"; do log "permit querying history for \"$key\"" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10, "should_filter_decoys":false}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}' expected_output="{\"transfer_history\":{\"txs\":[],\"total\":0}}" @@ -731,7 +731,7 @@ function test_permit() { local expected_output for key in "${KEY[@]}"; do log "permit querying history for \"$key\"" - permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") + permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'") permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}' expected_output="{\"allowance\":{\"spender\":\"${ADDRESS[$key]}\",\"owner\":\"${ADDRESS[$key]}\",\"allowance\":\"0\",\"expiration\":null}}" From 8212dbf79908933bc0639c5161376a0bd30c39e2 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 17 Jun 2024 09:56:00 +1200 Subject: [PATCH 34/87] add gas logging for testing --- src/contract.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 64c2a001..49b0253e 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,7 +1,7 @@ /// This contract implements SNIP-20 standard: /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md use cosmwasm_std::{ - entry_point, to_binary, Addr, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 + entry_point, to_binary, Addr, Api, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 }; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; @@ -1161,10 +1161,15 @@ fn try_transfer_impl( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, + logs: &mut Vec<(String, String)>, ) -> StdResult<()> { let raw_sender = deps.api.addr_canonicalize(sender.as_str())?; let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; + // TESTING + let gas = deps.api.check_gas()?; + logs.push(("gas1".to_string(), format!("{gas}"))); + perform_transfer( deps.storage, rng, @@ -1175,6 +1180,9 @@ fn try_transfer_impl( denom, memo, block, + // TESTING + deps.api, + logs, )?; Ok(()) @@ -1193,6 +1201,10 @@ fn try_transfer( let recipient: Addr = deps.api.addr_validate(recipient.as_str())?; let symbol = CONFIG.load(deps.storage)?.symbol; + + // TESTING + let mut logs = vec![]; + try_transfer_impl( &mut deps, rng, @@ -1202,11 +1214,20 @@ fn try_transfer( symbol, memo, &env.block, + &mut logs, )?; + // TESTING + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + for log in logs { + resp = resp.add_attribute_plaintext(log.0, log.1); + } + Ok( - Response::new() - .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?) + //Response::new() + // .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?) + resp ) } @@ -1230,6 +1251,8 @@ fn try_batch_transfer( symbol.clone(), action.memo, &env.block, + // TESTING + &mut vec![], )?; } @@ -1293,6 +1316,8 @@ fn try_send_impl( denom, memo.clone(), block, + // TESTING + &mut vec![], )?; try_add_receiver_api_callback( @@ -1448,6 +1473,9 @@ fn try_transfer_from_impl( denom, memo, &env.block, + // TESTING + deps.api, + &mut vec![], )?; Ok(()) @@ -1956,13 +1984,24 @@ fn perform_transfer( denom: String, memo: Option, block: &BlockInfo, + // TESTING + api: &dyn Api, + logs: &mut Vec<(String, String)>, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_transfer_action(store, from, sender, to, amount, denom, memo, block)?; + // TESTING + let gas = api.check_gas()?; + logs.push(("gas2".to_string(), format!("{gas}"))); + // load delayed write buffer let mut dwb = DWB.load(store)?; + // TESTING + let gas = api.check_gas()?; + logs.push(("gas3".to_string(), format!("{gas}"))); + let transfer_str = "transfer"; // settle the owner's account dwb.settle_sender_or_owner_account(store, rng, from, tx_id, amount, transfer_str)?; @@ -1971,11 +2010,23 @@ fn perform_transfer( dwb.settle_sender_or_owner_account(store, rng, sender, tx_id, 0, transfer_str)?; } + // TESTING + let gas = api.check_gas()?; + logs.push(("gas4".to_string(), format!("{gas}"))); + // add the tx info for the recipient to the buffer dwb.add_recipient(store, rng, to, tx_id, amount)?; + // TESTING + let gas = api.check_gas()?; + logs.push(("gas5".to_string(), format!("{gas}"))); + DWB.save(store, &dwb)?; + // TESTING + let gas = api.check_gas()?; + logs.push(("gas6".to_string(), format!("{gas}"))); + Ok(()) } @@ -2391,7 +2442,7 @@ mod tests { assert_ne!(1000, BalancesStore::load(&deps.storage, &alice_addr)); let dwb = DWB.load(&deps.storage).unwrap(); - //println!("DWB: {dwb:?}"); + println!("DWB: {dwb:?}"); // assert we have decremented empty_space_counter assert_eq!(62, dwb.empty_space_counter); // assert first entry has correct information for alice From e06150bb82b3a45249e4c69b0d960bba1880ae98 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Thu, 27 Jun 2024 00:49:27 -0700 Subject: [PATCH 35/87] fix: R5a. and reuse released addr --- src/dwb.rs | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/dwb.rs b/src/dwb.rs index d62cbff0..b061844d 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -153,13 +153,16 @@ impl DelayedWriteBuffer { // locate the position of the entry in the buffer let matched_entry_idx = self.recipient_match(address); - let replacement_entry = self.unique_random_entry(rng)?; + // create a new entry to replace the released one, giving it the same address to avoid introducing random addresses + let replacement_entry = DelayedWriteBufferEntry::new(address.clone()); // get the current entry at the matched index (0 if dummy) let entry = self.entries[matched_entry_idx]; + // add entry amount to the stored balance for the address (will be 0 if dummy) safe_add(&mut balance, entry.amount()? as u128); - // overwrite the entry idx with random addr replacement + + // overwrite the entry idx with replacement self.entries[matched_entry_idx] = replacement_entry; Ok((balance, entry)) @@ -208,44 +211,40 @@ impl DelayedWriteBuffer { // casting to i32 will never overflow, so long as dwb length is limited to a u16 value let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32); - // randomly pick an entry to exclude in case the recipient is not in the buffer - let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize; - //println!("random_exclude_index: {random_exclude_index}"); - - // index of entry to exclude from selection - let exclude_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, random_exclude_index); - - // randomly select any other entry to settle in constant-time (avoiding the reserved 0th position) - let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize; - //println!("random_settle_index: {random_settle_index}"); - // whether or not the buffer is fully saturated yet let if_undersaturated = constant_time_is_not_zero(self.empty_space_counter as i32); // find the next empty entry in the buffer let next_empty_index = (DWB_LEN - self.empty_space_counter) as usize; - // if buffer is not yet saturated, settle the address at the next empty index - let bounded_settle_index = constant_time_if_else(if_undersaturated, next_empty_index, random_settle_index); + // which entry to settle (not yet considering if recipient's entry has capacity in history list) + // if recipient is in buffer or buffer is undersaturated then settle the dummy entry + // otherwise, settle a random entry + let presumptive_settle_index = constant_time_if_else( + if_recipient_in_buffer, 0, + constant_time_if_else(if_undersaturated, 0, + random_in_range(rng, 1, DWB_LEN as u32)? as usize)); // check if we have any open slots in the linked list let if_list_can_grow = constant_time_is_not_zero((DWB_MAX_TX_EVENTS - self.entries[recipient_index].list_len()?) as i32); - // if we would overflow the list, just settle recipient - // TODO: see docs for attack analysis - let actual_settle_index = constant_time_if_else(if_list_can_grow, bounded_settle_index, recipient_index); + // if we would overflow the list by updating the existing entry, then just settle that recipient + let actual_settle_index = constant_time_if_else(if_list_can_grow, presumptive_settle_index, recipient_index); + + // where to write the new/replacement entry + // if recipient is in buffer then update it + // otherwise, if buffer is undersaturated then put new entry at next open slot + // otherwise, the buffer is saturated so replace the entry that is getting settled + let write_index = constant_time_if_else( + if_recipient_in_buffer, recipient_index, + constant_time_if_else(if_undersaturated, next_empty_index, + actual_settle_index)); // settle the entry self.settle_entry(store, actual_settle_index)?; - // replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer - let replacement_entry = self.unique_random_entry(rng)?; - self.entries[actual_settle_index] = replacement_entry; - - // pick the index to where the recipient's entry should be written - let write_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, actual_settle_index); - - // either updates the existing recipient entry, or overwrites the random replacement entry in the settled index + // write the new entry, which either overwrites the existing one for the same recipient, + // replaces a randomly settled one, or inserts into an "empty" slot in the buffer self.entries[write_index] = new_entry; // decrement empty space counter if it is undersaturated and the recipient was not already in the buffer From c2d6ef91a79d4766fc55e071d8e3863424ac4d70 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Thu, 27 Jun 2024 01:33:59 -0700 Subject: [PATCH 36/87] fix: use existing entry address --- src/dwb.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dwb.rs b/src/dwb.rs index b061844d..c8051285 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -153,12 +153,12 @@ impl DelayedWriteBuffer { // locate the position of the entry in the buffer let matched_entry_idx = self.recipient_match(address); - // create a new entry to replace the released one, giving it the same address to avoid introducing random addresses - let replacement_entry = DelayedWriteBufferEntry::new(address.clone()); - // get the current entry at the matched index (0 if dummy) let entry = self.entries[matched_entry_idx]; + // create a new entry to replace the released one, giving it the same address to avoid introducing random addresses + let replacement_entry = DelayedWriteBufferEntry::new(entry.recipient()?)?; + // add entry amount to the stored balance for the address (will be 0 if dummy) safe_add(&mut balance, entry.amount()? as u128); From 6a3a032c3df4e4ae6c2773bf5cbe25c3396d031c Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sun, 30 Jun 2024 03:17:02 -0700 Subject: [PATCH 37/87] dev: bitwise-trie of stored balances --- Cargo.lock | 1 + Cargo.toml | 1 + src/contract.rs | 1 + src/dwb.rs | 24 +-- src/lib.rs | 4 + src/stored_balances.rs | 425 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 444 insertions(+), 12 deletions(-) create mode 100644 src/stored_balances.rs diff --git a/Cargo.lock b/Cargo.lock index d695d8d1..7be22c62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,6 +792,7 @@ dependencies = [ "secret-toolkit-crypto", "serde", "serde-big-array", + "static_assertions", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 58b4df6a..fc90942d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } rand = { version = "0.8.5", default-features = false } secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] } secret-toolkit-crypto = { version = "0.10.0", default-features = false, features = ["hash"] } +static_assertions = "1.1.0" schemars = "0.8.12" serde = { version = "1.0.158", default-features = false, features = ["derive"] } diff --git a/src/contract.rs b/src/contract.rs index 49b0253e..cd5f2328 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -10,6 +10,7 @@ use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; use crate::dwb::{log_dwb, AccountTxsStore, DelayedWriteBuffer, ACCOUNT_TXS, ACCOUNT_TX_COUNT, DWB, TX_NODES}; +use crate::bucket; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, diff --git a/src/dwb.rs b/src/dwb.rs index c8051285..a2a327c3 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -6,7 +6,11 @@ use serde_big_array::BigArray; use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, Storage}; use secret_toolkit::storage::{AppendStore, Item}; -use crate::{msg::QueryAnswer, state::{safe_add, safe_add_u64, BalancesStore,}, transaction_history::{Tx, TRANSACTIONS}}; +use crate::{ + msg::QueryAnswer, + state::{safe_add, safe_add_u64, BalancesStore,}, + transaction_history::{Tx, TRANSACTIONS}, +}; pub const KEY_DWB: &[u8] = b"dwb"; pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt"; @@ -261,6 +265,7 @@ impl DelayedWriteBuffer { const U16_BYTES: usize = 2; const U64_BYTES: usize = 8; +const U128_BYTES: usize = 16; #[cfg(test)] const DWB_RECIPIENT_BYTES: usize = 54; // because mock_api creates rando canonical addr that is 54 bytes long @@ -270,6 +275,10 @@ const DWB_AMOUNT_BYTES: usize = 8; // Max 16 (u128) const DWB_HEAD_NODE_BYTES: usize = 5; // Max 8 (u64) const DWB_LIST_LEN_BYTES: usize = 2; // u16 +const_assert!(DWB_AMOUNT_BYTES <= U128_BYTES); +const_assert!(DWB_HEAD_NODE_BYTES <= U64_BYTES); +const_assert!(DWB_LIST_LEN_BYTES <= U16_BYTES); + const DWB_ENTRY_BYTES: usize = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES + DWB_LIST_LEN_BYTES; pub const ZERO_ADDR: [u8; DWB_RECIPIENT_BYTES] = [0u8; DWB_RECIPIENT_BYTES]; @@ -308,11 +317,11 @@ impl DelayedWriteBufferEntry { }) } - fn recipient_slice(&self) -> &[u8] { + pub fn recipient_slice(&self) -> &[u8] { &self.0[..DWB_RECIPIENT_BYTES] } - fn recipient(&self) -> StdResult { + pub fn recipient(&self) -> StdResult { let result = CanonicalAddr::try_from(self.recipient_slice()) .or(Err(StdError::generic_err("Get dwb recipient error")))?; Ok(result) @@ -340,9 +349,6 @@ impl DelayedWriteBufferEntry { fn set_amount(&mut self, val: u64) -> StdResult<()> { let start = DWB_RECIPIENT_BYTES; let end = start + DWB_AMOUNT_BYTES; - if DWB_AMOUNT_BYTES != U64_BYTES { - return Err(StdError::generic_err("Set dwb amount error")); - } self.0[start..end].copy_from_slice(&val.to_be_bytes()); Ok(()) } @@ -352,9 +358,6 @@ impl DelayedWriteBufferEntry { let end = start + DWB_HEAD_NODE_BYTES; let head_node_slice = &self.0[start..end]; let mut result = [0u8; U64_BYTES]; - if DWB_HEAD_NODE_BYTES > U64_BYTES { - return Err(StdError::generic_err("Get dwb head node error")); - } result[U64_BYTES - DWB_HEAD_NODE_BYTES..].copy_from_slice(head_node_slice); Ok(u64::from_be_bytes(result)) } @@ -383,9 +386,6 @@ impl DelayedWriteBufferEntry { fn set_list_len(&mut self, val: u16) -> StdResult<()> { let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES; let end = start + DWB_LIST_LEN_BYTES; - if DWB_LIST_LEN_BYTES != U16_BYTES { - return Err(StdError::generic_err("Set dwb amount error")); - } self.0[start..end].copy_from_slice(&val.to_be_bytes()); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index b070ddc3..701260c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#[macro_use] +extern crate static_assertions as sa; + mod batch; pub mod contract; pub mod msg; @@ -5,4 +8,5 @@ pub mod receiver; pub mod state; mod transaction_history; mod dwb; +mod stored_balances; mod strings; diff --git a/src/stored_balances.rs b/src/stored_balances.rs new file mode 100644 index 00000000..6af94b61 --- /dev/null +++ b/src/stored_balances.rs @@ -0,0 +1,425 @@ +use constant_time_eq::constant_time_eq; +use secret_toolkit::storage::Item; +use serde::{Serialize, Deserialize,}; +use serde_big_array::BigArray; +use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; + +use crate::{ + dwb::DelayedWriteBufferEntry, state::safe_add_u64 +}; + +// btsb = bitwise-trie of stored balances + +pub const KEY_BTSB_ENTRY_HISTORY: &[u8] = b"btsb-entry-hist"; +pub const KEY_BTSB_BUCKETS_COUNT: &[u8] = b"btsb-buckets-cnt"; +pub const KEY_BTSB_BUCKETS: &[u8] = b"btsb-buckets"; +pub const KEY_BTSB_TRIE_NODES: &[u8] = b"btsb-trie-nodes"; +pub const KEY_BTSB_TRIE_NODES_COUNT: &[u8] = b"btsb-trie-nodes-cnt"; + + +const U16_BYTES: usize = 2; +const U32_BYTES: usize = 4; +const U64_BYTES: usize = 8; +const U128_BYTES: usize = 16; + +#[cfg(test)] +const BTSB_BUCKET_ADDRESS_BYTES: usize = 54; +#[cfg(not(test))] +const BTSB_BUCKET_ADDRESS_BYTES: usize = 20; +const BTSB_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u128) +const BTSB_BUCKET_HISTORY_BYTES: usize = 5; // Max 8 (u64) + +const_assert!(BTSB_BUCKET_BALANCE_BYTES <= U128_BYTES); +const_assert!(BTSB_BUCKET_HISTORY_BYTES <= U64_BYTES); + +const BTSB_BUCKET_ENTRY_BYTES: usize = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES + BTSB_BUCKET_HISTORY_BYTES; + +const ZERO_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0u8; BTSB_BUCKET_ADDRESS_BYTES]; + +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct StoredBalanceEntry( + #[serde(with = "BigArray")] + [u8; BTSB_BUCKET_ENTRY_BYTES] +); + +impl StoredBalanceEntry { + pub fn new(address: CanonicalAddr) -> StdResult { + let address = address.as_slice(); + + if address.len() != BTSB_BUCKET_ADDRESS_BYTES { + return Err(StdError::generic_err("bucket: invalid address length")); + } + + let mut result = [0u8; BTSB_BUCKET_ENTRY_BYTES]; + result[..BTSB_BUCKET_ENTRY_BYTES].copy_from_slice(address); + Ok(Self { + 0: result + }) + } + + pub fn from(dwb_entry: DelayedWriteBufferEntry) -> StdResult { + let mut entry = StoredBalanceEntry::new(dwb_entry.recipient()?)?; + + entry.set_balace(dwb_entry.amount()?); + entry.set_history_len(1); + + Ok(entry) + } + + fn address_slice(&self) -> &[u8] { + &self.0[..BTSB_BUCKET_ADDRESS_BYTES] + } + + fn address(&self) -> StdResult { + let result = CanonicalAddr::try_from(self.address_slice()) + .or(Err(StdError::generic_err("Get bucket address error")))?; + Ok(result) + } + + pub fn balance(&self) -> StdResult { + let start = BTSB_BUCKET_ADDRESS_BYTES; + let end = start + BTSB_BUCKET_BALANCE_BYTES; + let amount_slice = &self.0[start..end]; + let result = amount_slice + .try_into() + .or(Err(StdError::generic_err("Get bucket balance error")))?; + Ok(u64::from_be_bytes(result)) + } + + fn set_balace(&mut self, val: u64) -> StdResult<()> { + let start = BTSB_BUCKET_ADDRESS_BYTES; + let end = start + BTSB_BUCKET_BALANCE_BYTES; + self.0[start..end].copy_from_slice(&val.to_be_bytes()); + Ok(()) + } + + pub fn history_len(&self) -> StdResult { + let start = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES; + let end = start + BTSB_BUCKET_HISTORY_BYTES; + let history_len_slice = &self.0[start..end]; + let mut result = [0u8; U64_BYTES]; + result[U64_BYTES - BTSB_BUCKET_HISTORY_BYTES..].copy_from_slice(history_len_slice); + Ok(u64::from_be_bytes(result)) + } + + fn set_history_len(&mut self, val: u64) -> StdResult<()> { + let start = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES; + let end = start + BTSB_BUCKET_HISTORY_BYTES; + let val_bytes = &val.to_be_bytes()[U64_BYTES - BTSB_BUCKET_HISTORY_BYTES..]; + if val_bytes.len() != BTSB_BUCKET_HISTORY_BYTES { + return Err(StdError::generic_err("Set bucket history len error")); + } + self.0[start..end].copy_from_slice(val_bytes); + Ok(()) + } + + pub fn merge_dwb_entry(&mut self, entry: &DelayedWriteBufferEntry) -> StdResult<()> { + let mut balance = self.balance()?; + safe_add_u64(&mut balance, entry.amount()?); + self.set_balace(balance)?; + + // TOOD: update history len + + Ok(()) + } + +} + + + +const BTSB_BUCKET_LEN: u16 = 128; + +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +struct BtsbBucket { + pub capacity: u16, + #[serde(with = "BigArray")] + pub entries: [StoredBalanceEntry; BTSB_BUCKET_LEN as usize], +} + +static BTSB_ENTRY_HISTORY: Item = Item::new(KEY_BTSB_ENTRY_HISTORY); +static BTSB_BUCKETS_COUNT: Item = Item::new(KEY_BTSB_BUCKETS_COUNT); +static BTSB_BUCKETS: Item = Item::new(KEY_BTSB_BUCKETS); + +// create type alias to refer to position of a bucket entry, which is its index in the array plus 1 +type BucketEntryPosition = usize; + +impl BtsbBucket { + pub fn new() -> StdResult { + Ok(Self { + capacity: BTSB_BUCKET_LEN, + entries: [ + StoredBalanceEntry::new(CanonicalAddr::from(&ZERO_ADDR))?; BTSB_BUCKET_LEN as usize + ] + }) + } + + pub fn add_entry(&mut self, storage: &mut dyn Storage, entry: &StoredBalanceEntry, bit_pos: u8) -> StdResult { + match self.capacity { + // buffer is at capacity + 0 => Err(StdError::generic_err("")), + + // has capacity for a new entry + _ => { + // save entry to bucket + self.entries[self.entries.len() - self.capacity as usize] = entry.clone(); + + // update capacity + self.capacity -= 1; + + // done + Ok(self.capacity) + } + } + } + + pub fn constant_time_find_address(&self, address: &CanonicalAddr) -> Option { + let address = address.as_slice(); + + let mut matched_index_p1: BucketEntryPosition = 0; + for (idx, entry) in self.entries.iter().enumerate() { + let equals = constant_time_eq(address, entry.address_slice()) as usize; + matched_index_p1 |= (idx + 1) * equals; + } + + match matched_index_p1 { + 0 => None, + idx => Some(self.entries[idx - 1]), + } + } + + pub fn quick_find_entry(&self, address: &CanonicalAddr) -> Option { + let address = address.as_slice(); + + let mut matched_index_p1: BucketEntryPosition = 0; + /* TODO: + binary search on bucket + */ + + match matched_index_p1 { + 0 => None, + idx => Some(self.entries[idx - 1]), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +pub struct BitwiseTrieNode { + pub left: u64, + pub right: u64, + pub bucket: u64, +} + + +pub static BTSB_TRIE_NODES: Item = Item::new(KEY_BTSB_TRIE_NODES); +pub static BTSB_TRIE_NODES_COUNT: Item = Item::new(KEY_BTSB_TRIE_NODES_COUNT); + +impl BitwiseTrieNode { + // creates a new leaf node + pub fn new_leaf(storage: &mut dyn Storage, bucket: BtsbBucket) -> StdResult { + let buckets_count = BTSB_BUCKETS_COUNT.load(storage).unwrap_or_default() + 1; + + // ID for new bucket + let bucket_id = buckets_count; + + // save updated count + BTSB_BUCKETS_COUNT.save(storage, &buckets_count)?; + + // save bucket to storage + BTSB_BUCKETS.add_suffix(&bucket_id.to_be_bytes()).save(storage, &bucket)?; + + // create new node + Ok(Self { + left: 0, + right: 0, + bucket: bucket_id, + }) + } + + // loads the node's bucket from storage + pub fn bucket(self, storage: &mut dyn Storage) -> StdResult { + if self.bucket == 0 { + return Err(StdError::generic_err("btsb: attempted to load bucket of branch node")); + } + + // load bucket from storage + BTSB_BUCKETS.add_suffix(&self.bucket.to_be_bytes()).load(storage) + } + + // stores the bucket associated with this node + fn set_and_save_bucket(self, storage: &mut dyn Storage, bucket: BtsbBucket) -> StdResult<()> { + if self.bucket == 0 { + return Err(StdError::generic_err("btsb: attempted to store a bucket to a branch node")); + } + + BTSB_BUCKETS.add_suffix(&self.bucket.to_be_bytes()).save(storage, &bucket) + } +} + + +// locates a btsb node given an address; returns tuple of (node, bit position) +pub fn locate_btsb_node(storage: &mut dyn Storage, address: &CanonicalAddr) -> StdResult<(BitwiseTrieNode, u64, u8)> { + let hash: [u8; 32] = [0u8; 32]; + /* TODO: + let hash := hkdf(ikm=contractInternalSecret, info=addrress, length=256bits) + */ + + // start at root of trie + let mut node_id: u64 = 1; + let mut node = BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).load(storage)?; + let mut bit_pos: u8 = 0; + + // while the node has children + while node.bucket == 0 { + // calculate bit value at current bit position + let bit_value = (hash[(bit_pos / 8) as usize] >> (7 - (bit_pos % 8))) & 1; + + // increment bit position + bit_pos += 1; + + // choose left or right child depending on bit value + node_id = if bit_value == 0 { node.left } else { node.right }; + + // load child node + node = BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).load(storage)?; + } + + Ok((node, node_id, bit_pos)) +} + + +// merges a dwb entry into the current node's bucket +pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferEntry) -> StdResult<()> { + // locate the node that the given entry belongs in + let (mut node, node_id, bit_pos) = locate_btsb_node(storage, &dwb_entry.recipient()?)?; + + // load that node's current bucket + let mut bucket = node.bucket(storage)?; + + // search for an existing entry + match bucket.constant_time_find_address(&dwb_entry.recipient()?) { + // found existing entry + Some(mut found_entry) => { + // merge amount and history from dwb entry + found_entry.merge_dwb_entry(&dwb_entry); + + // save updated bucket to storage + node.set_and_save_bucket(storage, bucket); + }, + + // need to insert new entry + None => { + // create new stored balance entry + let btsb_entry = StoredBalanceEntry::from(dwb_entry)?; + + /* TODO: + create new storage for dwb_entry's history + */ + + // try to add to the current bucket + match bucket.add_entry(storage, &btsb_entry, bit_pos) { + // bucket has capcity and it added the new entry + Ok(capacity) => { + // save bucket to storage + node.set_and_save_bucket(storage, bucket); + } + + // bucket is full; split on next bit position + Err(_) => { + // create new left and right buckets + let left_bucket = BtsbBucket::new()?; + let right_bucket = BtsbBucket::new()?; + + // each entry + for (idx, entry) in bucket.entries.iter().enumerate() { + /* TODO: + let key := hkdf(ikm=contractInternalSecret, info=canonical(addr), length=256bits) + let bit_value := (key >> (255 - bit_pos)) & 1 + if bit_value == 0: + left_bucket.add_entry(entry) + else: + right_bucket.add_entry(entry) + */ + } + + // save left node's bucket to storage, recycling this node's bucket ID + let left_bucket_id = node.bucket; + BTSB_BUCKETS.add_suffix(&left_bucket_id.to_be_bytes()).save(storage, &left_bucket); + + // global count of buckets + let mut buckets_count = BTSB_BUCKETS_COUNT.load(storage).unwrap_or_default(); + + // bucket ID for right node + buckets_count += 1; + let right_bucket_id = buckets_count; + BTSB_BUCKETS.add_suffix(&right_bucket_id.to_be_bytes()).save(storage, &right_bucket); + + // save updated count + BTSB_BUCKETS_COUNT.save(storage, &buckets_count)?; + + // globl count of trie nodes + let mut nodes_count = BTSB_TRIE_NODES_COUNT.load(storage).unwrap_or_default(); + + // ID for left node + nodes_count += 1; + let left_id = nodes_count; + + // ID for right node + nodes_count += 1; + let right_id = nodes_count; + + // save updated count + BTSB_TRIE_NODES_COUNT.save(storage, &nodes_count)?; + + // create left and right nodes + let left = BitwiseTrieNode { + left: 0, + right: 0, + bucket: left_bucket_id, + }; + let right = BitwiseTrieNode { + left: 0, + right: 0, + bucket: right_bucket_id, + }; + + // save left and right node to storage + BTSB_TRIE_NODES.add_suffix(&left_id.to_be_bytes()).save(storage, &left)?; + BTSB_TRIE_NODES.add_suffix(&right_id.to_be_bytes()).save(storage, &right)?; + + // convert this into a branch node + node.left = left_id; + node.right = right_id; + node.bucket = 0; + + // save node + BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node); + + // -- + + /* TODO + determine which child node the dwb entry belongs in, then retry insertion, + looping as many times as needed until the bucket has capacity for a new entry + */ + } + } + }, + } + + Ok(()) +} + + +// for fetching an account's stored balance during transfer executions +pub fn constant_time_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { + let (mut node, node_id, bit_pos) = locate_btsb_node(storage, &address)?; + + Ok(node.bucket(storage)?.constant_time_find_address(&address)) +} + +// for fetching account's stored balance and/or history during queries +pub fn quick_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { + let (mut node, node_id, bit_pos) = locate_btsb_node(storage, &address)?; + + Ok(node.bucket(storage)?.quick_find_entry(&address)) +} From 3d7de8b8f2803233dd487d99a581ce3363d14bcb Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 9 Jul 2024 22:30:53 +1200 Subject: [PATCH 38/87] dev: stored balances todos --- Cargo.lock | 175 ++++++++++++++-- Cargo.toml | 7 +- src/contract.rs | 123 ++++++----- src/dwb.rs | 143 +++++-------- src/state.rs | 43 +--- src/stored_balances.rs | 457 ++++++++++++++++++++++++++--------------- 6 files changed, 581 insertions(+), 367 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7be22c62..9fe43e79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ahash" version = "0.7.8" @@ -89,6 +99,41 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -168,6 +213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -288,6 +334,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + [[package]] name = "forward_ref" version = "1.0.0" @@ -341,6 +396,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -350,6 +414,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "1.0.11" @@ -374,6 +447,36 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "minicbor" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a20020e8e2d1881d8736f64011bb5ff99f1db9947ce3089706945c8915695cb" +dependencies = [ + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8608fb1c805b5b6b3d5ab7bd95c40c396df622b64d77b2d621a5eae1eed050ee" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "minicbor-ser" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0834b86a9c56311671913d56f640d7f0b6da803df61121661cc890f0edc0eb1" +dependencies = [ + "minicbor", + "serde", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -396,12 +499,33 @@ dependencies = [ "spki", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "uint", +] + [[package]] name = "proc-macro2" version = "1.0.85" @@ -592,10 +716,10 @@ dependencies = [ [[package]] name = "secret-toolkit" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338c972c0a98de51ccbb859312eb7672bc64b9050b086f058748ba26a509edbb" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" dependencies = [ "secret-toolkit-crypto", + "secret-toolkit-notification", "secret-toolkit-permit", "secret-toolkit-serialization", "secret-toolkit-storage", @@ -606,8 +730,7 @@ dependencies = [ [[package]] name = "secret-toolkit-crypto" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003d7d5772c67f2240b7f298f96eb73a8a501916fe18c1d730ebfd591bf7e519" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" dependencies = [ "rand_chacha", "rand_core 0.6.4", @@ -616,11 +739,28 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "secret-toolkit-notification" +version = "0.10.0" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +dependencies = [ + "chacha20poly1305", + "generic-array", + "hkdf", + "minicbor-ser", + "primitive-types", + "ripemd", + "schemars", + "secret-cosmwasm-std", + "secret-toolkit-crypto", + "serde", + "sha2 0.10.8", +] + [[package]] name = "secret-toolkit-permit" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4330571400b5959450fa37040609e6804a147d83f606783506bc2275f1527712" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" dependencies = [ "bech32", "remain", @@ -634,8 +774,7 @@ dependencies = [ [[package]] name = "secret-toolkit-serialization" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "890adaeaa710f9f7068a807eb1553edc8c30ce9907290895c9097dd642fc613b" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" dependencies = [ "bincode2", "schemars", @@ -646,8 +785,7 @@ dependencies = [ [[package]] name = "secret-toolkit-storage" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e8c5418af3e7ae1d1331c383b32d56c74a340dbc3b972d53555a768698f2a3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" dependencies = [ "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -658,8 +796,7 @@ dependencies = [ [[package]] name = "secret-toolkit-utils" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f1cba2e70fd701e3dfc6072807c02eeeb9776bee49e346a9c7745d84ff40c8" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" dependencies = [ "schemars", "secret-cosmwasm-std", @@ -670,8 +807,7 @@ dependencies = [ [[package]] name = "secret-toolkit-viewing-key" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d89a0b69fa9b12735a612fa30e6e7e48130943982f1783b7ddd5c46ed09e921" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" dependencies = [ "base64 0.21.7", "schemars", @@ -784,6 +920,7 @@ dependencies = [ "base64 0.21.7", "constant_time_eq", "cosmwasm-schema", + "primitive-types", "rand", "schemars", "secret-cosmwasm-std", @@ -883,6 +1020,16 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index fc90942d..11281745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,8 +35,10 @@ backtraces = ["cosmwasm-std/backtraces"] cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } rand = { version = "0.8.5", default-features = false } -secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] } -secret-toolkit-crypto = { version = "0.10.0", default-features = false, features = ["hash"] } +# secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] } +secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"] } +# secret-toolkit-crypto = { version = "0.10.0", default-features = false, features = ["hash"] } +secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash"] } static_assertions = "1.1.0" schemars = "0.8.12" @@ -44,6 +46,7 @@ serde = { version = "1.0.158", default-features = false, features = ["derive"] } serde-big-array = "0.5.1" base64 = "0.21.0" constant_time_eq = "0.3.0" +primitive-types = { version = "0.12.2", default-features = false } [dev-dependencies] cosmwasm-schema = { version = "1.1.8" } diff --git a/src/contract.rs b/src/contract.rs index cd5f2328..9b6b5661 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -3,22 +3,24 @@ use cosmwasm_std::{ entry_point, to_binary, Addr, Api, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 }; +use secret_toolkit::notification::hkdf_sha_256; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{log_dwb, AccountTxsStore, DelayedWriteBuffer, ACCOUNT_TXS, ACCOUNT_TX_COUNT, DWB, TX_NODES}; -use crate::bucket; +use crate::dwb::{log_dwb, DelayedWriteBuffer, DWB, TX_NODES}; +//use crate::bucket; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, }; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, PRNG, TOTAL_SUPPLY + safe_add, AllowancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, PRNG, TOTAL_SUPPLY }; +use crate::stored_balances::{find_start_bundle, stored_balance}; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx @@ -54,7 +56,7 @@ pub fn instantiate( let admin = match msg.admin { Some(admin_addr) => deps.api.addr_validate(admin_addr.as_str())?, - None => info.sender, + None => info.sender.clone(), }; let mut total_supply: u128 = 0; @@ -126,6 +128,26 @@ pub fn instantiate( ViewingKey::set_seed(deps.storage, &prng_seed_hashed); + // use entropy and env.random to create an internal secret for the contract + let entropy = msg.prng_seed.0.as_slice(); + let entropy_len = 16 + info.sender.to_string().len() + entropy.len(); + let mut rng_entropy = Vec::with_capacity(entropy_len); + rng_entropy.extend_from_slice(&env.block.height.to_be_bytes()); + rng_entropy.extend_from_slice(&env.block.time.seconds().to_be_bytes()); + rng_entropy.extend_from_slice(info.sender.as_bytes()); + rng_entropy.extend_from_slice(entropy); + let rng_seed = env.block.random.as_ref().unwrap(); + + // Create INTERNAL_SECRET + let salt = Some(sha_256(&rng_entropy).to_vec()); + let internal_secret = hkdf_sha_256( + &salt, + rng_seed.0.as_slice(), + "contract_internal_secret".as_bytes(), + 32, + )?; + INTERNAL_SECRET.save(deps.storage, &internal_secret)?; + Ok(Response::default()) } @@ -147,7 +169,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S denom, .. } if contract_status == ContractStatusLevel::StopAllButRedeems => { - try_redeem(deps, env, info, &mut rng, amount, denom) + try_redeem(deps, env, info, amount, denom) } _ => Err(StdError::generic_err( "This contract is stopped and this action is not allowed", @@ -158,6 +180,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ContractStatusLevel::NormalRun => {} // If it's a normal run just continue } + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); let response = match msg.clone() { // Native ExecuteMsg::Deposit { .. } => { @@ -167,7 +191,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, denom, .. - } => try_redeem(deps, env, info, &mut rng, amount, denom), + } => try_redeem(deps, env, info, amount, denom), // Base ExecuteMsg::Transfer { @@ -212,7 +236,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => try_burn(deps, env, info, &mut rng, amount, memo), + } => try_burn(deps, env, info, amount, memo), ExecuteMsg::RegisterReceive { code_hash, .. } => { try_register_receive(deps, info, code_hash) } @@ -283,13 +307,12 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S deps, &env, info, - &mut rng, owner, amount, memo, ), ExecuteMsg::BatchBurnFrom { actions, .. } => { - try_batch_burn_from(deps, &env, info, &mut rng, actions) + try_batch_burn_from(deps, &env, info, actions) } // Mint @@ -644,7 +667,7 @@ pub fn query_transactions( //println!("OPTION 3"); let settled_start = settled_tx_count.saturating_sub(start - txs_in_dwb_count).saturating_sub(1); - if let Some((bundle_idx, tx_bundle, start_at)) = AccountTxsStore::find_start_bundle( + if let Some((bundle_idx, tx_bundle, start_at)) = find_start_bundle( deps.storage, &account_raw, settled_start @@ -701,7 +724,7 @@ pub fn query_balance(deps: Deps, account: String) -> StdResult { let account = Addr::unchecked(account); let account = deps.api.addr_canonicalize(account.as_str())?; - let mut amount = BalancesStore::load(deps.storage, &account); + let mut amount = stored_balance(deps.storage, &account)?; let dwb = DWB.load(deps.storage)?; let dwb_index = dwb.recipient_match(&account); if dwb_index > 0 { @@ -1073,7 +1096,6 @@ fn try_redeem( deps: DepsMut, env: Env, info: MessageInfo, - rng: &mut ContractPrng, amount: Uint128, denom: Option, ) -> StdResult { @@ -1115,7 +1137,7 @@ fn try_redeem( let mut dwb = DWB.load(deps.storage)?; // settle the signer's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, rng, &sender_address, tx_id, amount_raw, "redeem")?; + dwb.settle_sender_or_owner_account(deps.storage, &sender_address, tx_id, amount_raw, "redeem")?; DWB.save(deps.storage, &dwb)?; @@ -1659,7 +1681,6 @@ fn try_burn_from( deps: DepsMut, env: &Env, info: MessageInfo, - rng: &mut ContractPrng, owner: String, amount: Uint128, memo: Option, @@ -1691,9 +1712,9 @@ fn try_burn_from( let mut dwb = DWB.load(deps.storage)?; // settle the owner's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_owner, tx_id, raw_amount, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_owner, tx_id, raw_amount, "burn")?; if raw_burner != raw_owner { // also settle sender's account - dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_burner, tx_id, 0, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_burner, tx_id, 0, "burn")?; } DWB.save(deps.storage, &dwb)?; @@ -1717,7 +1738,6 @@ fn try_batch_burn_from( deps: DepsMut, env: &Env, info: MessageInfo, - rng: &mut ContractPrng, actions: Vec, ) -> StdResult { let constants = CONFIG.load(deps.storage)?; @@ -1750,9 +1770,9 @@ fn try_batch_burn_from( let mut dwb = DWB.load(deps.storage)?; // settle the owner's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_owner, tx_id, amount, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_owner, tx_id, amount, "burn")?; if raw_spender != raw_owner { - dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_spender, tx_id, 0, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_spender, tx_id, 0, "burn")?; } DWB.save(deps.storage, &dwb)?; @@ -1930,7 +1950,6 @@ fn try_burn( deps: DepsMut, env: Env, info: MessageInfo, - rng: &mut ContractPrng, amount: Uint128, memo: Option, ) -> StdResult { @@ -1958,7 +1977,7 @@ fn try_burn( let mut dwb = DWB.load(deps.storage)?; // settle the signer's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, rng, &raw_burn_address, tx_id, raw_amount, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_burn_address, tx_id, raw_amount, "burn")?; DWB.save(deps.storage, &dwb)?; @@ -2005,10 +2024,10 @@ fn perform_transfer( let transfer_str = "transfer"; // settle the owner's account - dwb.settle_sender_or_owner_account(store, rng, from, tx_id, amount, transfer_str)?; + dwb.settle_sender_or_owner_account(store, from, tx_id, amount, transfer_str)?; // if this is a *_from action, settle the sender's account, too if sender != from { - dwb.settle_sender_or_owner_account(store, rng, sender, tx_id, 0, transfer_str)?; + dwb.settle_sender_or_owner_account(store, sender, tx_id, 0, transfer_str)?; } // TESTING @@ -2049,7 +2068,7 @@ fn perform_mint( // if minter is not recipient, settle them if minter != to { - dwb.settle_sender_or_owner_account(store, rng, minter, tx_id, 0, "mint")?; + dwb.settle_sender_or_owner_account(store, minter, tx_id, 0, "mint")?; } // add the tx info for the recipient to the buffer @@ -2438,9 +2457,9 @@ mod tests { .addr_canonicalize(Addr::unchecked("alice").as_str()) .unwrap(); - assert_eq!(5000 - 1000, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000, stored_balance(&deps.storage, &bob_addr).unwrap()); // alice has not been settled yet - assert_ne!(1000, BalancesStore::load(&deps.storage, &alice_addr)); + assert_ne!(1000, stored_balance(&deps.storage, &alice_addr).unwrap()); let dwb = DWB.load(&deps.storage).unwrap(); println!("DWB: {dwb:?}"); @@ -2480,11 +2499,11 @@ mod tests { .addr_canonicalize(Addr::unchecked("charlie").as_str()) .unwrap(); - assert_eq!(5000 - 1000 - 100, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100, stored_balance(&deps.storage, &bob_addr).unwrap()); // alice has not been settled yet - assert_ne!(1000, BalancesStore::load(&deps.storage, &alice_addr)); + assert_ne!(1000, stored_balance(&deps.storage, &alice_addr).unwrap()); // charlie has not been settled yet - assert_ne!(100, BalancesStore::load(&deps.storage, &charlie_addr)); + assert_ne!(100, stored_balance(&deps.storage, &charlie_addr).unwrap()); let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2514,9 +2533,9 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(5000 - 1000 - 100 - 500, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100 - 500, stored_balance(&deps.storage, &bob_addr).unwrap()); // make sure alice has not been settled yet - assert_ne!(1500, BalancesStore::load(&deps.storage, &alice_addr)); + assert_ne!(1500, stored_balance(&deps.storage, &alice_addr).unwrap()); let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2592,13 +2611,13 @@ mod tests { .addr_canonicalize(Addr::unchecked("ernie").as_str()) .unwrap(); - assert_eq!(5000 - 1000 - 100 - 500 - 200, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100 - 500 - 200, stored_balance(&deps.storage, &bob_addr).unwrap()); // alice has not been settled yet - assert_ne!(1500, BalancesStore::load(&deps.storage, &alice_addr)); + assert_ne!(1500, stored_balance(&deps.storage, &alice_addr).unwrap()); // charlie has not been settled yet - assert_ne!(100, BalancesStore::load(&deps.storage, &charlie_addr)); + assert_ne!(100, stored_balance(&deps.storage, &charlie_addr).unwrap()); // ernie has not been settled yet - assert_ne!(200, BalancesStore::load(&deps.storage, &ernie_addr)); + assert_ne!(200, stored_balance(&deps.storage, &ernie_addr).unwrap()); let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2635,9 +2654,9 @@ mod tests { .unwrap(); // alice has been settled - assert_eq!(1500 - 50, BalancesStore::load(&deps.storage, &alice_addr)); + assert_eq!(1500 - 50, stored_balance(&deps.storage, &alice_addr).unwrap()); // dora has not been settled - assert_ne!(50, BalancesStore::load(&deps.storage, &dora_addr)); + assert_ne!(50, stored_balance(&deps.storage, &dora_addr).unwrap()); let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2671,7 +2690,7 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); } - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59, stored_balance(&deps.storage, &bob_addr).unwrap()); let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2695,7 +2714,7 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1, stored_balance(&deps.storage, &bob_addr).unwrap()); //let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2716,7 +2735,7 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1 - 1, BalancesStore::load(&deps.storage, &bob_addr)); + assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1 - 1, stored_balance(&deps.storage, &bob_addr).unwrap()); //let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2740,7 +2759,7 @@ mod tests { assert!(ensure_success(result)); // alice should not settle - assert_eq!(1500 - 50, BalancesStore::load(&deps.storage, &alice_addr)); + assert_eq!(1500 - 50, stored_balance(&deps.storage, &alice_addr).unwrap()); } // alice sends 1 to dora to settle @@ -2759,7 +2778,7 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(2724, BalancesStore::load(&deps.storage, &alice_addr)); + assert_eq!(2724, stored_balance(&deps.storage, &alice_addr).unwrap()); // now we send 50 more transactions to alice from bob for i in 1..=50 { @@ -2780,7 +2799,7 @@ mod tests { assert!(ensure_success(result)); // alice should not settle - assert_eq!(2724, BalancesStore::load(&deps.storage, &alice_addr)); + assert_eq!(2724, stored_balance(&deps.storage, &alice_addr).unwrap()); } let handle_msg = ExecuteMsg::SetViewingKey { @@ -3605,8 +3624,8 @@ mod tests { .addr_canonicalize(Addr::unchecked("alice".to_string()).as_str()) .unwrap(); - let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); - let alice_balance = BalancesStore::load(&deps.storage, &alice_canonical); + let bob_balance = stored_balance(&deps.storage, &bob_canonical).unwrap(); + let alice_balance = stored_balance(&deps.storage, &alice_canonical).unwrap(); assert_eq!(bob_balance, 5000 - 2000); assert_ne!(alice_balance, 2000); let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); @@ -3748,8 +3767,8 @@ mod tests { .addr_canonicalize(Addr::unchecked("contract".to_string()).as_str()) .unwrap(); - let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); - let contract_balance = BalancesStore::load(&deps.storage, &contract_canonical); + let bob_balance = stored_balance(&deps.storage, &bob_canonical).unwrap(); + let contract_balance = stored_balance(&deps.storage, &contract_canonical).unwrap(); assert_eq!(bob_balance, 5000 - 2000); assert_ne!(contract_balance, 2000); let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); @@ -3880,7 +3899,7 @@ mod tests { .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str()) .unwrap(); - let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical); + let bob_balance = stored_balance(&deps.storage, &bob_canonical).unwrap(); assert_eq!(bob_balance, 10000 - 2000); let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); assert_eq!(total_supply, 10000 - 2000); @@ -4029,7 +4048,7 @@ mod tests { .api .addr_canonicalize(Addr::unchecked(name.to_string()).as_str()) .unwrap(); - let balance = BalancesStore::load(&deps.storage, &name_canon); + let balance = stored_balance(&deps.storage, &name_canon).unwrap(); assert_eq!(balance, 10000 - amount); } let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); @@ -4063,7 +4082,7 @@ mod tests { .api .addr_canonicalize(Addr::unchecked(name.to_string()).as_str()) .unwrap(); - let balance = BalancesStore::load(&deps.storage, &name_canon); + let balance = stored_balance(&deps.storage, &name_canon).unwrap(); assert_eq!(balance, 10000 - allowance_size); } let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap(); @@ -4413,7 +4432,7 @@ mod tests { .api .addr_canonicalize(Addr::unchecked("butler".to_string()).as_str()) .unwrap(); - assert_eq!(BalancesStore::load(&deps.storage, &canonical), 3000) + assert_eq!(stored_balance(&deps.storage, &canonical).unwrap(), 3000) } #[test] @@ -4486,7 +4505,7 @@ mod tests { .unwrap(); // stored balance not updated, still in dwb - assert_ne!(BalancesStore::load(&deps.storage, &canonical), 6000); + assert_ne!(stored_balance(&deps.storage, &canonical).unwrap(), 6000); let create_vk_msg = ExecuteMsg::CreateViewingKey { entropy: "34".to_string(), diff --git a/src/dwb.rs b/src/dwb.rs index a2a327c3..67afe12c 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -7,9 +7,7 @@ use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, S use secret_toolkit::storage::{AppendStore, Item}; use crate::{ - msg::QueryAnswer, - state::{safe_add, safe_add_u64, BalancesStore,}, - transaction_history::{Tx, TRANSACTIONS}, + msg::QueryAnswer, state::{safe_add, safe_add_u64,}, stored_balances::{merge_dwb_entry, stored_balance}, transaction_history::{Tx, TRANSACTIONS} }; pub const KEY_DWB: &[u8] = b"dwb"; @@ -46,13 +44,13 @@ pub struct DelayedWriteBuffer { pub entries: [DelayedWriteBufferEntry; DWB_LEN as usize], } -#[inline] -fn random_addr(rng: &mut ContractPrng) -> CanonicalAddr { - #[cfg(test)] - return CanonicalAddr::from(&[rng.rand_bytes(), rng.rand_bytes()].concat()[0..DWB_RECIPIENT_BYTES]); // because mock canonical addr is 54 bytes - #[cfg(not(test))] - CanonicalAddr::from(&rng.rand_bytes()[0..DWB_RECIPIENT_BYTES]) // canonical addr is 20 bytes (less than 32) -} +//#[inline] +//fn random_addr(rng: &mut ContractPrng) -> CanonicalAddr { +// #[cfg(test)] +// return CanonicalAddr::from(&[rng.rand_bytes(), rng.rand_bytes()].concat()[0..DWB_RECIPIENT_BYTES]); // because mock canonical addr is 54 bytes +// #[cfg(not(test))] +// CanonicalAddr::from(&rng.rand_bytes()[0..DWB_RECIPIENT_BYTES]) // canonical addr is 20 bytes (less than 32) +//} pub fn random_in_range(rng: &mut ContractPrng, a: u32, b: u32) -> StdResult { if b <= a { @@ -82,12 +80,14 @@ impl DelayedWriteBuffer { } /// settles an entry at a given index in the buffer + #[inline] fn settle_entry( - &mut self, + &self, store: &mut dyn Storage, index: usize, ) -> StdResult<()> { - let entry = self.entries[index]; + merge_dwb_entry(store, self.entries[index], None) +/* let account = entry.recipient()?; AccountTxsStore::append_bundle( @@ -102,6 +102,7 @@ impl DelayedWriteBuffer { safe_add(&mut balance, entry.amount()? as u128); // add the amount from entry to the stored balance BalancesStore::save(store, &account, balance) +*/ } /// settles a participant's account who may or may not have an entry in the buffer @@ -109,19 +110,27 @@ impl DelayedWriteBuffer { pub fn settle_sender_or_owner_account( &mut self, store: &mut dyn Storage, - rng: &mut ContractPrng, address: &CanonicalAddr, tx_id: u64, amount_spent: u128, op_name: &str, ) -> StdResult<()> { // release the address from the buffer - let (balance, mut entry) = self.constant_time_release( + let (balance, mut dwb_entry) = self.constant_time_release( store, - rng, address )?; + if balance.checked_sub(amount_spent).is_none() { + return Err(StdError::generic_err(format!( + "insufficient funds to {op_name}: balance={balance}, required={amount_spent}", + ))); + }; + + dwb_entry.add_tx_node(store, tx_id)?; + + merge_dwb_entry(store, dwb_entry, Some(amount_spent)) +/* let head_node = entry.add_tx_node(store, tx_id)?; AccountTxsStore::append_bundle( @@ -131,16 +140,10 @@ impl DelayedWriteBuffer { entry.list_len()?, )?; - let new_balance = if let Some(balance_after_sub) = balance.checked_sub(amount_spent) { - balance_after_sub - } else { - return Err(StdError::generic_err(format!( - "insufficient funds to {op_name}: balance={balance}, required={amount_spent}", - ))); - }; BalancesStore::save(store, address, new_balance)?; Ok(()) +*/ } /// "releases" a given recipient from the buffer, removing their entry if one exists, in constant-time @@ -148,11 +151,10 @@ impl DelayedWriteBuffer { fn constant_time_release( &mut self, store: &mut dyn Storage, - rng: &mut ContractPrng, address: &CanonicalAddr ) -> StdResult<(u128, DelayedWriteBufferEntry)> { // get the address' stored balance - let mut balance = BalancesStore::load(store, address); + let mut balance = stored_balance(store, address)?; // locate the position of the entry in the buffer let matched_entry_idx = self.recipient_match(address); @@ -172,15 +174,15 @@ impl DelayedWriteBuffer { Ok((balance, entry)) } - fn unique_random_entry(&self, rng: &mut ContractPrng) -> StdResult { - // produce a new random address - let mut replacement_address = random_addr(rng); - // ensure random addr is not already in dwb (extremely unlikely!!) - while self.recipient_match(&replacement_address) > 0 { - replacement_address = random_addr(rng); - } - DelayedWriteBufferEntry::new(replacement_address) - } + //fn unique_random_entry(&self, rng: &mut ContractPrng) -> StdResult { + // // produce a new random address + // let mut replacement_address = random_addr(rng); + // // ensure random addr is not already in dwb (extremely unlikely!!) + // while self.recipient_match(&replacement_address) > 0 { + // replacement_address = random_addr(rng); + // } + // DelayedWriteBufferEntry::new(replacement_address) + //} // returns matched index for a given address pub fn recipient_match(&self, address: &CanonicalAddr) -> usize { @@ -413,9 +415,7 @@ impl DelayedWriteBufferEntry { fn add_amount(&mut self, add_tx_amount: u128) -> StdResult { // change this to safe_add if your coin needs to store amount in buffer as u128 (e.g. 18 decimals) let mut amount = self.amount()?; - let add_tx_amount_u64 = add_tx_amount - .try_into() - .or_else(|_| return Err(StdError::generic_err("dwb: deposit overflow")))?; + let add_tx_amount_u64 = amount_u64(Some(add_tx_amount))?; safe_add_u64(&mut amount, add_tx_amount_u64); self.set_amount(amount)?; @@ -423,6 +423,14 @@ impl DelayedWriteBufferEntry { } } +pub fn amount_u64(amount_spent: Option) -> StdResult { + let amount_spent = amount_spent.unwrap_or_default(); + let amount_spent_u64 = amount_spent + .try_into() + .or_else(|_| return Err(StdError::generic_err("se: spent overflow")))?; + Ok(amount_spent_u64) +} + #[derive(Serialize, Deserialize, Clone, Copy, Debug)] pub struct TxNode { /// transaction id in the TRANSACTIONS list @@ -471,70 +479,11 @@ pub struct TxBundle { /// The bundle points to a linked list of transaction nodes, which each reference /// a transaction record by its global id. /// used with add_suffix(canonical addr of account) -pub static ACCOUNT_TXS: AppendStore = AppendStore::new(KEY_ACCOUNT_TXS); +//pub static ACCOUNT_TXS: AppendStore = AppendStore::new(KEY_ACCOUNT_TXS); /// Keeps track of the total count of txs for an account (not tx bundles) /// used with add_suffix(canonical addr of account) -pub static ACCOUNT_TX_COUNT: Item = Item::new(KEY_ACCOUNT_TX_COUNT); - -pub struct AccountTxsStore {} -impl AccountTxsStore { - /// appends a new tx bundle for an account, called when non-transfer tx occurs or is settled. - pub fn append_bundle(store: &mut dyn Storage, account: &CanonicalAddr, head_node: u64, list_len: u16) -> StdResult<()> { - let account_txs_store = ACCOUNT_TXS.add_suffix(account.as_slice()); - let account_txs_len = account_txs_store.get_len(store)?; - let tx_bundle; - if account_txs_len > 0 { - // peek at the last tx bundle added - let last_tx_bundle = account_txs_store.get_at(store, account_txs_len - 1)?; - tx_bundle = TxBundle { - head_node, - list_len, - offset: last_tx_bundle.offset + u32::from(last_tx_bundle.list_len), - }; - } else { // this is the first bundle for the account - tx_bundle = TxBundle { - head_node, - list_len, - offset: 0, - }; - } - - // update the total count of txs for account - let account_tx_count_store = ACCOUNT_TX_COUNT.add_suffix(account.as_slice()); - let account_tx_count = account_tx_count_store.may_load(store)?.unwrap_or_default(); - account_tx_count_store.save(store, &(account_tx_count.saturating_add(u32::from(list_len))))?; - - account_txs_store.push(store, &tx_bundle) - } - - /// Does a binary search on the append store to find the bundle where the `start_idx` tx can be found. - /// For a paginated search `start_idx` = `page` * `page_size`. - /// Returns the bundle index, the bundle, and the index in the bundle list to start at - pub fn find_start_bundle(store: &dyn Storage, account: &CanonicalAddr, start_idx: u32) -> StdResult> { - let account_txs_store = ACCOUNT_TXS.add_suffix(account.as_slice()); - - let mut left = 0u32; - let mut right = account_txs_store.get_len(store)?; - - while left <= right { - let mid = (left + right) / 2; - let mid_bundle = account_txs_store.get_at(store, mid)?; - if start_idx >= mid_bundle.offset && start_idx < mid_bundle.offset + (mid_bundle.list_len as u32) { - // we have the correct bundle - // which index in list to start at? - let start_at = (mid_bundle.list_len as u32) - (start_idx - mid_bundle.offset) - 1; - return Ok(Some((mid, mid_bundle, start_at))); - } else if start_idx < mid_bundle.offset { - right = mid - 1; - } else { - left = mid + 1; - } - } - - Ok(None) - } -} +//pub static ACCOUNT_TX_COUNT: Item = Item::new(KEY_ACCOUNT_TX_COUNT); #[inline] fn constant_time_is_not_zero(value: i32) -> u32 { diff --git a/src/state.rs b/src/state.rs index 09abb2de..30e927e4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -132,47 +132,6 @@ pub fn safe_add_u64(balance: &mut u64, amount: u64) -> u64 { *balance - prev_balance } -pub static BALANCES: Item = Item::new(PREFIX_BALANCES); -pub struct BalancesStore {} -impl BalancesStore { - pub fn save(store: &mut dyn Storage, account: &CanonicalAddr, amount: u128) -> StdResult<()> { - let balances = BALANCES.add_suffix(account.as_slice()); - balances.save(store, &amount) - } - - pub fn load(store: &dyn Storage, account: &CanonicalAddr) -> u128 { - let balances = BALANCES.add_suffix(account.as_slice()); - balances.load(store).unwrap_or_default() - } - - pub fn update_balance( - store: &mut dyn Storage, - account: &CanonicalAddr, - amount_to_be_updated: u128, - should_add: bool, - operation_name: &str, - ) -> StdResult<()> { - let mut balance = Self::load(store, account); - balance = match should_add { - true => { - safe_add(&mut balance, amount_to_be_updated); - balance - } - false => { - if let Some(balance) = balance.checked_sub(amount_to_be_updated) { - balance - } else { - return Err(StdError::generic_err(format!( - "insufficient funds to {operation_name}: balance={balance}, required={amount_to_be_updated}", - ))); - } - } - }; - - Self::save(store, account, balance) - } -} - // Allowances #[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Default, JsonSchema)] @@ -277,3 +236,5 @@ impl ReceiverHashStore { receiver_hash.save(store, &code_hash) } } + +pub static INTERNAL_SECRET: Item> = Item::new(b"internal-secret"); \ No newline at end of file diff --git a/src/stored_balances.rs b/src/stored_balances.rs index 6af94b61..04ecb081 100644 --- a/src/stored_balances.rs +++ b/src/stored_balances.rs @@ -1,11 +1,12 @@ use constant_time_eq::constant_time_eq; -use secret_toolkit::storage::Item; +use primitive_types::U256; +use secret_toolkit::{notification::hkdf_sha_256, serialization::{Bincode2, Serde}, storage::Item}; use serde::{Serialize, Deserialize,}; use serde_big_array::BigArray; use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; use crate::{ - dwb::DelayedWriteBufferEntry, state::safe_add_u64 + dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}, state::{safe_add_u64, INTERNAL_SECRET} }; // btsb = bitwise-trie of stored balances @@ -16,7 +17,6 @@ pub const KEY_BTSB_BUCKETS: &[u8] = b"btsb-buckets"; pub const KEY_BTSB_TRIE_NODES: &[u8] = b"btsb-trie-nodes"; pub const KEY_BTSB_TRIE_NODES_COUNT: &[u8] = b"btsb-trie-nodes-cnt"; - const U16_BYTES: usize = 2; const U32_BYTES: usize = 4; const U64_BYTES: usize = 8; @@ -27,24 +27,32 @@ const BTSB_BUCKET_ADDRESS_BYTES: usize = 54; #[cfg(not(test))] const BTSB_BUCKET_ADDRESS_BYTES: usize = 20; const BTSB_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u128) -const BTSB_BUCKET_HISTORY_BYTES: usize = 5; // Max 8 (u64) +const BTSB_BUCKET_HISTORY_BYTES: usize = 4; // Max 4 (u32) const_assert!(BTSB_BUCKET_BALANCE_BYTES <= U128_BYTES); -const_assert!(BTSB_BUCKET_HISTORY_BYTES <= U64_BYTES); +const_assert!(BTSB_BUCKET_HISTORY_BYTES <= U32_BYTES); const BTSB_BUCKET_ENTRY_BYTES: usize = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES + BTSB_BUCKET_HISTORY_BYTES; const ZERO_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0u8; BTSB_BUCKET_ADDRESS_BYTES]; +/// A `StoredEntry` consists of the address, balance, and tx bundle history length in a byte array representation. +/// The methods of the struct implementation also handle pushing and getting the tx bundle history in a simplified +/// append store. #[derive(Serialize, Deserialize, Clone, Copy, Debug)] #[cfg_attr(test, derive(Eq, PartialEq))] -pub struct StoredBalanceEntry( +pub struct StoredEntry( #[serde(with = "BigArray")] - [u8; BTSB_BUCKET_ENTRY_BYTES] + [u8; BTSB_BUCKET_ENTRY_BYTES], ); -impl StoredBalanceEntry { - pub fn new(address: CanonicalAddr) -> StdResult { +enum BalanceAction { + Sub, + Add, +} + +impl StoredEntry { + fn new(address: CanonicalAddr) -> StdResult { let address = address.as_slice(); if address.len() != BTSB_BUCKET_ADDRESS_BYTES { @@ -52,17 +60,35 @@ impl StoredBalanceEntry { } let mut result = [0u8; BTSB_BUCKET_ENTRY_BYTES]; - result[..BTSB_BUCKET_ENTRY_BYTES].copy_from_slice(address); + result[..BTSB_BUCKET_ADDRESS_BYTES].copy_from_slice(address); Ok(Self { - 0: result + 0: result, }) } - pub fn from(dwb_entry: DelayedWriteBufferEntry) -> StdResult { - let mut entry = StoredBalanceEntry::new(dwb_entry.recipient()?)?; - - entry.set_balace(dwb_entry.amount()?); - entry.set_history_len(1); + fn from(storage: &mut dyn Storage, dwb_entry: &DelayedWriteBufferEntry, amount_spent: Option) -> StdResult { + let mut entry = StoredEntry::new(dwb_entry.recipient()?)?; + + let amount_spent = amount_u64(amount_spent)?; + + // error should never happen because already checked in `settle_sender_or_owner_account` + let balance = if let Some(new_balance) = dwb_entry.amount()?.checked_sub(amount_spent) { + new_balance + } else { + return Err(StdError::generic_err(format!( + "insufficient funds", + ))); + }; + + entry.set_balance(balance)?; + entry.push_tx_bundle( + storage, + &TxBundle { + head_node: dwb_entry.head_node()?, + list_len: dwb_entry.list_len()?, + offset: 0, + } + )?; Ok(entry) } @@ -87,26 +113,26 @@ impl StoredBalanceEntry { Ok(u64::from_be_bytes(result)) } - fn set_balace(&mut self, val: u64) -> StdResult<()> { + fn set_balance(&mut self, val: u64) -> StdResult<()> { let start = BTSB_BUCKET_ADDRESS_BYTES; let end = start + BTSB_BUCKET_BALANCE_BYTES; self.0[start..end].copy_from_slice(&val.to_be_bytes()); Ok(()) } - pub fn history_len(&self) -> StdResult { + pub fn history_len(&self) -> StdResult { let start = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES; let end = start + BTSB_BUCKET_HISTORY_BYTES; let history_len_slice = &self.0[start..end]; - let mut result = [0u8; U64_BYTES]; - result[U64_BYTES - BTSB_BUCKET_HISTORY_BYTES..].copy_from_slice(history_len_slice); - Ok(u64::from_be_bytes(result)) + let mut result = [0u8; U32_BYTES]; + result[U32_BYTES - BTSB_BUCKET_HISTORY_BYTES..].copy_from_slice(history_len_slice); + Ok(u32::from_be_bytes(result)) } - fn set_history_len(&mut self, val: u64) -> StdResult<()> { + fn set_history_len(&mut self, val: u32) -> StdResult<()> { let start = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES; let end = start + BTSB_BUCKET_HISTORY_BYTES; - let val_bytes = &val.to_be_bytes()[U64_BYTES - BTSB_BUCKET_HISTORY_BYTES..]; + let val_bytes = &val.to_be_bytes()[U32_BYTES - BTSB_BUCKET_HISTORY_BYTES..]; if val_bytes.len() != BTSB_BUCKET_HISTORY_BYTES { return Err(StdError::generic_err("Set bucket history len error")); } @@ -114,30 +140,101 @@ impl StoredBalanceEntry { Ok(()) } - pub fn merge_dwb_entry(&mut self, entry: &DelayedWriteBufferEntry) -> StdResult<()> { - let mut balance = self.balance()?; - safe_add_u64(&mut balance, entry.amount()?); - self.set_balace(balance)?; + pub fn merge_dwb_entry( + &mut self, + storage: &mut dyn Storage, + dwb_entry: &DelayedWriteBufferEntry, + amount_spent: Option + ) -> StdResult<()> { + let history_len = self.history_len()?; + if history_len == 0 { + return Err(StdError::generic_err("use `from` to create new entry from dwb_entry")); + } - // TOOD: update history len + let mut balance = self.balance()?; + safe_add_u64(&mut balance, dwb_entry.amount()?); + + let amount_spent = amount_u64(amount_spent)?; + + // error should never happen because already checked in `settle_sender_or_owner_account` + let balance = if let Some(new_balance) = dwb_entry.amount()?.checked_sub(amount_spent) { + new_balance + } else { + return Err(StdError::generic_err(format!( + "insufficient funds", + ))); + }; + + self.set_balance(balance)?; + + // peek at the last tx bundle added + let last_tx_bundle = self.get_tx_bundle_at(storage, history_len - 1)?; + let tx_bundle = TxBundle { + head_node: dwb_entry.head_node()?, + list_len: dwb_entry.list_len()?, + offset: last_tx_bundle.offset + u32::from(last_tx_bundle.list_len), + }; + self.push_tx_bundle(storage, &tx_bundle)?; Ok(()) } -} + // simplified appendstore impl for tx history + + /// gets the element at pos if within bounds + pub fn get_tx_bundle_at(&self, storage: &dyn Storage, pos: u32) -> StdResult { + let len = self.history_len()?; + if pos >= len { + return Err(StdError::generic_err("access out of bounds")); + } + self.get_tx_bundle_at_unchecked(storage, pos) + } + + /// tries to get the element at pos + fn get_tx_bundle_at_unchecked(&self, storage: &dyn Storage, pos: u32) -> StdResult { + let bundle_data = storage.get(&[KEY_BTSB_ENTRY_HISTORY, self.address_slice(), pos.to_be_bytes().as_slice()].concat()); + let bundle_data = bundle_data.ok_or_else(|| { return StdError::generic_err("tx bundle not found"); } )?; + Bincode2::deserialize( + &bundle_data + ) + } + /// Replaces data at a position within bounds + fn set_tx_bundle_at(&self, storage: &mut dyn Storage, pos: u32, item: &TxBundle) -> StdResult<()> { + let len = self.history_len()?; + if pos >= len { + return Err(StdError::generic_err("access out of bounds")); + } + self.set_tx_bundle_at_unchecked(storage, pos, item) + } + /// Sets data at a given index + fn set_tx_bundle_at_unchecked(&self, storage: &mut dyn Storage, pos: u32, bundle: &TxBundle) -> StdResult<()> { + let bundle_data = Bincode2::serialize(bundle)?; + storage.set(&[KEY_BTSB_ENTRY_HISTORY, self.address_slice(), pos.to_be_bytes().as_slice()].concat(), &bundle_data); + Ok(()) + } + + /// Pushes a tx bundle + fn push_tx_bundle(&mut self, storage: &mut dyn Storage, bundle: &TxBundle) -> StdResult<()> { + let len = self.history_len()?; + self.set_tx_bundle_at_unchecked(storage, len, bundle)?; + self.set_history_len(len.saturating_add(1))?; + Ok(()) + } + +} const BTSB_BUCKET_LEN: u16 = 128; #[derive(Serialize, Deserialize, Clone, Copy, Debug)] -struct BtsbBucket { +pub struct BtsbBucket { pub capacity: u16, #[serde(with = "BigArray")] - pub entries: [StoredBalanceEntry; BTSB_BUCKET_LEN as usize], + pub entries: [StoredEntry; BTSB_BUCKET_LEN as usize], } -static BTSB_ENTRY_HISTORY: Item = Item::new(KEY_BTSB_ENTRY_HISTORY); +//static BTSB_ENTRY_HISTORY: Item = Item::new(KEY_BTSB_ENTRY_HISTORY); static BTSB_BUCKETS_COUNT: Item = Item::new(KEY_BTSB_BUCKETS_COUNT); static BTSB_BUCKETS: Item = Item::new(KEY_BTSB_BUCKETS); @@ -149,31 +246,28 @@ impl BtsbBucket { Ok(Self { capacity: BTSB_BUCKET_LEN, entries: [ - StoredBalanceEntry::new(CanonicalAddr::from(&ZERO_ADDR))?; BTSB_BUCKET_LEN as usize + StoredEntry::new(CanonicalAddr::from(&ZERO_ADDR))?; BTSB_BUCKET_LEN as usize ] }) } - pub fn add_entry(&mut self, storage: &mut dyn Storage, entry: &StoredBalanceEntry, bit_pos: u8) -> StdResult { - match self.capacity { + pub fn add_entry(&mut self, storage: &mut dyn Storage, entry: &StoredEntry) -> bool { + if self.capacity == 0 { // buffer is at capacity - 0 => Err(StdError::generic_err("")), - - // has capacity for a new entry - _ => { - // save entry to bucket - self.entries[self.entries.len() - self.capacity as usize] = entry.clone(); + return false; + } + // has capacity for a new entry + // save entry to bucket + self.entries[self.entries.len() - self.capacity as usize] = entry.clone(); - // update capacity - self.capacity -= 1; + // update capacity + self.capacity -= 1; - // done - Ok(self.capacity) - } - } + // done + true } - pub fn constant_time_find_address(&self, address: &CanonicalAddr) -> Option { + pub fn constant_time_find_address(&self, address: &CanonicalAddr) -> Option { let address = address.as_slice(); let mut matched_index_p1: BucketEntryPosition = 0; @@ -188,7 +282,7 @@ impl BtsbBucket { } } - pub fn quick_find_entry(&self, address: &CanonicalAddr) -> Option { + pub fn quick_find_entry(&self, address: &CanonicalAddr) -> Option { let address = address.as_slice(); let mut matched_index_p1: BucketEntryPosition = 0; @@ -210,7 +304,6 @@ pub struct BitwiseTrieNode { pub bucket: u64, } - pub static BTSB_TRIE_NODES: Item = Item::new(KEY_BTSB_TRIE_NODES); pub static BTSB_TRIE_NODES_COUNT: Item = Item::new(KEY_BTSB_TRIE_NODES_COUNT); @@ -237,7 +330,7 @@ impl BitwiseTrieNode { } // loads the node's bucket from storage - pub fn bucket(self, storage: &mut dyn Storage) -> StdResult { + pub fn bucket(self, storage: &dyn Storage) -> StdResult { if self.bucket == 0 { return Err(StdError::generic_err("btsb: attempted to load bucket of branch node")); } @@ -258,9 +351,13 @@ impl BitwiseTrieNode { // locates a btsb node given an address; returns tuple of (node, bit position) -pub fn locate_btsb_node(storage: &mut dyn Storage, address: &CanonicalAddr) -> StdResult<(BitwiseTrieNode, u64, u8)> { - let hash: [u8; 32] = [0u8; 32]; - /* TODO: +pub fn locate_btsb_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult<(BitwiseTrieNode, u64, u8)> { + //let hash: [u8; 32] = [0u8; 32]; + + let secret = INTERNAL_SECRET.load(storage)?; + let secret = secret.as_slice(); + let hash = hkdf_sha_256(&None, secret, address.as_slice(), 256)?; + /* let hash := hkdf(ikm=contractInternalSecret, info=addrress, length=256bits) */ @@ -287,9 +384,49 @@ pub fn locate_btsb_node(storage: &mut dyn Storage, address: &CanonicalAddr) -> S Ok((node, node_id, bit_pos)) } +// returns the current stored balance for an entry +pub fn stored_balance(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult { + let (node, _, _) = locate_btsb_node(storage, address)?; + let bucket = node.bucket(storage)?; + if let Some(entry) = bucket.constant_time_find_address(address) { + Ok(entry.balance()? as u128) + } else { + Ok(0_u128) + } +} + +/// Does a binary search on the append store to find the bundle where the `start_idx` tx can be found. +/// For a paginated search `start_idx` = `page` * `page_size`. +/// Returns the bundle index, the bundle, and the index in the bundle list to start at +pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_idx: u32) -> StdResult> { + let (node, _, _) = locate_btsb_node(storage, account)?; + let bucket = node.bucket(storage)?; + if let Some(entry) = bucket.constant_time_find_address(account) { + let mut left = 0u32; + let mut right = entry.history_len()?; + + while left <= right { + let mid = (left + right) / 2; + let mid_bundle = entry.get_tx_bundle_at(storage, mid)?; + if start_idx >= mid_bundle.offset && start_idx < mid_bundle.offset + (mid_bundle.list_len as u32) { + // we have the correct bundle + // which index in list to start at? + let start_at = (mid_bundle.list_len as u32) - (start_idx - mid_bundle.offset) - 1; + return Ok(Some((mid, mid_bundle, start_at))); + } else if start_idx < mid_bundle.offset { + right = mid - 1; + } else { + left = mid + 1; + } + } + } + + Ok(None) +} // merges a dwb entry into the current node's bucket -pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferEntry) -> StdResult<()> { +// `spent_amount` is any required subtraction due to being sender of tx +pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferEntry, amount_spent: Option) -> StdResult<()> { // locate the node that the given entry belongs in let (mut node, node_id, bit_pos) = locate_btsb_node(storage, &dwb_entry.recipient()?)?; @@ -297,113 +434,111 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE let mut bucket = node.bucket(storage)?; // search for an existing entry - match bucket.constant_time_find_address(&dwb_entry.recipient()?) { + if let Some(mut found_entry) = bucket.constant_time_find_address(&dwb_entry.recipient()?) { // found existing entry - Some(mut found_entry) => { - // merge amount and history from dwb entry - found_entry.merge_dwb_entry(&dwb_entry); - - // save updated bucket to storage - node.set_and_save_bucket(storage, bucket); - }, + // merge amount and history from dwb entry + found_entry.merge_dwb_entry(storage, &dwb_entry, amount_spent)?; + // save updated bucket to storage + node.set_and_save_bucket(storage, bucket)?; + } else { // need to insert new entry - None => { - // create new stored balance entry - let btsb_entry = StoredBalanceEntry::from(dwb_entry)?; - - /* TODO: - create new storage for dwb_entry's history - */ - - // try to add to the current bucket - match bucket.add_entry(storage, &btsb_entry, bit_pos) { - // bucket has capcity and it added the new entry - Ok(capacity) => { - // save bucket to storage - node.set_and_save_bucket(storage, bucket); - } - - // bucket is full; split on next bit position - Err(_) => { - // create new left and right buckets - let left_bucket = BtsbBucket::new()?; - let right_bucket = BtsbBucket::new()?; - - // each entry - for (idx, entry) in bucket.entries.iter().enumerate() { - /* TODO: - let key := hkdf(ikm=contractInternalSecret, info=canonical(addr), length=256bits) - let bit_value := (key >> (255 - bit_pos)) & 1 - if bit_value == 0: - left_bucket.add_entry(entry) - else: - right_bucket.add_entry(entry) - */ - } - - // save left node's bucket to storage, recycling this node's bucket ID - let left_bucket_id = node.bucket; - BTSB_BUCKETS.add_suffix(&left_bucket_id.to_be_bytes()).save(storage, &left_bucket); - - // global count of buckets - let mut buckets_count = BTSB_BUCKETS_COUNT.load(storage).unwrap_or_default(); - - // bucket ID for right node - buckets_count += 1; - let right_bucket_id = buckets_count; - BTSB_BUCKETS.add_suffix(&right_bucket_id.to_be_bytes()).save(storage, &right_bucket); - - // save updated count - BTSB_BUCKETS_COUNT.save(storage, &buckets_count)?; - - // globl count of trie nodes - let mut nodes_count = BTSB_TRIE_NODES_COUNT.load(storage).unwrap_or_default(); - - // ID for left node - nodes_count += 1; - let left_id = nodes_count; - - // ID for right node - nodes_count += 1; - let right_id = nodes_count; - - // save updated count - BTSB_TRIE_NODES_COUNT.save(storage, &nodes_count)?; - - // create left and right nodes - let left = BitwiseTrieNode { - left: 0, - right: 0, - bucket: left_bucket_id, - }; - let right = BitwiseTrieNode { - left: 0, - right: 0, - bucket: right_bucket_id, - }; - - // save left and right node to storage - BTSB_TRIE_NODES.add_suffix(&left_id.to_be_bytes()).save(storage, &left)?; - BTSB_TRIE_NODES.add_suffix(&right_id.to_be_bytes()).save(storage, &right)?; - - // convert this into a branch node - node.left = left_id; - node.right = right_id; - node.bucket = 0; - - // save node - BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node); - - // -- - - /* TODO - determine which child node the dwb entry belongs in, then retry insertion, - looping as many times as needed until the bucket has capacity for a new entry - */ + // create new stored balance entry + let btsb_entry = StoredEntry::from(storage, &dwb_entry, amount_spent)?; + + // try to add to the current bucket + if bucket.add_entry(storage, &btsb_entry) { + // bucket has capcity and it added the new entry + // save bucket to storage + node.set_and_save_bucket(storage, bucket)?; + } else { + // bucket is full; split on next bit position + // create new left and right buckets + let mut left_bucket = BtsbBucket::new()?; + let mut right_bucket = BtsbBucket::new()?; + + let secret = INTERNAL_SECRET.load(storage)?; + let secret = secret.as_slice(); + // each entry + for entry in bucket.entries { + let key = hkdf_sha_256(&None, secret, entry.address_slice(), 256)?; + let key = U256::from_big_endian(&key); + let bit_value = (key >> (255 - bit_pos)) & U256::from(1); + if bit_value == U256::from(0) { + left_bucket.add_entry(storage, &entry); + } else { + right_bucket.add_entry(storage, &entry); } + /* + let key := hkdf(ikm=contractInternalSecret, info=canonical(addr), length=256bits) + let bit_value := (key >> (255 - bit_pos)) & 1 + if bit_value == 0: + left_bucket.add_entry(entry) + else: + right_bucket.add_entry(entry) + */ } - }, + + // save left node's bucket to storage, recycling this node's bucket ID + let left_bucket_id = node.bucket; + BTSB_BUCKETS.add_suffix(&left_bucket_id.to_be_bytes()).save(storage, &left_bucket)?; + + // global count of buckets + let mut buckets_count = BTSB_BUCKETS_COUNT.load(storage).unwrap_or_default(); + + // bucket ID for right node + buckets_count += 1; + let right_bucket_id = buckets_count; + BTSB_BUCKETS.add_suffix(&right_bucket_id.to_be_bytes()).save(storage, &right_bucket)?; + + // save updated count + BTSB_BUCKETS_COUNT.save(storage, &buckets_count)?; + + // global count of trie nodes + let mut nodes_count = BTSB_TRIE_NODES_COUNT.load(storage).unwrap_or_default(); + + // ID for left node + nodes_count += 1; + let left_id = nodes_count; + + // ID for right node + nodes_count += 1; + let right_id = nodes_count; + + // save updated count + BTSB_TRIE_NODES_COUNT.save(storage, &nodes_count)?; + + // create left and right nodes + let left = BitwiseTrieNode { + left: 0, + right: 0, + bucket: left_bucket_id, + }; + let right = BitwiseTrieNode { + left: 0, + right: 0, + bucket: right_bucket_id, + }; + + // save left and right node to storage + BTSB_TRIE_NODES.add_suffix(&left_id.to_be_bytes()).save(storage, &left)?; + BTSB_TRIE_NODES.add_suffix(&right_id.to_be_bytes()).save(storage, &right)?; + + // convert this into a branch node + node.left = left_id; + node.right = right_id; + node.bucket = 0; + + // save node + BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node)?; + + // -- + + /* TODO + determine which child node the dwb entry belongs in, then retry insertion, + looping as many times as needed until the bucket has capacity for a new entry + */ + } } Ok(()) @@ -411,15 +546,15 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE // for fetching an account's stored balance during transfer executions -pub fn constant_time_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { - let (mut node, node_id, bit_pos) = locate_btsb_node(storage, &address)?; +pub fn constant_time_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { + let (node, _, _) = locate_btsb_node(storage, &address)?; Ok(node.bucket(storage)?.constant_time_find_address(&address)) } // for fetching account's stored balance and/or history during queries -pub fn quick_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { - let (mut node, node_id, bit_pos) = locate_btsb_node(storage, &address)?; +pub fn quick_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { + let (node, _, _) = locate_btsb_node(storage, &address)?; Ok(node.bucket(storage)?.quick_find_entry(&address)) } From 8d2d83ecd5c13e342265917c4cbea8c3279a691f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 10 Jul 2024 16:55:54 +1200 Subject: [PATCH 39/87] update query_transactions --- src/contract.rs | 75 ++++++++++++++++++++++-------------------- src/stored_balances.rs | 35 ++++++++++++-------- 2 files changed, 60 insertions(+), 50 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 9b6b5661..583347ab 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -20,7 +20,7 @@ use crate::receiver::Snip20ReceiveMsg; use crate::state::{ safe_add, AllowancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, PRNG, TOTAL_SUPPLY }; -use crate::stored_balances::{find_start_bundle, stored_balance}; +use crate::stored_balances::{find_start_bundle, stored_balance, stored_entry, stored_tx_count}; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx @@ -617,8 +617,9 @@ pub fn query_transactions( } } - let account_slice = account_raw.as_slice(); - let settled_tx_count = ACCOUNT_TX_COUNT.add_suffix(account_slice).load(deps.storage)?; + //let account_slice = account_raw.as_slice(); + let account_stored_entry = stored_entry(deps.storage, &account_raw)?; + let settled_tx_count = stored_tx_count(deps.storage, &account_stored_entry)?; let total = txs_in_dwb_count as u32 + settled_tx_count as u32; if end > total { end = total; @@ -637,24 +638,25 @@ pub fn query_transactions( //println!("OPTION 2"); txs = txs_in_dwb[start as usize..].to_vec(); // reverse chronological let mut txs_left = (end - start).saturating_sub(txs.len() as u32); - let tx_bundles_store = ACCOUNT_TXS.add_suffix(account_slice); - let tx_bundles_idx_len = tx_bundles_store.get_len(deps.storage)?; - if tx_bundles_idx_len > 0 { - let mut bundle_idx = tx_bundles_idx_len - 1; - loop { - let tx_bundle = tx_bundles_store.get_at(deps.storage, bundle_idx.clone())?; - let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; - let list_len = tx_bundle.list_len as u32; - if txs_left <= list_len { - txs.extend_from_slice(&head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize]); - break; - } - txs.extend(head_node.to_vec(deps.storage, deps.api)?); - txs_left = txs_left.saturating_sub(list_len); - if bundle_idx > 0 { - bundle_idx -= 1; - } else { - break; + if let Some(entry) = account_stored_entry { + let tx_bundles_idx_len = entry.history_len()?; + if tx_bundles_idx_len > 0 { + let mut bundle_idx = tx_bundles_idx_len - 1; + loop { + let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; + let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; + let list_len = tx_bundle.list_len as u32; + if txs_left <= list_len { + txs.extend_from_slice(&head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize]); + break; + } + txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs_left = txs_left.saturating_sub(list_len); + if bundle_idx > 0 { + bundle_idx -= 1; + } else { + break; + } } } } @@ -687,21 +689,22 @@ pub fn query_transactions( if bundle_idx > 0 && txs_left > 0 { // get the next earlier bundle let mut bundle_idx = bundle_idx - 1; - let tx_bundles_store = ACCOUNT_TXS.add_suffix(account_slice); - loop { - let tx_bundle = tx_bundles_store.get_at(deps.storage, bundle_idx.clone())?; - let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; - let list_len = tx_bundle.list_len as u32; - if txs_left <= list_len { - txs.extend_from_slice(&head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize]); - break; - } - txs.extend(head_node.to_vec(deps.storage, deps.api)?); - txs_left = txs_left.saturating_sub(list_len); - if bundle_idx > 0 { - bundle_idx -= 1; - } else { - break; + if let Some(entry) = account_stored_entry { + loop { + let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; + let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; + let list_len = tx_bundle.list_len as u32; + if txs_left <= list_len { + txs.extend_from_slice(&head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize]); + break; + } + txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs_left = txs_left.saturating_sub(list_len); + if bundle_idx > 0 { + bundle_idx -= 1; + } else { + break; + } } } } diff --git a/src/stored_balances.rs b/src/stored_balances.rs index 04ecb081..b7e04c3f 100644 --- a/src/stored_balances.rs +++ b/src/stored_balances.rs @@ -46,11 +46,6 @@ pub struct StoredEntry( [u8; BTSB_BUCKET_ENTRY_BYTES], ); -enum BalanceAction { - Sub, - Add, -} - impl StoredEntry { fn new(address: CanonicalAddr) -> StdResult { let address = address.as_slice(); @@ -199,15 +194,6 @@ impl StoredEntry { ) } - /// Replaces data at a position within bounds - fn set_tx_bundle_at(&self, storage: &mut dyn Storage, pos: u32, item: &TxBundle) -> StdResult<()> { - let len = self.history_len()?; - if pos >= len { - return Err(StdError::generic_err("access out of bounds")); - } - self.set_tx_bundle_at_unchecked(storage, pos, item) - } - /// Sets data at a given index fn set_tx_bundle_at_unchecked(&self, storage: &mut dyn Storage, pos: u32, bundle: &TxBundle) -> StdResult<()> { let bundle_data = Bincode2::serialize(bundle)?; @@ -424,6 +410,27 @@ pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_i Ok(None) } +/// gets the StoredEntry for a given account +pub fn stored_entry(storage: &dyn Storage, account: &CanonicalAddr) -> StdResult> { + let (node, _, _) = locate_btsb_node(storage, account)?; + let bucket = node.bucket(storage)?; + Ok(bucket.constant_time_find_address(account)) +} + +/// Returns the total number of settled transactions for an account by peeking at last bundle +pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> StdResult { + if let Some(entry) = entry { + // peek at last entry + let len = entry.history_len()?; + if len > 0 { + let bundle = entry.get_tx_bundle_at(storage, len - 1)?; + return Ok(bundle.offset + bundle.list_len as u32); + } + } + Ok(0) +} + + // merges a dwb entry into the current node's bucket // `spent_amount` is any required subtraction due to being sender of tx pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferEntry, amount_spent: Option) -> StdResult<()> { From 5bccca295d931d54c5b7775e231da017899435ff Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 10 Jul 2024 17:03:34 +1200 Subject: [PATCH 40/87] refactor stored_balance to use stored_entry --- src/stored_balances.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/stored_balances.rs b/src/stored_balances.rs index b7e04c3f..91e64312 100644 --- a/src/stored_balances.rs +++ b/src/stored_balances.rs @@ -370,17 +370,6 @@ pub fn locate_btsb_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdRe Ok((node, node_id, bit_pos)) } -// returns the current stored balance for an entry -pub fn stored_balance(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult { - let (node, _, _) = locate_btsb_node(storage, address)?; - let bucket = node.bucket(storage)?; - if let Some(entry) = bucket.constant_time_find_address(address) { - Ok(entry.balance()? as u128) - } else { - Ok(0_u128) - } -} - /// Does a binary search on the append store to find the bundle where the `start_idx` tx can be found. /// For a paginated search `start_idx` = `page` * `page_size`. /// Returns the bundle index, the bundle, and the index in the bundle list to start at @@ -417,6 +406,15 @@ pub fn stored_entry(storage: &dyn Storage, account: &CanonicalAddr) -> StdResult Ok(bucket.constant_time_find_address(account)) } +/// returns the current stored balance for an entry +pub fn stored_balance(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult { + if let Some(entry) = stored_entry(storage, address)? { + Ok(entry.balance()? as u128) + } else { + Ok(0_u128) + } +} + /// Returns the total number of settled transactions for an account by peeking at last bundle pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> StdResult { if let Some(entry) = entry { From 86cd75357204de192a301968455a64f4a23d511d Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 10 Jul 2024 21:06:53 +1200 Subject: [PATCH 41/87] dev: add child loop when inserting dwb entry --- src/stored_balances.rs | 184 ++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 86 deletions(-) diff --git a/src/stored_balances.rs b/src/stored_balances.rs index 91e64312..9b02d134 100644 --- a/src/stored_balances.rs +++ b/src/stored_balances.rs @@ -451,98 +451,110 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE // create new stored balance entry let btsb_entry = StoredEntry::from(storage, &dwb_entry, amount_spent)?; - // try to add to the current bucket - if bucket.add_entry(storage, &btsb_entry) { - // bucket has capcity and it added the new entry - // save bucket to storage - node.set_and_save_bucket(storage, bucket)?; - } else { - // bucket is full; split on next bit position - // create new left and right buckets - let mut left_bucket = BtsbBucket::new()?; - let mut right_bucket = BtsbBucket::new()?; - - let secret = INTERNAL_SECRET.load(storage)?; - let secret = secret.as_slice(); - // each entry - for entry in bucket.entries { - let key = hkdf_sha_256(&None, secret, entry.address_slice(), 256)?; + let secret = INTERNAL_SECRET.load(storage)?; + let secret = secret.as_slice(); + + loop { + // try to add to the current bucket + if bucket.add_entry(storage, &btsb_entry) { + // bucket has capacity and it added the new entry + // save bucket to storage + node.set_and_save_bucket(storage, bucket)?; + // break out of the loop + break; + } else { + // bucket is full; split on next bit position + // create new left and right buckets + let mut left_bucket = BtsbBucket::new()?; + let mut right_bucket = BtsbBucket::new()?; + + // each entry + for entry in bucket.entries { + let key = hkdf_sha_256(&None, secret, entry.address_slice(), 256)?; + let key = U256::from_big_endian(&key); + let bit_value = (key >> (255 - bit_pos)) & U256::from(1); + if bit_value == U256::from(0) { + left_bucket.add_entry(storage, &entry); + } else { + right_bucket.add_entry(storage, &entry); + } + /* + let key := hkdf(ikm=contractInternalSecret, info=canonical(addr), length=256bits) + let bit_value := (key >> (255 - bit_pos)) & 1 + if bit_value == 0: + left_bucket.add_entry(entry) + else: + right_bucket.add_entry(entry) + */ + } + + // save left node's bucket to storage, recycling this node's bucket ID + let left_bucket_id = node.bucket; + BTSB_BUCKETS.add_suffix(&left_bucket_id.to_be_bytes()).save(storage, &left_bucket)?; + + // global count of buckets + let mut buckets_count = BTSB_BUCKETS_COUNT.load(storage).unwrap_or_default(); + + // bucket ID for right node + buckets_count += 1; + let right_bucket_id = buckets_count; + BTSB_BUCKETS.add_suffix(&right_bucket_id.to_be_bytes()).save(storage, &right_bucket)?; + + // save updated count + BTSB_BUCKETS_COUNT.save(storage, &buckets_count)?; + + // global count of trie nodes + let mut nodes_count = BTSB_TRIE_NODES_COUNT.load(storage).unwrap_or_default(); + + // ID for left node + nodes_count += 1; + let left_id = nodes_count; + + // ID for right node + nodes_count += 1; + let right_id = nodes_count; + + // save updated count + BTSB_TRIE_NODES_COUNT.save(storage, &nodes_count)?; + + // create left and right nodes + let left = BitwiseTrieNode { + left: 0, + right: 0, + bucket: left_bucket_id, + }; + let right = BitwiseTrieNode { + left: 0, + right: 0, + bucket: right_bucket_id, + }; + + // save left and right node to storage + BTSB_TRIE_NODES.add_suffix(&left_id.to_be_bytes()).save(storage, &left)?; + BTSB_TRIE_NODES.add_suffix(&right_id.to_be_bytes()).save(storage, &right)?; + + // convert this into a branch node + node.left = left_id; + node.right = right_id; + node.bucket = 0; + + // save node + BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node)?; + + let key = hkdf_sha_256(&None, secret, btsb_entry.address_slice(), 256)?; let key = U256::from_big_endian(&key); let bit_value = (key >> (255 - bit_pos)) & U256::from(1); + + // determine which child node the dwb entry belongs in, then retry insertion, + // looping as many times as needed until the bucket has capacity for a new entry if bit_value == U256::from(0) { - left_bucket.add_entry(storage, &entry); + node = left; + bucket = left_bucket; } else { - right_bucket.add_entry(storage, &entry); + node = right; + bucket = right_bucket; } - /* - let key := hkdf(ikm=contractInternalSecret, info=canonical(addr), length=256bits) - let bit_value := (key >> (255 - bit_pos)) & 1 - if bit_value == 0: - left_bucket.add_entry(entry) - else: - right_bucket.add_entry(entry) - */ } - - // save left node's bucket to storage, recycling this node's bucket ID - let left_bucket_id = node.bucket; - BTSB_BUCKETS.add_suffix(&left_bucket_id.to_be_bytes()).save(storage, &left_bucket)?; - - // global count of buckets - let mut buckets_count = BTSB_BUCKETS_COUNT.load(storage).unwrap_or_default(); - - // bucket ID for right node - buckets_count += 1; - let right_bucket_id = buckets_count; - BTSB_BUCKETS.add_suffix(&right_bucket_id.to_be_bytes()).save(storage, &right_bucket)?; - - // save updated count - BTSB_BUCKETS_COUNT.save(storage, &buckets_count)?; - - // global count of trie nodes - let mut nodes_count = BTSB_TRIE_NODES_COUNT.load(storage).unwrap_or_default(); - - // ID for left node - nodes_count += 1; - let left_id = nodes_count; - - // ID for right node - nodes_count += 1; - let right_id = nodes_count; - - // save updated count - BTSB_TRIE_NODES_COUNT.save(storage, &nodes_count)?; - - // create left and right nodes - let left = BitwiseTrieNode { - left: 0, - right: 0, - bucket: left_bucket_id, - }; - let right = BitwiseTrieNode { - left: 0, - right: 0, - bucket: right_bucket_id, - }; - - // save left and right node to storage - BTSB_TRIE_NODES.add_suffix(&left_id.to_be_bytes()).save(storage, &left)?; - BTSB_TRIE_NODES.add_suffix(&right_id.to_be_bytes()).save(storage, &right)?; - - // convert this into a branch node - node.left = left_id; - node.right = right_id; - node.bucket = 0; - - // save node - BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node)?; - - // -- - - /* TODO - determine which child node the dwb entry belongs in, then retry insertion, - looping as many times as needed until the bucket has capacity for a new entry - */ } } From c199827ff704ecdf581b7f2095662bef786cb784 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 10 Jul 2024 21:08:39 +1200 Subject: [PATCH 42/87] comments --- src/stored_balances.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/stored_balances.rs b/src/stored_balances.rs index 9b02d134..45317ddc 100644 --- a/src/stored_balances.rs +++ b/src/stored_balances.rs @@ -454,7 +454,7 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE let secret = INTERNAL_SECRET.load(storage)?; let secret = secret.as_slice(); - loop { + loop { // looping as many times as needed until the bucket has capacity for a new entry // try to add to the current bucket if bucket.add_entry(storage, &btsb_entry) { // bucket has capacity and it added the new entry @@ -544,9 +544,8 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE let key = hkdf_sha_256(&None, secret, btsb_entry.address_slice(), 256)?; let key = U256::from_big_endian(&key); let bit_value = (key >> (255 - bit_pos)) & U256::from(1); - + // determine which child node the dwb entry belongs in, then retry insertion, - // looping as many times as needed until the bucket has capacity for a new entry if bit_value == U256::from(0) { node = left; bucket = left_bucket; From 06e82d723f7caf975e873ce9e5dc9e534b258b71 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 10 Jul 2024 21:27:25 +1200 Subject: [PATCH 43/87] dev: inc bit_pos --- src/stored_balances.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/stored_balances.rs b/src/stored_balances.rs index 45317ddc..ac4c8bf9 100644 --- a/src/stored_balances.rs +++ b/src/stored_balances.rs @@ -433,7 +433,7 @@ pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> St // `spent_amount` is any required subtraction due to being sender of tx pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferEntry, amount_spent: Option) -> StdResult<()> { // locate the node that the given entry belongs in - let (mut node, node_id, bit_pos) = locate_btsb_node(storage, &dwb_entry.recipient()?)?; + let (mut node, node_id, mut bit_pos) = locate_btsb_node(storage, &dwb_entry.recipient()?)?; // load that node's current bucket let mut bucket = node.bucket(storage)?; @@ -545,7 +545,7 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE let key = U256::from_big_endian(&key); let bit_value = (key >> (255 - bit_pos)) & U256::from(1); - // determine which child node the dwb entry belongs in, then retry insertion, + // determine which child node the dwb entry belongs in, then retry insertion if bit_value == U256::from(0) { node = left; bucket = left_bucket; @@ -553,6 +553,8 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE node = right; bucket = right_bucket; } + // increment bit position for next iteration of the loop + bit_pos += 1; } } } From bcd8938d02372542a79df42dcb1a9a2b37878084 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 10 Jul 2024 22:21:01 +1200 Subject: [PATCH 44/87] dev: init btsb --- src/contract.rs | 51 ++++++++++++++++++++++-------------------- src/stored_balances.rs | 18 +++++++++++++++ 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 583347ab..d7c47a5d 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -20,7 +20,7 @@ use crate::receiver::Snip20ReceiveMsg; use crate::state::{ safe_add, AllowancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, PRNG, TOTAL_SUPPLY }; -use crate::stored_balances::{find_start_bundle, stored_balance, stored_entry, stored_tx_count}; +use crate::stored_balances::{find_start_bundle, initialize_btsb, stored_balance, stored_entry, stored_tx_count}; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx @@ -64,17 +64,39 @@ pub fn instantiate( let prng_seed_hashed = sha_256(&msg.prng_seed.0); PrngStore::save(deps.storage, prng_seed_hashed)?; + // initialize the bitwise-trie of stored entries + initialize_btsb(deps.storage)?; + // initialize the delay write buffer DWB.save(deps.storage, &DelayedWriteBuffer::new()?)?; let initial_balances = msg.initial_balances.unwrap_or_default(); let raw_admin = deps.api.addr_canonicalize(admin.as_str())?; - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &prng_seed_hashed); + let rng_seed = env.block.random.as_ref().unwrap(); + let mut rng = ContractPrng::new(rng_seed.as_slice(), &prng_seed_hashed); + + // use entropy and env.random to create an internal secret for the contract + let entropy = msg.prng_seed.0.as_slice(); + let entropy_len = 16 + info.sender.to_string().len() + entropy.len(); + let mut rng_entropy = Vec::with_capacity(entropy_len); + rng_entropy.extend_from_slice(&env.block.height.to_be_bytes()); + rng_entropy.extend_from_slice(&env.block.time.seconds().to_be_bytes()); + rng_entropy.extend_from_slice(info.sender.as_bytes()); + rng_entropy.extend_from_slice(entropy); + + // Create INTERNAL_SECRET + let salt = Some(sha_256(&rng_entropy).to_vec()); + let internal_secret = hkdf_sha_256( + &salt, + rng_seed.0.as_slice(), + "contract_internal_secret".as_bytes(), + 32, + )?; + INTERNAL_SECRET.save(deps.storage, &internal_secret)?; + for balance in initial_balances { let amount = balance.amount.u128(); let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?; - perform_mint( deps.storage, &mut rng, @@ -128,26 +150,6 @@ pub fn instantiate( ViewingKey::set_seed(deps.storage, &prng_seed_hashed); - // use entropy and env.random to create an internal secret for the contract - let entropy = msg.prng_seed.0.as_slice(); - let entropy_len = 16 + info.sender.to_string().len() + entropy.len(); - let mut rng_entropy = Vec::with_capacity(entropy_len); - rng_entropy.extend_from_slice(&env.block.height.to_be_bytes()); - rng_entropy.extend_from_slice(&env.block.time.seconds().to_be_bytes()); - rng_entropy.extend_from_slice(info.sender.as_bytes()); - rng_entropy.extend_from_slice(entropy); - let rng_seed = env.block.random.as_ref().unwrap(); - - // Create INTERNAL_SECRET - let salt = Some(sha_256(&rng_entropy).to_vec()); - let internal_secret = hkdf_sha_256( - &salt, - rng_seed.0.as_slice(), - "contract_internal_secret".as_bytes(), - 32, - )?; - INTERNAL_SECRET.save(deps.storage, &internal_secret)?; - Ok(Response::default()) } @@ -2065,6 +2067,7 @@ fn perform_mint( ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_mint_action(store, minter, to, amount, denom, memo, block)?; + println!("tx_id: {}", tx_id); // load delayed write buffer let mut dwb = DWB.load(store)?; diff --git a/src/stored_balances.rs b/src/stored_balances.rs index ac4c8bf9..c4cf7e4a 100644 --- a/src/stored_balances.rs +++ b/src/stored_balances.rs @@ -402,6 +402,7 @@ pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_i /// gets the StoredEntry for a given account pub fn stored_entry(storage: &dyn Storage, account: &CanonicalAddr) -> StdResult> { let (node, _, _) = locate_btsb_node(storage, account)?; + println!("node: {:?}", node); let bucket = node.bucket(storage)?; Ok(bucket.constant_time_find_address(account)) } @@ -576,3 +577,20 @@ pub fn quick_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) - Ok(node.bucket(storage)?.quick_find_entry(&address)) } + +/// initializes the btsb +pub fn initialize_btsb(storage: &mut dyn Storage) -> StdResult<()> { + let btsb_bucket = BtsbBucket::new()?; + BTSB_BUCKETS.add_suffix(&1_u64.to_be_bytes()).save(storage, &btsb_bucket)?; + BTSB_BUCKETS_COUNT.save(storage, &1)?; + BTSB_TRIE_NODES.add_suffix(&1_u64.to_be_bytes()).save( + storage, + &BitwiseTrieNode{ + left: 0, + right: 0, + bucket: 1, + } + )?; + BTSB_TRIE_NODES_COUNT.save(storage, &1)?; + Ok(()) +} \ No newline at end of file From 6ecd16b75a89031246cdab5b95df2607c9698a53 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 13 Jul 2024 22:25:42 +1200 Subject: [PATCH 45/87] dev: all unit tests pass with stored balances --- src/contract.rs | 1 - src/dwb.rs | 2 +- src/stored_balances.rs | 31 +++++++++++++++++++------------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index d7c47a5d..521d924a 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -2067,7 +2067,6 @@ fn perform_mint( ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_mint_action(store, minter, to, amount, denom, memo, block)?; - println!("tx_id: {}", tx_id); // load delayed write buffer let mut dwb = DWB.load(store)?; diff --git a/src/dwb.rs b/src/dwb.rs index 67afe12c..8e3abff5 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -7,7 +7,7 @@ use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, S use secret_toolkit::storage::{AppendStore, Item}; use crate::{ - msg::QueryAnswer, state::{safe_add, safe_add_u64,}, stored_balances::{merge_dwb_entry, stored_balance}, transaction_history::{Tx, TRANSACTIONS} + dwb, msg::QueryAnswer, state::{safe_add, safe_add_u64,}, stored_balances::{merge_dwb_entry, stored_balance}, transaction_history::{Tx, TRANSACTIONS} }; pub const KEY_DWB: &[u8] = b"dwb"; diff --git a/src/stored_balances.rs b/src/stored_balances.rs index c4cf7e4a..44e078bb 100644 --- a/src/stored_balances.rs +++ b/src/stored_balances.rs @@ -35,12 +35,19 @@ const_assert!(BTSB_BUCKET_HISTORY_BYTES <= U32_BYTES); const BTSB_BUCKET_ENTRY_BYTES: usize = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES + BTSB_BUCKET_HISTORY_BYTES; const ZERO_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0u8; BTSB_BUCKET_ADDRESS_BYTES]; +/// canonical address bytes corresponding to the 33-byte null public key, in hexadecimal +#[cfg(test)] +const IMPOSSIBLE_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x62,0x55,0xA7,0x84,0x51,0xEE,0xB4,0xB1,0x29,0xED,0x8E,0xAC,0xFF,0xA2,0xFE,0xEF, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]; +#[cfg(not(test))] +const IMPOSSIBLE_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x62,0x55,0xA7,0x84,0x51,0xEE,0xB4,0xB1,0x29,0xED,0x8E,0xAC,0xFF,0xA2,0xFE,0xEF]; /// A `StoredEntry` consists of the address, balance, and tx bundle history length in a byte array representation. /// The methods of the struct implementation also handle pushing and getting the tx bundle history in a simplified /// append store. -#[derive(Serialize, Deserialize, Clone, Copy, Debug)] -#[cfg_attr(test, derive(Eq, PartialEq))] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +#[cfg_attr(test, derive(Eq))] pub struct StoredEntry( #[serde(with = "BigArray")] [u8; BTSB_BUCKET_ENTRY_BYTES], @@ -152,7 +159,7 @@ impl StoredEntry { let amount_spent = amount_u64(amount_spent)?; // error should never happen because already checked in `settle_sender_or_owner_account` - let balance = if let Some(new_balance) = dwb_entry.amount()?.checked_sub(amount_spent) { + let balance = if let Some(new_balance) = balance.checked_sub(amount_spent) { new_balance } else { return Err(StdError::generic_err(format!( @@ -213,7 +220,7 @@ impl StoredEntry { const BTSB_BUCKET_LEN: u16 = 128; -#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] pub struct BtsbBucket { pub capacity: u16, #[serde(with = "BigArray")] @@ -232,7 +239,7 @@ impl BtsbBucket { Ok(Self { capacity: BTSB_BUCKET_LEN, entries: [ - StoredEntry::new(CanonicalAddr::from(&ZERO_ADDR))?; BTSB_BUCKET_LEN as usize + StoredEntry::new(CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; BTSB_BUCKET_LEN as usize ] }) } @@ -253,7 +260,7 @@ impl BtsbBucket { true } - pub fn constant_time_find_address(&self, address: &CanonicalAddr) -> Option { + pub fn constant_time_find_address(&self, address: &CanonicalAddr) -> Option<(usize, StoredEntry)> { let address = address.as_slice(); let mut matched_index_p1: BucketEntryPosition = 0; @@ -264,7 +271,7 @@ impl BtsbBucket { match matched_index_p1 { 0 => None, - idx => Some(self.entries[idx - 1]), + idx => Some((idx - 1, self.entries[idx - 1])), } } @@ -376,7 +383,7 @@ pub fn locate_btsb_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdRe pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_idx: u32) -> StdResult> { let (node, _, _) = locate_btsb_node(storage, account)?; let bucket = node.bucket(storage)?; - if let Some(entry) = bucket.constant_time_find_address(account) { + if let Some((_, entry)) = bucket.constant_time_find_address(account) { let mut left = 0u32; let mut right = entry.history_len()?; @@ -402,9 +409,8 @@ pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_i /// gets the StoredEntry for a given account pub fn stored_entry(storage: &dyn Storage, account: &CanonicalAddr) -> StdResult> { let (node, _, _) = locate_btsb_node(storage, account)?; - println!("node: {:?}", node); let bucket = node.bucket(storage)?; - Ok(bucket.constant_time_find_address(account)) + Ok(bucket.constant_time_find_address(account).map(|b| b.1)) } /// returns the current stored balance for an entry @@ -440,10 +446,11 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE let mut bucket = node.bucket(storage)?; // search for an existing entry - if let Some(mut found_entry) = bucket.constant_time_find_address(&dwb_entry.recipient()?) { + if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(&dwb_entry.recipient()?) { // found existing entry // merge amount and history from dwb entry found_entry.merge_dwb_entry(storage, &dwb_entry, amount_spent)?; + bucket.entries[idx] = found_entry; // save updated bucket to storage node.set_and_save_bucket(storage, bucket)?; @@ -568,7 +575,7 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE pub fn constant_time_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { let (node, _, _) = locate_btsb_node(storage, &address)?; - Ok(node.bucket(storage)?.constant_time_find_address(&address)) + Ok(node.bucket(storage)?.constant_time_find_address(&address).map(|b| b.1)) } // for fetching account's stored balance and/or history during queries From a6307324548ebd5f719f697c6a7eae84dcf8c38b Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sun, 14 Jul 2024 01:43:25 -0700 Subject: [PATCH 46/87] dev: btsb -> btbe --- src/{stored_balances.rs => btbe.rs} | 269 ++++++++++++---------------- src/contract.rs | 9 +- src/dwb.rs | 9 +- src/lib.rs | 2 +- src/state.rs | 2 +- 5 files changed, 128 insertions(+), 163 deletions(-) rename src/{stored_balances.rs => btbe.rs} (66%) diff --git a/src/stored_balances.rs b/src/btbe.rs similarity index 66% rename from src/stored_balances.rs rename to src/btbe.rs index 44e078bb..7de85474 100644 --- a/src/stored_balances.rs +++ b/src/btbe.rs @@ -1,3 +1,5 @@ +//! BTBE stands for bitwise-trie of bucketed entries + use constant_time_eq::constant_time_eq; use primitive_types::U256; use secret_toolkit::{notification::hkdf_sha_256, serialization::{Bincode2, Serde}, storage::Item}; @@ -5,17 +7,16 @@ use serde::{Serialize, Deserialize,}; use serde_big_array::BigArray; use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; -use crate::{ - dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}, state::{safe_add_u64, INTERNAL_SECRET} -}; +use crate::dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}; +use crate::state::{safe_add_u64, INTERNAL_SECRET}; -// btsb = bitwise-trie of stored balances +pub const KEY_BTBE_ENTRY_HISTORY: &[u8] = b"btbe-entry-hist"; +pub const KEY_BTBE_BUCKETS_COUNT: &[u8] = b"btbe-buckets-cnt"; +pub const KEY_BTBE_BUCKETS: &[u8] = b"btbe-buckets"; +pub const KEY_BTBE_TRIE_NODES: &[u8] = b"btbe-trie-nodes"; +pub const KEY_BTBE_TRIE_NODES_COUNT: &[u8] = b"btbe-trie-nodes-cnt"; -pub const KEY_BTSB_ENTRY_HISTORY: &[u8] = b"btsb-entry-hist"; -pub const KEY_BTSB_BUCKETS_COUNT: &[u8] = b"btsb-buckets-cnt"; -pub const KEY_BTSB_BUCKETS: &[u8] = b"btsb-buckets"; -pub const KEY_BTSB_TRIE_NODES: &[u8] = b"btsb-trie-nodes"; -pub const KEY_BTSB_TRIE_NODES_COUNT: &[u8] = b"btsb-trie-nodes-cnt"; +const BUCKETING_SALT_BYTES: &[u8; 14] = b"bucketing-salt"; const U16_BYTES: usize = 2; const U32_BYTES: usize = 4; @@ -23,25 +24,24 @@ const U64_BYTES: usize = 8; const U128_BYTES: usize = 16; #[cfg(test)] -const BTSB_BUCKET_ADDRESS_BYTES: usize = 54; +const BTBE_BUCKET_ADDRESS_BYTES: usize = 54; #[cfg(not(test))] -const BTSB_BUCKET_ADDRESS_BYTES: usize = 20; -const BTSB_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u128) -const BTSB_BUCKET_HISTORY_BYTES: usize = 4; // Max 4 (u32) +const BTBE_BUCKET_ADDRESS_BYTES: usize = 20; +const BTBE_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u128) +const BTBE_BUCKET_HISTORY_BYTES: usize = 4; // Max 4 (u32) -const_assert!(BTSB_BUCKET_BALANCE_BYTES <= U128_BYTES); -const_assert!(BTSB_BUCKET_HISTORY_BYTES <= U32_BYTES); +const_assert!(BTBE_BUCKET_BALANCE_BYTES <= U128_BYTES); +const_assert!(BTBE_BUCKET_HISTORY_BYTES <= U32_BYTES); -const BTSB_BUCKET_ENTRY_BYTES: usize = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES + BTSB_BUCKET_HISTORY_BYTES; +const BTBE_BUCKET_ENTRY_BYTES: usize = BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES; -const ZERO_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0u8; BTSB_BUCKET_ADDRESS_BYTES]; /// canonical address bytes corresponding to the 33-byte null public key, in hexadecimal #[cfg(test)] -const IMPOSSIBLE_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x62,0x55,0xA7,0x84,0x51,0xEE,0xB4,0xB1,0x29,0xED,0x8E,0xAC,0xFF,0xA2,0xFE,0xEF, +const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x62,0x55,0xA7,0x84,0x51,0xEE,0xB4,0xB1,0x29,0xED,0x8E,0xAC,0xFF,0xA2,0xFE,0xEF, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]; #[cfg(not(test))] -const IMPOSSIBLE_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x62,0x55,0xA7,0x84,0x51,0xEE,0xB4,0xB1,0x29,0xED,0x8E,0xAC,0xFF,0xA2,0xFE,0xEF]; +const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x62,0x55,0xA7,0x84,0x51,0xEE,0xB4,0xB1,0x29,0xED,0x8E,0xAC,0xFF,0xA2,0xFE,0xEF]; /// A `StoredEntry` consists of the address, balance, and tx bundle history length in a byte array representation. /// The methods of the struct implementation also handle pushing and getting the tx bundle history in a simplified @@ -50,19 +50,19 @@ const IMPOSSIBLE_ADDR: [u8; BTSB_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x #[cfg_attr(test, derive(Eq))] pub struct StoredEntry( #[serde(with = "BigArray")] - [u8; BTSB_BUCKET_ENTRY_BYTES], + [u8; BTBE_BUCKET_ENTRY_BYTES], ); impl StoredEntry { fn new(address: CanonicalAddr) -> StdResult { let address = address.as_slice(); - if address.len() != BTSB_BUCKET_ADDRESS_BYTES { + if address.len() != BTBE_BUCKET_ADDRESS_BYTES { return Err(StdError::generic_err("bucket: invalid address length")); } - let mut result = [0u8; BTSB_BUCKET_ENTRY_BYTES]; - result[..BTSB_BUCKET_ADDRESS_BYTES].copy_from_slice(address); + let mut result = [0u8; BTBE_BUCKET_ENTRY_BYTES]; + result[..BTBE_BUCKET_ADDRESS_BYTES].copy_from_slice(address); Ok(Self { 0: result, }) @@ -96,7 +96,7 @@ impl StoredEntry { } fn address_slice(&self) -> &[u8] { - &self.0[..BTSB_BUCKET_ADDRESS_BYTES] + &self.0[..BTBE_BUCKET_ADDRESS_BYTES] } fn address(&self) -> StdResult { @@ -106,8 +106,8 @@ impl StoredEntry { } pub fn balance(&self) -> StdResult { - let start = BTSB_BUCKET_ADDRESS_BYTES; - let end = start + BTSB_BUCKET_BALANCE_BYTES; + let start = BTBE_BUCKET_ADDRESS_BYTES; + let end = start + BTBE_BUCKET_BALANCE_BYTES; let amount_slice = &self.0[start..end]; let result = amount_slice .try_into() @@ -116,26 +116,26 @@ impl StoredEntry { } fn set_balance(&mut self, val: u64) -> StdResult<()> { - let start = BTSB_BUCKET_ADDRESS_BYTES; - let end = start + BTSB_BUCKET_BALANCE_BYTES; + let start = BTBE_BUCKET_ADDRESS_BYTES; + let end = start + BTBE_BUCKET_BALANCE_BYTES; self.0[start..end].copy_from_slice(&val.to_be_bytes()); Ok(()) } pub fn history_len(&self) -> StdResult { - let start = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES; - let end = start + BTSB_BUCKET_HISTORY_BYTES; + let start = BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES; + let end = start + BTBE_BUCKET_HISTORY_BYTES; let history_len_slice = &self.0[start..end]; let mut result = [0u8; U32_BYTES]; - result[U32_BYTES - BTSB_BUCKET_HISTORY_BYTES..].copy_from_slice(history_len_slice); + result[U32_BYTES - BTBE_BUCKET_HISTORY_BYTES..].copy_from_slice(history_len_slice); Ok(u32::from_be_bytes(result)) } fn set_history_len(&mut self, val: u32) -> StdResult<()> { - let start = BTSB_BUCKET_ADDRESS_BYTES + BTSB_BUCKET_BALANCE_BYTES; - let end = start + BTSB_BUCKET_HISTORY_BYTES; - let val_bytes = &val.to_be_bytes()[U32_BYTES - BTSB_BUCKET_HISTORY_BYTES..]; - if val_bytes.len() != BTSB_BUCKET_HISTORY_BYTES { + let start = BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES; + let end = start + BTBE_BUCKET_HISTORY_BYTES; + let val_bytes = &val.to_be_bytes()[U32_BYTES - BTBE_BUCKET_HISTORY_BYTES..]; + if val_bytes.len() != BTBE_BUCKET_HISTORY_BYTES { return Err(StdError::generic_err("Set bucket history len error")); } self.0[start..end].copy_from_slice(val_bytes); @@ -194,7 +194,7 @@ impl StoredEntry { /// tries to get the element at pos fn get_tx_bundle_at_unchecked(&self, storage: &dyn Storage, pos: u32) -> StdResult { - let bundle_data = storage.get(&[KEY_BTSB_ENTRY_HISTORY, self.address_slice(), pos.to_be_bytes().as_slice()].concat()); + let bundle_data = storage.get(&[KEY_BTBE_ENTRY_HISTORY, self.address_slice(), pos.to_be_bytes().as_slice()].concat()); let bundle_data = bundle_data.ok_or_else(|| { return StdError::generic_err("tx bundle not found"); } )?; Bincode2::deserialize( &bundle_data @@ -204,7 +204,7 @@ impl StoredEntry { /// Sets data at a given index fn set_tx_bundle_at_unchecked(&self, storage: &mut dyn Storage, pos: u32, bundle: &TxBundle) -> StdResult<()> { let bundle_data = Bincode2::serialize(bundle)?; - storage.set(&[KEY_BTSB_ENTRY_HISTORY, self.address_slice(), pos.to_be_bytes().as_slice()].concat(), &bundle_data); + storage.set(&[KEY_BTBE_ENTRY_HISTORY, self.address_slice(), pos.to_be_bytes().as_slice()].concat(), &bundle_data); Ok(()) } @@ -215,42 +215,42 @@ impl StoredEntry { self.set_history_len(len.saturating_add(1))?; Ok(()) } - } -const BTSB_BUCKET_LEN: u16 = 128; +const BTBE_BUCKET_LEN: u16 = 128; #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] -pub struct BtsbBucket { +pub struct BtbeBucket { pub capacity: u16, #[serde(with = "BigArray")] - pub entries: [StoredEntry; BTSB_BUCKET_LEN as usize], + pub entries: [StoredEntry; BTBE_BUCKET_LEN as usize], } -//static BTSB_ENTRY_HISTORY: Item = Item::new(KEY_BTSB_ENTRY_HISTORY); -static BTSB_BUCKETS_COUNT: Item = Item::new(KEY_BTSB_BUCKETS_COUNT); -static BTSB_BUCKETS: Item = Item::new(KEY_BTSB_BUCKETS); +//static BTBE_ENTRY_HISTORY: Item = Item::new(KEY_BTBE_ENTRY_HISTORY); +static BTBE_BUCKETS_COUNT: Item = Item::new(KEY_BTBE_BUCKETS_COUNT); +static BTBE_BUCKETS: Item = Item::new(KEY_BTBE_BUCKETS); // create type alias to refer to position of a bucket entry, which is its index in the array plus 1 type BucketEntryPosition = usize; -impl BtsbBucket { +impl BtbeBucket { pub fn new() -> StdResult { Ok(Self { - capacity: BTSB_BUCKET_LEN, + capacity: BTBE_BUCKET_LEN, entries: [ - StoredEntry::new(CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; BTSB_BUCKET_LEN as usize + StoredEntry::new(CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; BTBE_BUCKET_LEN as usize ] }) } - pub fn add_entry(&mut self, storage: &mut dyn Storage, entry: &StoredEntry) -> bool { + /// Attempts to add an entry to the bucket; returns false if bucket is at capacity, or true on success + pub fn add_entry(&mut self, entry: &StoredEntry) -> bool { + // buffer is at capacity if self.capacity == 0 { - // buffer is at capacity return false; } - // has capacity for a new entry - // save entry to bucket + + // has capacity for a new entry; save entry to bucket self.entries[self.entries.len() - self.capacity as usize] = entry.clone(); // update capacity @@ -260,9 +260,11 @@ impl BtsbBucket { true } + /// Searches the bucket for an entry containing the given address pub fn constant_time_find_address(&self, address: &CanonicalAddr) -> Option<(usize, StoredEntry)> { let address = address.as_slice(); + // contant-time only applies to this part, so that the index of the entry cannot be distinguished let mut matched_index_p1: BucketEntryPosition = 0; for (idx, entry) in self.entries.iter().enumerate() { let equals = constant_time_eq(address, entry.address_slice()) as usize; @@ -274,20 +276,6 @@ impl BtsbBucket { idx => Some((idx - 1, self.entries[idx - 1])), } } - - pub fn quick_find_entry(&self, address: &CanonicalAddr) -> Option { - let address = address.as_slice(); - - let mut matched_index_p1: BucketEntryPosition = 0; - /* TODO: - binary search on bucket - */ - - match matched_index_p1 { - 0 => None, - idx => Some(self.entries[idx - 1]), - } - } } #[derive(Serialize, Deserialize, Clone, Copy, Debug)] @@ -297,22 +285,22 @@ pub struct BitwiseTrieNode { pub bucket: u64, } -pub static BTSB_TRIE_NODES: Item = Item::new(KEY_BTSB_TRIE_NODES); -pub static BTSB_TRIE_NODES_COUNT: Item = Item::new(KEY_BTSB_TRIE_NODES_COUNT); +pub static BTBE_TRIE_NODES: Item = Item::new(KEY_BTBE_TRIE_NODES); +pub static BTBE_TRIE_NODES_COUNT: Item = Item::new(KEY_BTBE_TRIE_NODES_COUNT); impl BitwiseTrieNode { // creates a new leaf node - pub fn new_leaf(storage: &mut dyn Storage, bucket: BtsbBucket) -> StdResult { - let buckets_count = BTSB_BUCKETS_COUNT.load(storage).unwrap_or_default() + 1; + pub fn new_leaf(storage: &mut dyn Storage, bucket: BtbeBucket) -> StdResult { + let buckets_count = BTBE_BUCKETS_COUNT.load(storage).unwrap_or_default() + 1; // ID for new bucket let bucket_id = buckets_count; // save updated count - BTSB_BUCKETS_COUNT.save(storage, &buckets_count)?; + BTBE_BUCKETS_COUNT.save(storage, &buckets_count)?; // save bucket to storage - BTSB_BUCKETS.add_suffix(&bucket_id.to_be_bytes()).save(storage, &bucket)?; + BTBE_BUCKETS.add_suffix(&bucket_id.to_be_bytes()).save(storage, &bucket)?; // create new node Ok(Self { @@ -323,40 +311,49 @@ impl BitwiseTrieNode { } // loads the node's bucket from storage - pub fn bucket(self, storage: &dyn Storage) -> StdResult { + pub fn bucket(self, storage: &dyn Storage) -> StdResult { if self.bucket == 0 { - return Err(StdError::generic_err("btsb: attempted to load bucket of branch node")); + return Err(StdError::generic_err("btbe: attempted to load bucket of branch node")); } // load bucket from storage - BTSB_BUCKETS.add_suffix(&self.bucket.to_be_bytes()).load(storage) + BTBE_BUCKETS.add_suffix(&self.bucket.to_be_bytes()).load(storage) } // stores the bucket associated with this node - fn set_and_save_bucket(self, storage: &mut dyn Storage, bucket: BtsbBucket) -> StdResult<()> { + fn set_and_save_bucket(self, storage: &mut dyn Storage, bucket: BtbeBucket) -> StdResult<()> { if self.bucket == 0 { - return Err(StdError::generic_err("btsb: attempted to store a bucket to a branch node")); + return Err(StdError::generic_err("btbe: attempted to store a bucket to a branch node")); } - BTSB_BUCKETS.add_suffix(&self.bucket.to_be_bytes()).save(storage, &bucket) + BTBE_BUCKETS.add_suffix(&self.bucket.to_be_bytes()).save(storage, &bucket) } } +/// Determines whether a given entry belongs in the left node (true) or right node (false) +fn entry_belongs_in_left_node(secret: &[u8], entry: StoredEntry, bit_pos: u8) -> StdResult { + // create key bytes + let key_bytes = hkdf_sha_256(&Some(BUCKETING_SALT_BYTES.to_vec()), secret, entry.address_slice(), 256)?; -// locates a btsb node given an address; returns tuple of (node, bit position) -pub fn locate_btsb_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult<(BitwiseTrieNode, u64, u8)> { - //let hash: [u8; 32] = [0u8; 32]; + // convert to u258 + let key_u256 = U256::from_big_endian(&key_bytes); + // extract the bit value at the target bit position + return Ok(U256::from(0) == (key_u256 >> (255 - bit_pos)) & U256::from(1)); +} + +/// Locates a btbe node given an address; returns tuple of (node, bit position) +pub fn locate_btbe_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult<(BitwiseTrieNode, u64, u8)> { + // load internal contract secret let secret = INTERNAL_SECRET.load(storage)?; let secret = secret.as_slice(); - let hash = hkdf_sha_256(&None, secret, address.as_slice(), 256)?; - /* - let hash := hkdf(ikm=contractInternalSecret, info=addrress, length=256bits) - */ + // create key bytes + let hash = hkdf_sha_256(&Some(BUCKETING_SALT_BYTES.to_vec()), secret, address.as_slice(), 256)?; + // start at root of trie let mut node_id: u64 = 1; - let mut node = BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).load(storage)?; + let mut node = BTBE_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).load(storage)?; let mut bit_pos: u8 = 0; // while the node has children @@ -371,7 +368,7 @@ pub fn locate_btsb_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdRe node_id = if bit_value == 0 { node.left } else { node.right }; // load child node - node = BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).load(storage)?; + node = BTBE_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).load(storage)?; } Ok((node, node_id, bit_pos)) @@ -381,7 +378,7 @@ pub fn locate_btsb_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdRe /// For a paginated search `start_idx` = `page` * `page_size`. /// Returns the bundle index, the bundle, and the index in the bundle list to start at pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_idx: u32) -> StdResult> { - let (node, _, _) = locate_btsb_node(storage, account)?; + let (node, _, _) = locate_btbe_node(storage, account)?; let bucket = node.bucket(storage)?; if let Some((_, entry)) = bucket.constant_time_find_address(account) { let mut left = 0u32; @@ -408,7 +405,7 @@ pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_i /// gets the StoredEntry for a given account pub fn stored_entry(storage: &dyn Storage, account: &CanonicalAddr) -> StdResult> { - let (node, _, _) = locate_btsb_node(storage, account)?; + let (node, _, _) = locate_btbe_node(storage, account)?; let bucket = node.bucket(storage)?; Ok(bucket.constant_time_find_address(account).map(|b| b.1)) } @@ -440,7 +437,7 @@ pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> St // `spent_amount` is any required subtraction due to being sender of tx pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferEntry, amount_spent: Option) -> StdResult<()> { // locate the node that the given entry belongs in - let (mut node, node_id, mut bit_pos) = locate_btsb_node(storage, &dwb_entry.recipient()?)?; + let (mut node, node_id, mut bit_pos) = locate_btbe_node(storage, &dwb_entry.recipient()?)?; // load that node's current bucket let mut bucket = node.bucket(storage)?; @@ -457,14 +454,15 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE } else { // need to insert new entry // create new stored balance entry - let btsb_entry = StoredEntry::from(storage, &dwb_entry, amount_spent)?; + let btbe_entry = StoredEntry::from(storage, &dwb_entry, amount_spent)?; + // load contract's internal secret let secret = INTERNAL_SECRET.load(storage)?; let secret = secret.as_slice(); loop { // looping as many times as needed until the bucket has capacity for a new entry // try to add to the current bucket - if bucket.add_entry(storage, &btsb_entry) { + if bucket.add_entry(&btbe_entry) { // bucket has capacity and it added the new entry // save bucket to storage node.set_and_save_bucket(storage, bucket)?; @@ -473,46 +471,36 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE } else { // bucket is full; split on next bit position // create new left and right buckets - let mut left_bucket = BtsbBucket::new()?; - let mut right_bucket = BtsbBucket::new()?; + let mut left_bucket = BtbeBucket::new()?; + let mut right_bucket = BtbeBucket::new()?; // each entry for entry in bucket.entries { - let key = hkdf_sha_256(&None, secret, entry.address_slice(), 256)?; - let key = U256::from_big_endian(&key); - let bit_value = (key >> (255 - bit_pos)) & U256::from(1); - if bit_value == U256::from(0) { - left_bucket.add_entry(storage, &entry); + // route entry + if entry_belongs_in_left_node(secret, entry, bit_pos)? { + left_bucket.add_entry(&entry); } else { - right_bucket.add_entry(storage, &entry); + right_bucket.add_entry(&entry); } - /* - let key := hkdf(ikm=contractInternalSecret, info=canonical(addr), length=256bits) - let bit_value := (key >> (255 - bit_pos)) & 1 - if bit_value == 0: - left_bucket.add_entry(entry) - else: - right_bucket.add_entry(entry) - */ } // save left node's bucket to storage, recycling this node's bucket ID let left_bucket_id = node.bucket; - BTSB_BUCKETS.add_suffix(&left_bucket_id.to_be_bytes()).save(storage, &left_bucket)?; + BTBE_BUCKETS.add_suffix(&left_bucket_id.to_be_bytes()).save(storage, &left_bucket)?; // global count of buckets - let mut buckets_count = BTSB_BUCKETS_COUNT.load(storage).unwrap_or_default(); + let mut buckets_count = BTBE_BUCKETS_COUNT.load(storage).unwrap_or_default(); // bucket ID for right node buckets_count += 1; let right_bucket_id = buckets_count; - BTSB_BUCKETS.add_suffix(&right_bucket_id.to_be_bytes()).save(storage, &right_bucket)?; + BTBE_BUCKETS.add_suffix(&right_bucket_id.to_be_bytes()).save(storage, &right_bucket)?; // save updated count - BTSB_BUCKETS_COUNT.save(storage, &buckets_count)?; + BTBE_BUCKETS_COUNT.save(storage, &buckets_count)?; // global count of trie nodes - let mut nodes_count = BTSB_TRIE_NODES_COUNT.load(storage).unwrap_or_default(); + let mut nodes_count = BTBE_TRIE_NODES_COUNT.load(storage).unwrap_or_default(); // ID for left node nodes_count += 1; @@ -523,7 +511,7 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE let right_id = nodes_count; // save updated count - BTSB_TRIE_NODES_COUNT.save(storage, &nodes_count)?; + BTBE_TRIE_NODES_COUNT.save(storage, &nodes_count)?; // create left and right nodes let left = BitwiseTrieNode { @@ -538,8 +526,8 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE }; // save left and right node to storage - BTSB_TRIE_NODES.add_suffix(&left_id.to_be_bytes()).save(storage, &left)?; - BTSB_TRIE_NODES.add_suffix(&right_id.to_be_bytes()).save(storage, &right)?; + BTBE_TRIE_NODES.add_suffix(&left_id.to_be_bytes()).save(storage, &left)?; + BTBE_TRIE_NODES.add_suffix(&right_id.to_be_bytes()).save(storage, &right)?; // convert this into a branch node node.left = left_id; @@ -547,20 +535,17 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE node.bucket = 0; // save node - BTSB_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node)?; + BTBE_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node)?; - let key = hkdf_sha_256(&None, secret, btsb_entry.address_slice(), 256)?; - let key = U256::from_big_endian(&key); - let bit_value = (key >> (255 - bit_pos)) & U256::from(1); - - // determine which child node the dwb entry belongs in, then retry insertion - if bit_value == U256::from(0) { + // route entry + if entry_belongs_in_left_node(secret, btbe_entry, bit_pos)? { node = left; bucket = left_bucket; } else { node = right; bucket = right_bucket; } + // increment bit position for next iteration of the loop bit_pos += 1; } @@ -570,34 +555,16 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE Ok(()) } +/// initializes the btbe +pub fn initialize_btbe(storage: &mut dyn Storage) -> StdResult<()> { + let bucket = BtbeBucket::new()?; + let node = BitwiseTrieNode::new_leaf(storage, bucket)?; + + // save count + BTBE_TRIE_NODES_COUNT.save(storage, &1)?; + + // save root node to storage + BTBE_TRIE_NODES.add_suffix(&1_u64.to_be_bytes()).save(storage, &node)?; -// for fetching an account's stored balance during transfer executions -pub fn constant_time_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { - let (node, _, _) = locate_btsb_node(storage, &address)?; - - Ok(node.bucket(storage)?.constant_time_find_address(&address).map(|b| b.1)) -} - -// for fetching account's stored balance and/or history during queries -pub fn quick_get_btsb_entry(storage: &mut dyn Storage, address: CanonicalAddr) -> StdResult> { - let (node, _, _) = locate_btsb_node(storage, &address)?; - - Ok(node.bucket(storage)?.quick_find_entry(&address)) -} - -/// initializes the btsb -pub fn initialize_btsb(storage: &mut dyn Storage) -> StdResult<()> { - let btsb_bucket = BtsbBucket::new()?; - BTSB_BUCKETS.add_suffix(&1_u64.to_be_bytes()).save(storage, &btsb_bucket)?; - BTSB_BUCKETS_COUNT.save(storage, &1)?; - BTSB_TRIE_NODES.add_suffix(&1_u64.to_be_bytes()).save( - storage, - &BitwiseTrieNode{ - left: 0, - right: 0, - bucket: 1, - } - )?; - BTSB_TRIE_NODES_COUNT.save(storage, &1)?; Ok(()) -} \ No newline at end of file +} diff --git a/src/contract.rs b/src/contract.rs index 521d924a..a61fce13 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -11,7 +11,6 @@ use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; use crate::dwb::{log_dwb, DelayedWriteBuffer, DWB, TX_NODES}; -//use crate::bucket; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -20,7 +19,7 @@ use crate::receiver::Snip20ReceiveMsg; use crate::state::{ safe_add, AllowancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, PRNG, TOTAL_SUPPLY }; -use crate::stored_balances::{find_start_bundle, initialize_btsb, stored_balance, stored_entry, stored_tx_count}; +use crate::btbe::{find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count}; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx @@ -64,8 +63,8 @@ pub fn instantiate( let prng_seed_hashed = sha_256(&msg.prng_seed.0); PrngStore::save(deps.storage, prng_seed_hashed)?; - // initialize the bitwise-trie of stored entries - initialize_btsb(deps.storage)?; + // initialize the bitwise-trie of bucketed entries + initialize_btbe(deps.storage)?; // initialize the delay write buffer DWB.save(deps.storage, &DelayedWriteBuffer::new()?)?; @@ -182,8 +181,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ContractStatusLevel::NormalRun => {} // If it's a normal run just continue } - let secret = INTERNAL_SECRET.load(deps.storage)?; - let secret = secret.as_slice(); let response = match msg.clone() { // Native ExecuteMsg::Deposit { .. } => { diff --git a/src/dwb.rs b/src/dwb.rs index 8e3abff5..25f09462 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -4,11 +4,12 @@ use secret_toolkit_crypto::ContractPrng; use serde::{Serialize, Deserialize,}; use serde_big_array::BigArray; use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, Storage}; -use secret_toolkit::storage::{AppendStore, Item}; +use secret_toolkit::storage::Item; -use crate::{ - dwb, msg::QueryAnswer, state::{safe_add, safe_add_u64,}, stored_balances::{merge_dwb_entry, stored_balance}, transaction_history::{Tx, TRANSACTIONS} -}; +use crate::msg::QueryAnswer; +use crate::state::{safe_add, safe_add_u64,}; +use crate::btbe::{merge_dwb_entry, stored_balance}; +use crate::transaction_history::{Tx, TRANSACTIONS}; pub const KEY_DWB: &[u8] = b"dwb"; pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt"; diff --git a/src/lib.rs b/src/lib.rs index 701260c4..ffa335dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,5 +8,5 @@ pub mod receiver; pub mod state; mod transaction_history; mod dwb; -mod stored_balances; +mod btbe; mod strings; diff --git a/src/state.rs b/src/state.rs index 30e927e4..0529eccc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, CanonicalAddr, StdError, StdResult, Storage}; +use cosmwasm_std::{Addr, StdError, StdResult, Storage}; use secret_toolkit::serialization::Json; use secret_toolkit::storage::{Item, Keymap, Keyset}; use secret_toolkit_crypto::SHA256_HASH_SIZE; From e9986fcbb19cbfeb893d70e7fa1ccdb6e3f7413c Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Wed, 31 Jul 2024 06:35:44 +0000 Subject: [PATCH 47/87] dev: integration tests --- .gitignore | 7 + .vscode/launch.json | 36 +++++ .vscode/settings.json | 33 +++++ Cargo.toml | 3 +- Makefile | 2 +- build.rs | 21 +++ src/config.rs | 1 + src/contract.rs | 110 ++++++++-------- src/gas_tracker.rs | 84 ++++++++++++ src/lib.rs | 1 + tests/dwb/.eslintrc.cjs | 14 ++ tests/dwb/bun.lockb | Bin 0 -> 102137 bytes tests/dwb/package.json | 23 ++++ tests/dwb/src/constants.ts | 26 ++++ tests/dwb/src/contract.ts | 105 +++++++++++++++ tests/dwb/src/dwb-entry.ts | 76 +++++++++++ tests/dwb/src/dwb.ts | 248 +++++++++++++++++++++++++++++++++++ tests/dwb/src/gas-checker.ts | 50 +++++++ tests/dwb/src/helper.ts | 89 +++++++++++++ tests/dwb/src/main.ts | 117 +++++++++++++++++ tests/dwb/src/snip.ts | 181 +++++++++++++++++++++++++ tests/dwb/tsconfig.json | 9 ++ 22 files changed, 1180 insertions(+), 56 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 build.rs create mode 100644 src/config.rs create mode 100644 src/gas_tracker.rs create mode 100644 tests/dwb/.eslintrc.cjs create mode 100755 tests/dwb/bun.lockb create mode 100644 tests/dwb/package.json create mode 100644 tests/dwb/src/constants.ts create mode 100644 tests/dwb/src/contract.ts create mode 100644 tests/dwb/src/dwb-entry.ts create mode 100644 tests/dwb/src/dwb.ts create mode 100644 tests/dwb/src/gas-checker.ts create mode 100644 tests/dwb/src/helper.ts create mode 100644 tests/dwb/src/main.ts create mode 100644 tests/dwb/src/snip.ts create mode 100644 tests/dwb/tsconfig.json diff --git a/.gitignore b/.gitignore index ea00b0b0..0ff28d37 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,10 @@ contract.wasm.gz # IDEs *.iml .idea + +# Packages +node_modules/ + +# Private +.env +scrap/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..0827ff3b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,36 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "bun", + "internalConsoleOptions": "neverOpen", + "request": "launch", + "name": "Debug File", + "program": "${file}", + "cwd": "${workspaceFolder}", + "stopOnEntry": false, + "watchMode": false + }, + { + "type": "bun", + "internalConsoleOptions": "neverOpen", + "request": "launch", + "name": "Run File", + "program": "${file}", + "cwd": "${workspaceFolder}", + "noDebug": true, + "watchMode": false + }, + { + "type": "bun", + "internalConsoleOptions": "neverOpen", + "request": "attach", + "name": "Attach Bun", + "url": "ws://localhost:6499/", + "stopOnEntry": false + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..83bf78f8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,33 @@ +{ + "eslint.enable": true, + "editor.fontSize": 11, + "scm.inputFontSize": 11, + "debug.console.fontSize": 10, + "markdown.preview.fontSize": 11, + "terminal.integrated.fontSize": 10, + "files.exclude": { + "dist": true, + "submodules": true, + "**/.git": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "**/node_modules": true + }, + "editor.insertSpaces": false, + "editor.tabSize": 3, + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.preferences.quoteStyle": "single", + "javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false, + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, + "eslint.workingDirectories": [ + "./tests/dwb/src", + ], + "eslint.validate": [ + "javascript", + "typescript", + ], + "editor.codeActionsOnSave": { + "source.fixAll": "explicit" + } +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 11281745..8d8417e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "snip20-reference-impl" version = "1.0.0" -authors = ["@reuvenpo","@toml01","@assafmo","@liorbond","Itzik ","@darwinzer0"] +authors = ["@reuvenpo","@toml01","@assafmo","@liorbond","Itzik ","@darwinzer0","@supdoggie"] edition = "2021" exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. @@ -29,6 +29,7 @@ overflow-checks = true # for more explicit tests, cargo test --features=backtraces #default = ["debug-print"] backtraces = ["cosmwasm-std/backtraces"] +gas_tracking = [] # debug-print = ["cosmwasm-std/debug-print"] [dependencies] diff --git a/Makefile b/Makefile index 8db76b36..1770609c 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ compile-optimized: _compile-optimized contract.wasm.gz _compile-optimized: RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown @# The following line is not necessary, may work only on linux (extra size optimization) - wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm + wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm .PHONY: compile-optimized-reproducible compile-optimized-reproducible: diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..b001d38a --- /dev/null +++ b/build.rs @@ -0,0 +1,21 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +fn main() { + // config parameters + // let dwb_capacity = env!("DWB_CAPACITY").parse().unwrap_or_else(|_| "4".to_string()); + let dwb_capacity = env::var("DWB_CAPACITY").unwrap_or_else(|_| "4".to_string()); + + // path to destination config.rs file + let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); + let dest_path = Path::new(&out_dir).join("config.rs"); + + // write constants + let mut file = File::create(&dest_path).expect("Failed to write to config.rs"); + write!(file, "pub const DWB_CAPACITY: u16 = {};", dwb_capacity).unwrap(); + + // monitor + println!("cargo:rerun-if-env-changed=DWB_CAPACITY"); +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..5a078b8f --- /dev/null +++ b/src/config.rs @@ -0,0 +1 @@ +pub const DWB_CAPACITY: u16 = 4; \ No newline at end of file diff --git a/src/contract.rs b/src/contract.rs index a61fce13..dc80eaa3 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -11,6 +11,7 @@ use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; use crate::dwb::{log_dwb, DelayedWriteBuffer, DWB, TX_NODES}; +use crate::gas_tracker::{GasTracker, LoggingExt}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -1177,7 +1178,7 @@ fn try_redeem( } #[allow(clippy::too_many_arguments)] -fn try_transfer_impl( +fn try_transfer_impl<'a>( deps: &mut DepsMut, rng: &mut ContractPrng, sender: &Addr, @@ -1186,15 +1187,11 @@ fn try_transfer_impl( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, - logs: &mut Vec<(String, String)>, + tracker: &mut GasTracker<'a>, ) -> StdResult<()> { let raw_sender = deps.api.addr_canonicalize(sender.as_str())?; let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; - // TESTING - let gas = deps.api.check_gas()?; - logs.push(("gas1".to_string(), format!("{gas}"))); - perform_transfer( deps.storage, rng, @@ -1205,9 +1202,7 @@ fn try_transfer_impl( denom, memo, block, - // TESTING - deps.api, - logs, + tracker )?; Ok(()) @@ -1227,8 +1222,7 @@ fn try_transfer( let symbol = CONFIG.load(deps.storage)?.symbol; - // TESTING - let mut logs = vec![]; + let mut tracker: GasTracker = GasTracker::new(deps.api); try_transfer_impl( &mut deps, @@ -1239,21 +1233,17 @@ fn try_transfer( symbol, memo, &env.block, - &mut logs, + &mut tracker )?; - // TESTING let mut resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); - for log in logs { - resp = resp.add_attribute_plaintext(log.0, log.1); + + if cfg!(feature="gas_tracking") { + resp = tracker.add_to_response(resp); } - Ok( - //Response::new() - // .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?) - resp - ) + Ok(resp) } fn try_batch_transfer( @@ -1265,6 +1255,8 @@ fn try_batch_transfer( ) -> StdResult { let symbol = CONFIG.load(deps.storage)?.symbol; + let mut tracker: GasTracker = GasTracker::new(deps.api); + for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_transfer_impl( @@ -1276,16 +1268,18 @@ fn try_batch_transfer( symbol.clone(), action.memo, &env.block, - // TESTING - &mut vec![], + &mut tracker )?; } - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer { - status: Success, - })?), - ) + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + + if cfg!(feature="gas_tracking") { + resp = tracker.add_to_response(resp); + } + + Ok(resp) } #[allow(clippy::too_many_arguments)] @@ -1331,6 +1325,7 @@ fn try_send_impl( memo: Option, msg: Option, block: &cosmwasm_std::BlockInfo, + tracker: &mut GasTracker ) -> StdResult<()> { try_transfer_impl( deps, @@ -1341,8 +1336,7 @@ fn try_send_impl( denom, memo.clone(), block, - // TESTING - &mut vec![], + tracker, )?; try_add_receiver_api_callback( @@ -1377,6 +1371,8 @@ fn try_send( let mut messages = vec![]; let symbol = CONFIG.load(deps.storage)?.symbol; + let mut tracker: GasTracker = GasTracker::new(deps.api); + try_send_impl( &mut deps, rng, @@ -1389,11 +1385,18 @@ fn try_send( memo, msg, &env.block, + &mut tracker )?; - Ok(Response::new() + let mut resp = Response::new() .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?)) + .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); + + if cfg!(feature="gas_tracking") { + resp = tracker.add_to_response(resp); + } + + Ok(resp) } fn try_batch_send( @@ -1405,6 +1408,9 @@ fn try_batch_send( ) -> StdResult { let mut messages = vec![]; let symbol = CONFIG.load(deps.storage)?.symbol; + + let mut tracker: GasTracker = GasTracker::new(deps.api); + for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; try_send_impl( @@ -1419,6 +1425,7 @@ fn try_batch_send( action.memo, action.msg, &env.block, + &mut tracker, )?; } @@ -1488,6 +1495,8 @@ fn try_transfer_from_impl( use_allowance(deps.storage, env, owner, spender, raw_amount)?; + let mut tracker: GasTracker = GasTracker::new(deps.api); + perform_transfer( deps.storage, rng, @@ -1498,9 +1507,7 @@ fn try_transfer_from_impl( denom, memo, &env.block, - // TESTING - deps.api, - &mut vec![], + &mut tracker, )?; Ok(()) @@ -2006,48 +2013,43 @@ fn perform_transfer( denom: String, memo: Option, block: &BlockInfo, - // TESTING - api: &dyn Api, - logs: &mut Vec<(String, String)>, + tracker: &mut GasTracker, ) -> StdResult<()> { + // #[cfg(feature="gas_tracking")] + let mut group1 = tracker.group("perform_transfer1"); + // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_transfer_action(store, from, sender, to, amount, denom, memo, block)?; - // TESTING - let gas = api.check_gas()?; - logs.push(("gas2".to_string(), format!("{gas}"))); + // #[cfg(feature="gas_tracking")] + group1.log("store_transfer_action"); // load delayed write buffer let mut dwb = DWB.load(store)?; - // TESTING - let gas = api.check_gas()?; - logs.push(("gas3".to_string(), format!("{gas}"))); + // #[cfg(feature="gas_tracking")] + group1.log("DWB.load"); let transfer_str = "transfer"; + // settle the owner's account dwb.settle_sender_or_owner_account(store, from, tx_id, amount, transfer_str)?; + // if this is a *_from action, settle the sender's account, too if sender != from { dwb.settle_sender_or_owner_account(store, sender, tx_id, 0, transfer_str)?; } - // TESTING - let gas = api.check_gas()?; - logs.push(("gas4".to_string(), format!("{gas}"))); - // add the tx info for the recipient to the buffer dwb.add_recipient(store, rng, to, tx_id, amount)?; - - // TESTING - let gas = api.check_gas()?; - logs.push(("gas5".to_string(), format!("{gas}"))); + + // #[cfg(feature="gas_tracking")] + let mut group2 = tracker.group("perform_transfer2"); DWB.save(store, &dwb)?; - // TESTING - let gas = api.check_gas()?; - logs.push(("gas6".to_string(), format!("{gas}"))); + // #[cfg(feature="gas_tracking")] + group2.log("DWB.save"); Ok(()) } diff --git a/src/gas_tracker.rs b/src/gas_tracker.rs new file mode 100644 index 00000000..48c3a3ee --- /dev/null +++ b/src/gas_tracker.rs @@ -0,0 +1,84 @@ +use cosmwasm_std::{Api, Response}; + +pub struct GasTracker<'a> { + logs: Vec<(String, String)>, + api: &'a dyn Api, +} + +impl<'a> GasTracker<'a> { + pub fn new(api: &'a dyn Api) -> Self { + Self { + logs: Vec::new(), + api, + } + } + + pub fn group<'b>(&'b mut self, name: &str) -> GasGroup<'a, 'b> { + let mut group = GasGroup::new(self, name.to_string()); + group.mark(); + group + } + + // pub fn from<'b>(&'b mut self, other: GasGroup<'b, 'b>) -> GasGroup<'a, 'b> { + // let mut group = GasGroup::new(self, other.name); + // group.index = other.index; + // group + // } + + // pub fn from<'b>(&'b mut self, name: &str, index: usize) -> GasGroup<'a, 'b> { + // let mut group = GasGroup::new(self, name.to_string()); + // group.index = index; + // group + // } + + pub fn add_to_response(self, resp: Response) -> Response { + let mut new_resp = resp.clone(); + for log in self.logs.into_iter() { + new_resp = new_resp.add_attribute_plaintext( + log.0, + log.1 + ); + } + new_resp + } +} + +pub trait LoggingExt { + fn add_gas_tracker(&self, tracker: GasTracker) -> Response; +} + +impl LoggingExt for Response { + fn add_gas_tracker(&self, tracker: GasTracker) -> Response { + tracker.add_to_response(self.to_owned()) + } +} + +pub struct GasGroup<'a, 'b> { + pub tracker: &'b mut GasTracker<'a>, + pub name: String, + pub index: usize, +} + +impl<'a, 'b> GasGroup<'a, 'b> { + fn new(tracker: &'b mut GasTracker<'a>, name: String) -> Self { + Self { + tracker, + name, + index: 0, + } + } + + pub fn mark(&mut self) { + self.log(""); + } + + pub fn log(&mut self, comment: &str) { + let gas = self.tracker.api.check_gas(); + let log_entry = ( + format!("gas.{}", self.name,), + format!("{}:{}:{}", self.index, gas.unwrap_or(0u64).to_string(), comment), + ); + self.tracker.logs.push(log_entry); + self.index += 1; + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ffa335dd..1334fcbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,3 +10,4 @@ mod transaction_history; mod dwb; mod btbe; mod strings; +mod gas_tracker; diff --git a/tests/dwb/.eslintrc.cjs b/tests/dwb/.eslintrc.cjs new file mode 100644 index 00000000..b045f013 --- /dev/null +++ b/tests/dwb/.eslintrc.cjs @@ -0,0 +1,14 @@ + +module.exports = { + extends: '@blake.regalia/eslint-config-elite', + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + tsconfigRootDir: __dirname, + project: 'tsconfig.json', + }, + rules: { + 'no-console': 'off', + '@typescript-eslint/naming-convention': 'off', + }, +}; diff --git a/tests/dwb/bun.lockb b/tests/dwb/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..0aebc0e6d936a91f9be007e2f4bd00d73ffabe23 GIT binary patch literal 102137 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 G|NI|NG?VuL literal 0 HcmV?d00001 diff --git a/tests/dwb/package.json b/tests/dwb/package.json new file mode 100644 index 00000000..6ce0d170 --- /dev/null +++ b/tests/dwb/package.json @@ -0,0 +1,23 @@ +{ + "private": "true", + "scripts": { + "test": "pushd ../../ && make compile-optimized && popd && bun run src/main.ts" + }, + "devDependencies": { + "@blake.regalia/belt": "^0.36.2", + "@blake.regalia/eslint-config-elite": "^0.4.4", + "@blake.regalia/tsconfig": "^0.2.0", + "@solar-republic/types": "^0.2.10", + "@types/node": "^22.0.0", + "chai": "^5.1.1", + "chai-bites": "^0.2.0", + "eslint": " 8" + }, + "dependencies": { + "@solar-republic/contractor": "^0.8.15", + "@solar-republic/cosmos-grpc": "^0.15.4", + "@solar-republic/crypto": "^0.2.11", + "@solar-republic/neutrino": "^1.4.5", + "bignumber.js": "^9.1.2" + } +} \ No newline at end of file diff --git a/tests/dwb/src/constants.ts b/tests/dwb/src/constants.ts new file mode 100644 index 00000000..7591e3f4 --- /dev/null +++ b/tests/dwb/src/constants.ts @@ -0,0 +1,26 @@ +import type {TrustedContextUrl} from '@solar-republic/types'; + +import {base64_to_bytes} from '@blake.regalia/belt'; +import {Wallet} from '@solar-republic/neutrino'; + +export const P_LOCALSECRET_LCD = (process.env['SECRET_LCD'] || 'http://localhost:1317') as TrustedContextUrl; +export const P_LOCALSECRET_RPC = (process.env['SECRET_RPC'] || 'http://localhost:26656') as TrustedContextUrl; + +export const X_GAS_PRICE = 0.1; + +// import pre-configured wallets +export const [k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d] = await Promise.all([ + '8Ke2frmnGdVPipv7+xh9jClrl5EaBb9cowSUgj5GvrY=', + 'buqil+tLeeW7VLuugvOdTmkP3+tUwlCoScPZxeteBPE=', + 'UFrCdmofR9iChp6Eg7kE5O3wT+jsOXwJPWwB6kSeuhE=', + 'MM/1ZSbT5RF1BnaY6ui/i7yEN0mukGzvXUv+jOyjD0E=', +].map(sb64_sk => Wallet(base64_to_bytes(sb64_sk), 'secretdev-1', P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, 'secret'))); + +export const H_ADDRS = { + [k_wallet_a.addr]: 'Alice', + [k_wallet_b.addr]: 'Bob', + [k_wallet_c.addr]: 'Carol', + [k_wallet_d.addr]: 'David', +}; + +export const N_DECIMALS = 6; diff --git a/tests/dwb/src/contract.ts b/tests/dwb/src/contract.ts new file mode 100644 index 00000000..cd0f3d5c --- /dev/null +++ b/tests/dwb/src/contract.ts @@ -0,0 +1,105 @@ +import type {JsonObject} from '@blake.regalia/belt'; +import type {EncodedGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any'; +import type {TxResultTuple, Wallet, WeakSecretAccAddr} from '@solar-republic/neutrino'; +import type {CwHexLower, WeakUintStr} from '@solar-republic/types'; + +import {promisify} from 'node:util'; +import {gunzip} from 'node:zlib'; + +import {base64_to_bytes, bytes_to_hex, bytes_to_text, cast, sha256} from '@blake.regalia/belt'; +import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any'; +import {SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_STORE_CODE, SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_INSTANTIATE_CONTRACT, encodeSecretComputeMsgStoreCode, encodeSecretComputeMsgInstantiateContract} from '@solar-republic/cosmos-grpc/secret/compute/v1beta1/msg'; +import {querySecretComputeCodeHashByCodeId, querySecretComputeCodes} from '@solar-republic/cosmos-grpc/secret/compute/v1beta1/query'; +import {destructSecretRegistrationKey} from '@solar-republic/cosmos-grpc/secret/registration/v1beta1/msg'; +import {querySecretRegistrationTxKey} from '@solar-republic/cosmos-grpc/secret/registration/v1beta1/query'; +import {SecretWasm, broadcast_result, create_and_sign_tx_direct, exec_fees} from '@solar-republic/neutrino'; + +import {X_GAS_PRICE, P_LOCALSECRET_LCD} from './constants'; + + +export async function exec(k_wallet: Wallet, atu8_msg: EncodedGoogleProtobufAny, xg_gas_limit: bigint): Promise { + const [atu8_raw, atu8_signdoc, si_txn] = await create_and_sign_tx_direct( + k_wallet, + [atu8_msg], + exec_fees(xg_gas_limit, X_GAS_PRICE, 'uscrt'), + xg_gas_limit + ); + + return await broadcast_result(k_wallet, atu8_raw, si_txn); +} + +export async function upload_code(k_wallet: Wallet, atu8_wasm: Uint8Array): Promise { + let atu8_bytecode = atu8_wasm; + + // gzip-encoded; decompress + if(0x1f === atu8_wasm[0] && 0x8b === atu8_wasm[1]) { + atu8_bytecode = await promisify(gunzip)(atu8_wasm); + } + + // hash + const atu8_hash = await sha256(atu8_bytecode); + const sb16_hash = cast(bytes_to_hex(atu8_hash)); + + // fetch all uploaded codes + const [,, g_codes] = await querySecretComputeCodes(P_LOCALSECRET_LCD); + + // already uploaded + const g_existing = g_codes?.code_infos?.find(g => g.code_hash! === sb16_hash); + if(g_existing) { + return g_existing.code_id as WeakUintStr; + } + + // upload + const [xc_code, sx_res, g_meta, atu8_data, h_events] = await exec(k_wallet, encodeGoogleProtobufAny( + SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_STORE_CODE, + encodeSecretComputeMsgStoreCode( + k_wallet.addr, + atu8_bytecode + ) + ), 30_000_000n); + + if(xc_code) throw Error(sx_res); + + return h_events!['message.code_id'][0] as WeakUintStr; +} + +export async function instantiate_contract(k_wallet: Wallet, sg_code_id: WeakUintStr, h_init_msg: JsonObject): Promise { + const [,, g_reg] = await querySecretRegistrationTxKey(P_LOCALSECRET_LCD); + const [atu8_cons_pk] = destructSecretRegistrationKey(g_reg); + const k_wasm = await SecretWasm(atu8_cons_pk); + const [,, g_hash] = await querySecretComputeCodeHashByCodeId(P_LOCALSECRET_LCD, sg_code_id); + + // @ts-expect-error imported types versioning + const atu8_body = await k_wasm.encodeMsg(g_hash!.code_hash, h_init_msg); + + const [xc_code, sx_res, g_meta, atu8_data, h_events] = await exec(k_wallet, encodeGoogleProtobufAny( + SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_INSTANTIATE_CONTRACT, + encodeSecretComputeMsgInstantiateContract( + k_wallet.addr, + null, + sg_code_id, + h_init_msg['name'] as string, + atu8_body + ) + ), 10_000_000n); + + if(xc_code) { + const s_error = g_meta?.log ?? sx_res; + + // encrypted error message + const m_response = /(\d+):(?: \w+:)*? encrypted: (.+?): (.+?) contract/.exec(s_error); + if(m_response) { + // destructure match + const [, s_index, sb64_encrypted, si_action] = m_response; + + // decrypt ciphertext + const atu8_plaintext = await k_wasm.decrypt(base64_to_bytes(sb64_encrypted), atu8_body.slice(0, 32)); + + throw Error(bytes_to_text(atu8_plaintext)); + } + + throw Error(sx_res); + } + + return h_events!['message.contract_address'][0] as WeakSecretAccAddr; +} diff --git a/tests/dwb/src/dwb-entry.ts b/tests/dwb/src/dwb-entry.ts new file mode 100644 index 00000000..32372535 --- /dev/null +++ b/tests/dwb/src/dwb-entry.ts @@ -0,0 +1,76 @@ +import type {Nilable} from '@blake.regalia/belt'; +import type {CwSecretAccAddr} from '@solar-republic/neutrino'; + +import {bytes_to_biguint_be, bytes_to_hex} from '@blake.regalia/belt'; +import {bech32_encode} from '@solar-republic/crypto'; +import {BigNumber} from 'bignumber.js'; + +import {H_ADDRS} from './constants'; +import {SX_ANSI_BLUE, SX_ANSI_DIM_ON, SX_ANSI_GREEN, SX_ANSI_RESET, SX_ANSI_YELLOW} from './helper'; + +const NB_ADDR = 20; +const NB_AMOUNT = 8; +const NB_HEAD = 5; +const NB_LEN = 2; + +const NB_ENTRY = NB_ADDR+NB_AMOUNT+NB_HEAD+NB_LEN; + +export class DwbEntry { + constructor(protected _atu8_raw: Uint8Array) { + if(this._atu8_raw.byteLength !== NB_ENTRY) { + throw Error(`DWB entry was not exactly ${NB_ENTRY} bytes in length`); + } + } + + get raw(): Uint8Array { + return this._atu8_raw; + } + + get isNil(): boolean { + return /^0+$/.test(bytes_to_hex(this._atu8_raw)); + } + + get address(): CwSecretAccAddr { + return bech32_encode('secret', this._atu8_raw.subarray(0, NB_ADDR)); + } + + get amount(): bigint { + return bytes_to_biguint_be(this._atu8_raw.subarray(NB_ADDR, NB_ADDR+NB_AMOUNT)); + } + + get head(): bigint { + return bytes_to_biguint_be(this._atu8_raw.subarray(NB_ADDR+NB_AMOUNT, NB_ADDR+NB_AMOUNT+NB_HEAD)); + } + + get listlen(): bigint { + return bytes_to_biguint_be(this._atu8_raw.subarray(NB_ADDR+NB_AMOUNT+NB_HEAD, NB_ADDR+NB_AMOUNT+NB_HEAD+NB_LEN)); + } + + toString(k_prev?: Nilable): string { + let s_alias = H_ADDRS[this.address] || ''; + s_alias += s_alias? ` (${this.address.slice(0, 12)+'...'+this.address.slice(-5)})`: this.address; + s_alias = s_alias.padEnd(45, ' '); + + let s_amount = BigNumber(this.amount+'').shiftedBy(-6).toFixed(6).padStart(12, ' '); + + if(k_prev) { + if(this.address !== k_prev.address) { + const sx_color = this.amount? SX_ANSI_GREEN: SX_ANSI_YELLOW; + + s_alias = `${sx_color}${s_alias}${SX_ANSI_RESET}`; + s_amount = `${sx_color}${s_amount}${SX_ANSI_RESET}`; + } + else if(this.amount !== k_prev.amount) { + s_alias = `${SX_ANSI_BLUE}${s_alias}${SX_ANSI_RESET}`; + s_amount = `${SX_ANSI_BLUE}${s_amount}${SX_ANSI_RESET}`; + } + } + + return [ + s_alias, + s_amount, + (this.head+'').padStart(4, ' '), + (this.listlen+'').padStart(4, ' '), + ].map(s => this.amount? s: `${SX_ANSI_DIM_ON}${s}${SX_ANSI_RESET}`).join(' │ '); + } +} diff --git a/tests/dwb/src/dwb.ts b/tests/dwb/src/dwb.ts new file mode 100644 index 00000000..51c4eca8 --- /dev/null +++ b/tests/dwb/src/dwb.ts @@ -0,0 +1,248 @@ +import type {SecretApp, WeakSecretAccAddr} from '@solar-republic/neutrino'; + +import {bytes, parse_json} from '@blake.regalia/belt'; +import * as chai from 'chai'; +const {expect} = chai; + + +import {DwbEntry} from './dwb-entry'; +import {SX_ANSI_DIM_ON, SX_ANSI_RESET, fail} from './helper'; + +export type DwbRequirements = { + showDelta?: boolean; + shouldNotContainEntriesFor?: WeakSecretAccAddr[]; +}; + +const R_ENTRY = /\s*DelayedWriteBufferEntry\(([^]*?)\)\s*,?/y; + +export function parse_dwb_dump(sx_dump: string) { + const [, sx_contents] = /DelayedWriteBuffer\s*\{\s*([^]*?)\s*\}\s*$/.exec(sx_dump)!; + const [, sg_empty, sx_entries] = /^\s*empty_space_counter:\s*(\d+),\s*entries:\s*\[([^]*)\]\s*$/.exec(sx_contents)!; + + const a_entries: Uint8Array[] = []; + for(;;) { + const m_entry = R_ENTRY.exec(sx_entries)!; + if(!m_entry) break; + + a_entries.push(bytes(parse_json(m_entry[1]))); + } + + return { + empty_space_counter: parse_json(sg_empty), + entries: a_entries, + }; +} + +export class DwbValidator { + protected _a_entries_prev: DwbEntry[] = []; + protected _a_entries: DwbEntry[] = []; + protected _n_empty = 0; + + constructor(protected _k_app: SecretApp) {} + + get entries(): DwbEntry[] { + return this._a_entries.slice(); + } + + get previous(): DwbEntry[] { + return this._a_entries_prev.slice(); + } + + get empty(): number { + return this._n_empty; + } + + async sync() { + // cache previous state + this._a_entries_prev = this._a_entries.slice(); + + // dump dwb contents + const [g_dwb_res] = await this._k_app.query('dwb', {}); + + // parse + const { + empty_space_counter: sg_empty, + entries: a_entries, + } = parse_dwb_dump(g_dwb_res!.dwb as string); + + // update cached entries + this._a_entries.length = 0; + this._a_entries.push(...a_entries.map(atu8 => new DwbEntry(atu8))); + + // save empty spaces counter + this._n_empty = parseFloat(sg_empty as string); + + return this._a_entries; + } + + async check(gc_check?: DwbRequirements) { + const a_prev = this._a_entries_prev; + const a_entries = this._a_entries; + + // should exclude entry for given addresses + const a_exclude = gc_check?.shouldNotContainEntriesFor; + if(a_exclude?.length) { + for(const sa_exclude of a_exclude) { + const i_found = a_entries.findIndex(k => sa_exclude === k.address); + + if(i_found > -1) { + fail(`Expected buffer to NOT contain an entry for ${sa_exclude} but found it at position ${i_found}`); + } + } + } + + // count empty spaces + let c_empty_actual = 0; + for(let i_space=a_entries.length-1; i_space>0; i_space--) { + if(!a_entries[i_space].amount) { + c_empty_actual += 1; + } + else { + break; + } + } + + // find changes + for(let i_space=0; i_space `┃ ${SX_ANSI_DIM_ON}`+`...(empty x ${c})`.padEnd(78, ' ')+`${SX_ANSI_RESET}`+' ┃'; + let i_index = 0; + let c_empty = 0; + + for(const k_entry of this._a_entries) { + if(k_entry.isNil) { + c_empty += 1; + } + else { + if(c_empty) { + a_lines.push(empty_row(c_empty)); + c_empty = 0; + } + + a_lines.push(`┃ ${(i_index+'').padStart(3, ' ')} │ ${k_entry.toString(b_show_delta? a_prev[i_index]: null)} ┃`); + } + + i_index += 1; + } + + if(c_empty) a_lines.push(empty_row(c_empty)); + + return [ + a_lines.join('\n'), + `┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛`, + ].join('\n'); + } + + print(b_show_delta?: boolean): void { + console.log(this.toString(b_show_delta)); + } +} + + +const g_dwb = parse_dwb_dump(` + DelayedWriteBuffer { +empty_space_counter: 61, + entries: [ + DelayedWriteBufferEntry([30, 64, 27, 13, 80, 9, 191, 112, 225, 11, 76, 117, 251, 233, 171, 52, 62, 116, 221, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([252, 120, 243, 61, 153, 55, 155, 238, 217, 219, 75, 240, 232, 43, 128, 39, 177, 94, 70, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([78, 34, 145, 19, 199, 90, 194, 255, 187, 156, 147, 189, 154, 40, 119, 128, 77, 51, 242, 84, 0, 0, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, 3, 0, 1]), + DelayedWriteBufferEntry([236, 133, 74, 220, 71, 232, 157, 194, 70, 160, 113, 10, 155, 74, 105, 192, 216, 151, 180, 80, 0, 0, 0, 0, 0, 30, 132, 128, 0, 0, 0, 0, 7, 0, 2]), + DelayedWriteBufferEntry([171, 152, 150, 130, 223, 89, 19, 108, 106, 73, 34, 29, 160, 38, 68, 217, 164, 90, 53, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + ] +} +`); + +console.log(g_dwb.entries.length); diff --git a/tests/dwb/src/gas-checker.ts b/tests/dwb/src/gas-checker.ts new file mode 100644 index 00000000..c5491e9b --- /dev/null +++ b/tests/dwb/src/gas-checker.ts @@ -0,0 +1,50 @@ +import type {GroupedGasLogs} from './snip'; + +import {entries} from '@blake.regalia/belt'; + +import {SX_ANSI_GREEN, SX_ANSI_MAGENTA, SX_ANSI_RESET} from './helper'; + +export class GasChecker { + constructor(protected _h_baseline: GroupedGasLogs, protected _xg_used: bigint) {} + + compare(h_local: GroupedGasLogs, xg_used: bigint) { + const {_h_baseline, _xg_used} = this; + + console.log(` ⚖️ Gas usage relative to baseline: ${xg_used === _xg_used + ? `${SX_ANSI_GREEN}0` + : `${SX_ANSI_MAGENTA}${xg_used > _xg_used? '+': ''}${xg_used - _xg_used}` + }${SX_ANSI_RESET}`); + + // each group + for(const [si_group, a_logs_local] of entries(h_local)) { + // logs emitted from this transfer group + let c_logs = 0; + + // find group in baseline + const a_logs_baseline = _h_baseline[si_group]; + + // offset + const xg_previous = a_logs_local[0]?.gas; + + // each log + for(let i_log=1; i_log g.index === g_log_local.index)!; + + const xg_gap_baseline = g_log_baseline.gap; + const xg_gap_local = g_log_local.gap; + + // calculate delta + const xg_delta = xg_gap_local - xg_gap_baseline; + + // non-zero delta + if(xg_delta) { + console.log(` ${si_group.slice(0, 20).padEnd(20, ' ')} │ ${((xg_delta > 0? '+': '')+xg_delta).padEnd(5, ' ')} │ ${g_log_local.comment}`); + c_logs += 1; + } + } + } + } +} diff --git a/tests/dwb/src/helper.ts b/tests/dwb/src/helper.ts new file mode 100644 index 00000000..60836095 --- /dev/null +++ b/tests/dwb/src/helper.ts @@ -0,0 +1,89 @@ +import type {Promisable} from '@blake.regalia/belt'; + +import {is_string, map_entries} from '@blake.regalia/belt'; + +/* eslint-disable @typescript-eslint/naming-convention */ +export const SX_ANSI_RESET = '\x1b[0m'; +export const SX_ANSI_DIM_ON = '\x1b[2m'; +export const SX_ANSI_UNDERLINE = '\x1b[4m'; +export const SX_ANSI_DIM_OFF = '\x1b[22m'; +export const SX_ANSI_RED = '\x1b[31m'; +export const SX_ANSI_GREEN = '\x1b[32m'; +export const SX_ANSI_YELLOW = '\x1b[33m'; +export const SX_ANSI_BLUE = '\x1b[34m'; +export const SX_ANSI_MAGENTA = '\x1b[35m'; +export const SX_ANSI_CYAN = '\x1b[36m'; +export const SX_ANSI_WHITE = '\x1b[37m'; +export const SX_ANSI_GRAY_BG = '\x1b[100m'; +/* eslint-enable */ + +// polyfill crypto global for node.js env +globalThis.crypto ||= (await import('crypto')).webcrypto; + +export function pass(s_test: string): void { + // eslint-disable-next-line no-console + console.log(`${SX_ANSI_GREEN}✓${SX_ANSI_RESET} ${s_test}`); +} + +function error(s_test: string, ...a_args: Array) { + const a_rest = a_args.map(z => is_string(z)? z: map_entries(z, ([si, w]) => `\n\t${si}: ${w}`).join('\n')); + console.error(`${s_test}: ${a_rest.join('; ')}`); +} + +export function fail(s_test: string, ...a_args: Array): void { + error(`❌ ${s_test}`, ...a_args); + throw Error(`Exitting on error`); +} + +export function caught(s_test: string, ...a_args: Array): void { + error(`💀 ${s_test}`, ...a_args); +} + +interface GroupCallback { + it(s_test: string, f_test: () => Promisable): Promise; +} + +export async function describe(s_group: string, f_group: (g_call: GroupCallback) => Promisable): Promise { + const a_results: Array<{ + type: 'pass'; + name: string; + } | { + type: 'fail'; + name: string; + message: string; + }> = []; + + await f_group({ + async it(s_test: string, f_test: () => Promisable) { + try { + await f_test(); + + a_results.push({ + type: 'pass', + name: s_test, + }); + } + catch(e_run) { + a_results.push({ + type: 'fail', + name: s_test, + message: (e_run as Error).stack || '', + }); + } + }, + }); + + console.log(''); + console.log(`# ${s_group}\n${'='.repeat(2+s_group.length)}`); + + for(const g_result of a_results) { + if('pass' === g_result.type) { + pass(g_result.name); + } + else { + fail(g_result.name, g_result.message); + } + } + + console.log(''); +} diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts new file mode 100644 index 00000000..317b6b7c --- /dev/null +++ b/tests/dwb/src/main.ts @@ -0,0 +1,117 @@ +import type {Snip24} from '@solar-republic/contractor'; + +import {readFileSync} from 'node:fs'; + +import {bytes, bytes_to_base64, entries} from '@blake.regalia/belt'; +import {SecretApp, SecretContract, random_32} from '@solar-republic/neutrino'; +import {BigNumber} from 'bignumber.js'; + +import {N_DECIMALS, P_LOCALSECRET_LCD, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; +import {upload_code, instantiate_contract} from './contract'; +import {DwbValidator} from './dwb'; +import {GasChecker} from './gas-checker'; +import {transfer} from './snip'; + +const S_CONTRACT_LABEL = 'snip2x-test_'+bytes_to_base64(crypto.getRandomValues(bytes(6))); + +const atu8_wasm = readFileSync('../../contract.wasm'); + +console.debug(`Uploading code...`); +const sg_code_id = await upload_code(k_wallet_a, atu8_wasm); + +console.debug(`Instantiating contract...`); + +const sa_snip = await instantiate_contract(k_wallet_a, sg_code_id, { + name: S_CONTRACT_LABEL, + symbol: 'TKN', + decimals: 6, + admin: k_wallet_a.addr, + initial_balances: entries({ + [k_wallet_a.addr]: 100_000000n, + }).map(([sa_account, xg_balance]) => ({ + address: sa_account, + amount: `${xg_balance}`, + })), + prng_seed: bytes_to_base64(random_32()), + config: { + public_total_supply: true, + enable_deposit: true, + enable_redeem: true, + enable_mint: true, + enable_burn: true, + }, +}); + +console.debug(`Running tests against ${sa_snip}...`); + +// @ts-expect-error deep instantiation +const k_contract = await SecretContract(P_LOCALSECRET_LCD, sa_snip); + +const k_app_a = SecretApp(k_wallet_a, k_contract, X_GAS_PRICE); +const k_app_b = SecretApp(k_wallet_b, k_contract, X_GAS_PRICE); +const k_app_c = SecretApp(k_wallet_c, k_contract, X_GAS_PRICE); +const k_app_d = SecretApp(k_wallet_d, k_contract, X_GAS_PRICE); + +const H_APPS = { + a: k_app_a, + b: k_app_b, + c: k_app_c, + d: k_app_d, +}; + +const k_dwbv = new DwbValidator(k_app_a); + +console.log('# Initialized'); +await k_dwbv.sync(); +await k_dwbv.print(); +console.log('\n'); + +async function transfer_chain(sx_chain: string) { + const a_lines = sx_chain.split(/\s*\n+\s*/g).filter(s => s && /^\s*(\d+)/.test(s)); + + let k_checker: GasChecker | null = null; + + for(const sx_line of a_lines) { + const [, sx_amount, si_from, si_to] = /^\s*([\d.]+)(?:\s*TKN)?\s+(\w+)(?:\s+to|\s*[-=]*>+)?\s+(\w+)\s*/.exec(sx_line)!; + + const xg_amount = BigInt(BigNumber(sx_amount).shiftedBy(N_DECIMALS).toFixed(0)); + + console.log(sx_amount, si_from, si_to); + + const g_result = await transfer(k_dwbv, xg_amount, H_APPS[si_from[0].toLowerCase()], H_APPS[si_to[0].toLowerCase()], k_checker); + + if(!k_checker) { + k_checker = new GasChecker(g_result.tracking, g_result.gasUsed); + } + } +} + +{ + await transfer_chain(` + 1 TKN Alice => Bob + 2 TKN Alice => Carol + 5 TKN Alice => David + 1 TKN Bob => Carol -- Bob's entire balance; settles Bob for 1st time + 1 TKN Carol => David -- should accumulate; settles Carol for 1st time + 1 TKN David => Alice -- re-adds Alice to buffer; settles David for 1st time + `); + + /* + All operations should be same gas from now on + Alice: 93 + Bob: 0 + Carol: 1 + David: 4 + */ + + console.log('--- should all be same gas ---'); + + await transfer_chain(` + 1 TKN David => Bob + 1 TKN David => Bob -- exact same transfer repeated + 1 TKN Alice => Bob + 1 TKN Bob => Carol + 1 TKN Alice => Carol + 1 TKN Carol => Bob -- yet again + `); +} diff --git a/tests/dwb/src/snip.ts b/tests/dwb/src/snip.ts new file mode 100644 index 00000000..26c78c2b --- /dev/null +++ b/tests/dwb/src/snip.ts @@ -0,0 +1,181 @@ +import type {DwbValidator} from './dwb'; +import type {GasChecker} from './gas-checker'; +import type {Dict, Nilable} from '@blake.regalia/belt'; +import type {SecretContractInterface} from '@solar-republic/contractor'; +import type {SecretApp} from '@solar-republic/neutrino'; +import type {CwUint128, SecretQueryPermit, WeakUintStr} from '@solar-republic/types'; + +import {entries} from '@blake.regalia/belt'; +import {sign_secret_query_permit} from '@solar-republic/neutrino'; +import BigNumber from 'bignumber.js'; + +import {H_ADDRS, N_DECIMALS} from './constants'; +import {fail} from './helper'; + + +export type GasLog = { + index: number; + gas: bigint; + gap: bigint; + comment: string; +}; + +export type GroupedGasLogs = Dict; + +export type TransferResult = { + tracking: GroupedGasLogs; + gasUsed: bigint; +}; + +type TokenBalance = SecretContractInterface<{ + queries: { + balance: [{}, { + amount: CwUint128; + }]; + + with_permit: { + variants: [ + { + msg: { + query: { + balance: {}; + }; + permit: SecretQueryPermit; + }; + response: { + balance: { + amount: CwUint128; + }; + }; + }, + ]; + }; + }; +}>; + +export async function balance(k_app: SecretApp) { + const g_permit = await sign_secret_query_permit(k_app.wallet, 'snip-balance', [k_app.contract.addr], ['balance']); + return await k_app.query('balance', {}, g_permit as unknown as null); +} + +export async function transfer( + k_dwbv: DwbValidator, + xg_amount: bigint, + k_app_owner: SecretApp, + k_app_recipient: SecretApp, + k_checker?: Nilable +): Promise { + const sa_owner = k_app_owner.wallet.addr; + const sa_recipient = k_app_recipient.wallet.addr; + + // query balance of owner and recipient + const [ + [g_balance_owner_before], + [g_balance_recipient_before], + ] = await Promise.all([ + balance(k_app_owner), + balance(k_app_recipient), + ]); + + // execute transfer + const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_owner.exec('transfer', { + amount: `${xg_amount}` as CwUint128, + recipient: sa_recipient, + }, 100_000n); + + if(xc_code) { + throw Error(sx_res); + } + + // console.log(h_events); + + // query balance of owner and recipient again + const [ + [g_balance_owner_after], + [g_balance_recipient_after], + ] = await Promise.all([ + balance(k_app_owner), + balance(k_app_recipient), + ]); + + // sync the buffer + await k_dwbv.sync(); + + // // results + // const sg_gas_used = g_meta?.gas_used; + // console.log(` ⏹ ${k_dwbv.empty} spaces`); + + // section header + console.log(`# Transfer ${BigNumber(xg_amount+'').shiftedBy(-N_DECIMALS).toFixed()} TKN ${H_ADDRS[sa_owner] || sa_owner} => ${H_ADDRS[sa_recipient]} | ⏹ ${k_dwbv.empty} spaces | ⛽️ ${g_meta!.gas_used} gas used`); + + const h_tracking: GroupedGasLogs = {}; + for(const [si_key, a_values] of entries(h_events!)) { + const m_key = /^wasm\.gas\.(\w+)$/.exec(si_key); + if(m_key) { + const [, si_group] = m_key; + + const a_logs: GasLog[] = []; + let xg_previous = 0n; + + for(const sx_value of a_values) { + const [, sg_index, sg_gas, s_comment] = /^(\d+):(\d+):([^]*)$/.exec(sx_value)!; + + const xg_gas = BigInt(sg_gas); + + a_logs.push({ + index: parseInt(sg_index), + gas: xg_gas, + gap: xg_gas - xg_previous, + comment: s_comment, + }); + + xg_previous = xg_gas; + } + + h_tracking[si_group] = a_logs.sort((g_a, g_b) => g_a.index - g_b.index); + } + } + + // console.log(h_tracking); + + if(k_checker) { + k_checker.compare(h_tracking, BigInt(g_meta!.gas_used)); + } + else if(null === k_checker) { + console.log(` ⚖️ Setting baseline gas used to ${g_meta!.gas_used}`); + } + + // prit its state + k_dwbv.print(true); + + + // balance queries failed + if(!g_balance_owner_before || !g_balance_recipient_before || !g_balance_owner_after || !g_balance_recipient_after) { + throw fail(`Failed to fetch balances`); + } + + // expect exact amount difference + const xg_owner_loss = BigInt(g_balance_owner_before.amount as string) - BigInt(g_balance_owner_after.amount); + if(xg_owner_loss !== xg_amount) { + fail(`Owner's balance changed by ${-xg_owner_loss}, but the amount sent was ${xg_amount}`); + } + + // expect exact amount difference + const xg_recipient_gain = BigInt(g_balance_recipient_after.amount) - BigInt(g_balance_recipient_before.amount); + if(xg_recipient_gain !== xg_amount) { + fail(`Recipient's balance changed by ${xg_recipient_gain}, but the amount sent was ${xg_amount}`); + } + + // make assertions + await k_dwbv.check({ + // shouldNotContainEntriesFor: [k_app_owner.wallet.addr], + }); + + // close + console.log('\n'); + + return { + tracking: h_tracking, + gasUsed: BigInt(g_meta!.gas_used), + }; +} diff --git a/tests/dwb/tsconfig.json b/tests/dwb/tsconfig.json new file mode 100644 index 00000000..6107ae94 --- /dev/null +++ b/tests/dwb/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": [ + "@blake.regalia/tsconfig/tsconfig.node.json" + ], + + "compilerOptions": { + "moduleResolution": "Bundler", + }, +} \ No newline at end of file From f7e1d0c03aa07a20492c4ddb53da37259ff5ba98 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Fri, 2 Aug 2024 06:01:32 +0000 Subject: [PATCH 48/87] fix: dwb_entry recipient and hkdf size --- .cargo/config | 3 + .gitignore | 1 + Makefile | 9 +- build.rs | 8 +- src/btbe.rs | 51 +++++++--- src/config.rs | 1 - src/contract.rs | 124 +++++++++++++++++------ src/dwb.rs | 115 ++++++++++++--------- src/gas_tracker.rs | 4 + src/msg.rs | 4 +- tests/dwb/bun.lockb | Bin 102137 -> 104964 bytes tests/dwb/package.json | 26 +++-- tests/dwb/src/contract.ts | 14 +-- tests/dwb/src/dwb.ts | 150 ++++++++++++++-------------- tests/dwb/src/gas-checker.ts | 35 +++++-- tests/dwb/src/main.ts | 44 +++++--- tests/dwb/src/snip.ts | 65 ++++++++---- tests/dwb/tsconfig.json | 1 + tests/dwb/tsconfig.tsc-esm-fix.json | 7 ++ 19 files changed, 431 insertions(+), 231 deletions(-) delete mode 100644 src/config.rs create mode 100644 tests/dwb/tsconfig.tsc-esm-fix.json diff --git a/.cargo/config b/.cargo/config index bbe1fc95..0e2770dc 100644 --- a/.cargo/config +++ b/.cargo/config @@ -6,3 +6,6 @@ unit-test = "test --lib" integration-test = "test --test integration" schema = "run --example schema" + +[features] +gas_tracking = [] diff --git a/.gitignore b/.gitignore index 0ff28d37..83987d19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Build results /target +/tests/dwb/dist/ contract.wasm contract.wasm.gz diff --git a/Makefile b/Makefile index 1770609c..bd27390a 100644 --- a/Makefile +++ b/Makefile @@ -51,12 +51,19 @@ _compile: cargo build --target wasm32-unknown-unknown --locked cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm +.PHONY: compile-integration _compile-integration +compile-integration: _compile-integration contract.wasm.gz +_compile-integration: + DWB_CAPACITY=8 BTBE_CAPACITY=8 RUSTFLAGS='-C link-arg=-s' cargo build --features "gas_tracking" --release --target wasm32-unknown-unknown + @# The following line is not necessary, may work only on linux (extra size optimization) + wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm + .PHONY: compile-optimized _compile-optimized compile-optimized: _compile-optimized contract.wasm.gz _compile-optimized: RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown @# The following line is not necessary, may work only on linux (extra size optimization) - wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm + wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm .PHONY: compile-optimized-reproducible compile-optimized-reproducible: diff --git a/build.rs b/build.rs index b001d38a..6cef931c 100644 --- a/build.rs +++ b/build.rs @@ -5,8 +5,8 @@ use std::path::Path; fn main() { // config parameters - // let dwb_capacity = env!("DWB_CAPACITY").parse().unwrap_or_else(|_| "4".to_string()); - let dwb_capacity = env::var("DWB_CAPACITY").unwrap_or_else(|_| "4".to_string()); + let dwb_capacity = env::var("DWB_CAPACITY").unwrap_or_else(|_| "64".to_string()); + let btbe_capacity = env::var("BTBE_CAPACITY").unwrap_or_else(|_| "128".to_string()); // path to destination config.rs file let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); @@ -14,8 +14,10 @@ fn main() { // write constants let mut file = File::create(&dest_path).expect("Failed to write to config.rs"); - write!(file, "pub const DWB_CAPACITY: u16 = {};", dwb_capacity).unwrap(); + write!(file, "pub const DWB_CAPACITY: u16 = {};\n", dwb_capacity).unwrap(); + write!(file, "pub const BTBE_CAPACITY: u16 = {};\n", btbe_capacity).unwrap(); // monitor println!("cargo:rerun-if-env-changed=DWB_CAPACITY"); + println!("cargo:rerun-if-env-changed=BTBE_CAPACITY"); } diff --git a/src/btbe.rs b/src/btbe.rs index 7de85474..75fcbd1e 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -1,5 +1,7 @@ //! BTBE stands for bitwise-trie of bucketed entries +include!(concat!(env!("OUT_DIR"), "/config.rs")); + use constant_time_eq::constant_time_eq; use primitive_types::U256; use secret_toolkit::{notification::hkdf_sha_256, serialization::{Bincode2, Serde}, storage::Item}; @@ -7,7 +9,7 @@ use serde::{Serialize, Deserialize,}; use serde_big_array::BigArray; use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; -use crate::dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}; +use crate::{dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}, gas_tracker::GasTracker}; use crate::state::{safe_add_u64, INTERNAL_SECRET}; pub const KEY_BTBE_ENTRY_HISTORY: &[u8] = b"btbe-entry-hist"; @@ -78,7 +80,7 @@ impl StoredEntry { new_balance } else { return Err(StdError::generic_err(format!( - "insufficient funds", + "insufficient funds while creating StoredEntry; balance:{}, amount_spent:{}", dwb_entry.amount()?, amount_spent, ))); }; @@ -163,7 +165,7 @@ impl StoredEntry { new_balance } else { return Err(StdError::generic_err(format!( - "insufficient funds", + "insufficient funds while merging entry; balance:{}, amount_spent:{}", balance, amount_spent ))); }; @@ -217,13 +219,11 @@ impl StoredEntry { } } -const BTBE_BUCKET_LEN: u16 = 128; - #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] pub struct BtbeBucket { pub capacity: u16, #[serde(with = "BigArray")] - pub entries: [StoredEntry; BTBE_BUCKET_LEN as usize], + pub entries: [StoredEntry; BTBE_CAPACITY as usize], } //static BTBE_ENTRY_HISTORY: Item = Item::new(KEY_BTBE_ENTRY_HISTORY); @@ -236,9 +236,9 @@ type BucketEntryPosition = usize; impl BtbeBucket { pub fn new() -> StdResult { Ok(Self { - capacity: BTBE_BUCKET_LEN, + capacity: BTBE_CAPACITY, entries: [ - StoredEntry::new(CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; BTBE_BUCKET_LEN as usize + StoredEntry::new(CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; BTBE_CAPACITY as usize ] }) } @@ -251,7 +251,7 @@ impl BtbeBucket { } // has capacity for a new entry; save entry to bucket - self.entries[self.entries.len() - self.capacity as usize] = entry.clone(); + self.entries[(BTBE_CAPACITY - self.capacity) as usize] = entry.clone(); // update capacity self.capacity -= 1; @@ -333,7 +333,7 @@ impl BitwiseTrieNode { /// Determines whether a given entry belongs in the left node (true) or right node (false) fn entry_belongs_in_left_node(secret: &[u8], entry: StoredEntry, bit_pos: u8) -> StdResult { // create key bytes - let key_bytes = hkdf_sha_256(&Some(BUCKETING_SALT_BYTES.to_vec()), secret, entry.address_slice(), 256)?; + let key_bytes = hkdf_sha_256(&Some(BUCKETING_SALT_BYTES.to_vec()), secret, entry.address_slice(), 32)?; // convert to u258 let key_u256 = U256::from_big_endian(&key_bytes); @@ -349,7 +349,7 @@ pub fn locate_btbe_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdRe let secret = secret.as_slice(); // create key bytes - let hash = hkdf_sha_256(&Some(BUCKETING_SALT_BYTES.to_vec()), secret, address.as_slice(), 256)?; + let hash = hkdf_sha_256(&Some(BUCKETING_SALT_BYTES.to_vec()), secret, address.as_slice(), 32)?; // start at root of trie let mut node_id: u64 = 1; @@ -435,13 +435,24 @@ pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> St // merges a dwb entry into the current node's bucket // `spent_amount` is any required subtraction due to being sender of tx -pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferEntry, amount_spent: Option) -> StdResult<()> { +pub fn merge_dwb_entry( + storage: &mut dyn Storage, + dwb_entry: DelayedWriteBufferEntry, + amount_spent: Option, + tracker: &mut GasTracker, +) -> StdResult<()> { + #[cfg(feature="gas_tracking")] + let mut group1 = tracker.group("merge_dwb_entry.1"); + // locate the node that the given entry belongs in - let (mut node, node_id, mut bit_pos) = locate_btbe_node(storage, &dwb_entry.recipient()?)?; + let (mut node, mut node_id, mut bit_pos) = locate_btbe_node(storage, &dwb_entry.recipient()?)?; // load that node's current bucket let mut bucket = node.bucket(storage)?; + // bucket ID for logging purposes + let mut bucket_id = node.bucket; + // search for an existing entry if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(&dwb_entry.recipient()?) { // found existing entry @@ -449,6 +460,9 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE found_entry.merge_dwb_entry(storage, &dwb_entry, amount_spent)?; bucket.entries[idx] = found_entry; + #[cfg(feature="gas_tracking")] + group1.logf(format!("@merged {} into node #{}, bucket #{} at position {} ", dwb_entry.recipient()?, node_id, bucket_id, idx)); + // save updated bucket to storage node.set_and_save_bucket(storage, bucket)?; } else { @@ -463,6 +477,9 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE loop { // looping as many times as needed until the bucket has capacity for a new entry // try to add to the current bucket if bucket.add_entry(&btbe_entry) { + #[cfg(feature="gas_tracking")] + group1.logf(format!("@inserted into node #{}, bucket #{} (bitpos: {}) at position {}", node_id, bucket_id, bit_pos, BTBE_CAPACITY - bucket.capacity - 1)); + // bucket has capacity and it added the new entry // save bucket to storage node.set_and_save_bucket(storage, bucket)?; @@ -476,6 +493,7 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE // each entry for entry in bucket.entries { + // left_bucket.add_entry(&entry); // route entry if entry_belongs_in_left_node(secret, entry, bit_pos)? { left_bucket.add_entry(&entry); @@ -537,13 +555,20 @@ pub fn merge_dwb_entry(storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferE // save node BTBE_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node)?; + #[cfg(feature="gas_tracking")] + group1.logf(format!("@split node #{}, bucket #{} at bitpos {}, ", node_id, bucket_id, bit_pos)); + // route entry if entry_belongs_in_left_node(secret, btbe_entry, bit_pos)? { node = left; + node_id = left_id; bucket = left_bucket; + bucket_id = left_bucket_id; } else { node = right; + node_id = right_id; bucket = right_bucket; + bucket_id = right_bucket_id; } // increment bit position for next iteration of the loop diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 5a078b8f..00000000 --- a/src/config.rs +++ /dev/null @@ -1 +0,0 @@ -pub const DWB_CAPACITY: u16 = 4; \ No newline at end of file diff --git a/src/contract.rs b/src/contract.rs index dc80eaa3..dbde828c 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -10,7 +10,11 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{log_dwb, DelayedWriteBuffer, DWB, TX_NODES}; + +use crate::dwb::{DelayedWriteBuffer, DWB, TX_NODES}; +#[cfg(feature="gas_tracking")] +use crate::dwb::log_dwb; + use crate::gas_tracker::{GasTracker, LoggingExt}; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, @@ -97,6 +101,7 @@ pub fn instantiate( for balance in initial_balances { let amount = balance.amount.u128(); let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?; + let mut tracker = GasTracker::new(deps.api); perform_mint( deps.storage, &mut rng, @@ -105,7 +110,8 @@ pub fn instantiate( amount, msg.symbol.clone(), Some("Initial Balance".to_string()), - &env.block + &env.block, + &mut tracker, )?; if let Some(new_total_supply) = total_supply.checked_add(amount) { @@ -360,8 +366,10 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::ExchangeRate {} => query_exchange_rate(deps.storage), QueryMsg::Minters { .. } => query_minters(deps), QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query), - // FOR TESTING ONLY! REMOVE + + #[cfg(feature="gas_tracking")] QueryMsg::Dwb { } => log_dwb(deps.storage), + _ => viewing_keys_queries(deps, msg), }, RESPONSE_BLOCK_SIZE, @@ -823,12 +831,23 @@ fn try_mint_impl( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, + tracker: &mut GasTracker, ) -> StdResult<()> { let raw_amount = amount.u128(); let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; let raw_minter = deps.api.addr_canonicalize(minter.as_str())?; - perform_mint(deps.storage, rng, &raw_minter, &raw_recipient, raw_amount, denom, memo, block)?; + perform_mint( + deps.storage, + rng, + &raw_minter, + &raw_recipient, + raw_amount, + denom, + memo, + block, + tracker, + )?; Ok(()) } @@ -864,6 +883,8 @@ fn try_mint( let minted_amount = safe_add(&mut total_supply, amount.u128()); TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + let mut tracker: GasTracker = GasTracker::new(deps.api); + // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency try_mint_impl( &mut deps, @@ -874,9 +895,17 @@ fn try_mint( constants.symbol, memo, &env.block, + &mut tracker, )?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?)) + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); + + if cfg!(feature="gas_tracking") { + resp = tracker.add_to_response(resp); + } + + Ok(resp) } fn try_batch_mint( @@ -908,6 +937,9 @@ fn try_batch_mint( let actual_amount = safe_add(&mut total_supply, action.amount.u128()); let recipient = deps.api.addr_validate(action.recipient.as_str())?; + + let mut tracker: GasTracker = GasTracker::new(deps.api); + try_mint_impl( &mut deps, rng, @@ -917,6 +949,7 @@ fn try_batch_mint( constants.symbol.clone(), action.memo, &env.block, + &mut tracker, )?; } @@ -1090,9 +1123,26 @@ fn try_deposit( let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; - perform_deposit(deps.storage, rng, &sender_address, raw_amount, "uscrt".to_string(), &env.block)?; + let mut tracker: GasTracker = GasTracker::new(deps.api); + + perform_deposit( + deps.storage, + rng, + &sender_address, + raw_amount, + "uscrt".to_string(), + &env.block, + &mut tracker, + )?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?); + + if cfg!(feature="gas_tracking") { + resp = tracker.add_to_response(resp); + } - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?)) + Ok(resp) } fn try_redeem( @@ -1139,8 +1189,10 @@ fn try_redeem( // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; + let mut tracker = GasTracker::new(deps.api); + // settle the signer's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, &sender_address, tx_id, amount_raw, "redeem")?; + dwb.settle_sender_or_owner_account(deps.storage, &sender_address, tx_id, amount_raw, "redeem", &mut tracker)?; DWB.save(deps.storage, &dwb)?; @@ -1178,7 +1230,7 @@ fn try_redeem( } #[allow(clippy::too_many_arguments)] -fn try_transfer_impl<'a>( +fn try_transfer_impl( deps: &mut DepsMut, rng: &mut ContractPrng, sender: &Addr, @@ -1187,7 +1239,7 @@ fn try_transfer_impl<'a>( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, - tracker: &mut GasTracker<'a>, + tracker: &mut GasTracker, ) -> StdResult<()> { let raw_sender = deps.api.addr_canonicalize(sender.as_str())?; let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; @@ -1236,9 +1288,15 @@ fn try_transfer( &mut tracker )?; + #[cfg(feature="gas_tracking")] + let mut group1 = tracker.group("try_transfer.rest"); + let mut resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + #[cfg(feature="gas_tracking")] + group1.log("rest"); + if cfg!(feature="gas_tracking") { resp = tracker.add_to_response(resp); } @@ -1720,10 +1778,12 @@ fn try_burn_from( // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; + let mut tracker = GasTracker::new(deps.api); + // settle the owner's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, &raw_owner, tx_id, raw_amount, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_owner, tx_id, raw_amount, "burn", &mut tracker)?; if raw_burner != raw_owner { // also settle sender's account - dwb.settle_sender_or_owner_account(deps.storage, &raw_burner, tx_id, 0, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_burner, tx_id, 0, "burn", &mut tracker)?; } DWB.save(deps.storage, &dwb)?; @@ -1777,11 +1837,13 @@ fn try_batch_burn_from( // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; + + let mut tracker = GasTracker::new(deps.api); // settle the owner's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, &raw_owner, tx_id, amount, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_owner, tx_id, amount, "burn", &mut tracker)?; if raw_spender != raw_owner { - dwb.settle_sender_or_owner_account(deps.storage, &raw_spender, tx_id, 0, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_spender, tx_id, 0, "burn", &mut tracker)?; } DWB.save(deps.storage, &dwb)?; @@ -1985,8 +2047,10 @@ fn try_burn( // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; + let mut tracker = GasTracker::new(deps.api); + // settle the signer's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, &raw_burn_address, tx_id, raw_amount, "burn")?; + dwb.settle_sender_or_owner_account(deps.storage, &raw_burn_address, tx_id, raw_amount, "burn", &mut tracker)?; DWB.save(deps.storage, &dwb)?; @@ -2015,40 +2079,40 @@ fn perform_transfer( block: &BlockInfo, tracker: &mut GasTracker, ) -> StdResult<()> { - // #[cfg(feature="gas_tracking")] - let mut group1 = tracker.group("perform_transfer1"); + #[cfg(feature="gas_tracking")] + let mut group1 = tracker.group("perform_transfer.1"); // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_transfer_action(store, from, sender, to, amount, denom, memo, block)?; - // #[cfg(feature="gas_tracking")] - group1.log("store_transfer_action"); + #[cfg(feature="gas_tracking")] + group1.log("@store_transfer_action"); // load delayed write buffer let mut dwb = DWB.load(store)?; - // #[cfg(feature="gas_tracking")] + #[cfg(feature="gas_tracking")] group1.log("DWB.load"); let transfer_str = "transfer"; // settle the owner's account - dwb.settle_sender_or_owner_account(store, from, tx_id, amount, transfer_str)?; + dwb.settle_sender_or_owner_account(store, from, tx_id, amount, transfer_str, tracker)?; // if this is a *_from action, settle the sender's account, too if sender != from { - dwb.settle_sender_or_owner_account(store, sender, tx_id, 0, transfer_str)?; + dwb.settle_sender_or_owner_account(store, sender, tx_id, 0, transfer_str, tracker)?; } // add the tx info for the recipient to the buffer - dwb.add_recipient(store, rng, to, tx_id, amount)?; + dwb.add_recipient(store, rng, to, tx_id, amount, tracker)?; - // #[cfg(feature="gas_tracking")] - let mut group2 = tracker.group("perform_transfer2"); + #[cfg(feature="gas_tracking")] + let mut group2 = tracker.group("perform_transfer.2"); DWB.save(store, &dwb)?; - // #[cfg(feature="gas_tracking")] + #[cfg(feature="gas_tracking")] group2.log("DWB.save"); Ok(()) @@ -2063,6 +2127,7 @@ fn perform_mint( denom: String, memo: Option, block: &BlockInfo, + tracker: &mut GasTracker, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_mint_action(store, minter, to, amount, denom, memo, block)?; @@ -2072,11 +2137,11 @@ fn perform_mint( // if minter is not recipient, settle them if minter != to { - dwb.settle_sender_or_owner_account(store, minter, tx_id, 0, "mint")?; + dwb.settle_sender_or_owner_account(store, minter, tx_id, 0, "mint", tracker)?; } // add the tx info for the recipient to the buffer - dwb.add_recipient(store, rng, to, tx_id, amount)?; + dwb.add_recipient(store, rng, to, tx_id, amount, tracker)?; DWB.save(store, &dwb)?; @@ -2090,6 +2155,7 @@ fn perform_deposit( amount: u128, denom: String, block: &BlockInfo, + tracker: &mut GasTracker, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_deposit_action(store, amount, denom, block)?; @@ -2098,7 +2164,7 @@ fn perform_deposit( let mut dwb = DWB.load(store)?; // add the tx info for the recipient to the buffer - dwb.add_recipient(store, rng, to, tx_id, amount)?; + dwb.add_recipient(store, rng, to, tx_id, amount, tracker)?; DWB.save(store, &dwb)?; diff --git a/src/dwb.rs b/src/dwb.rs index 25f09462..587f7d0d 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,3 +1,4 @@ +use std::env::{var}; use constant_time_eq::constant_time_eq; use rand::RngCore; use secret_toolkit_crypto::ContractPrng; @@ -6,11 +7,14 @@ use serde_big_array::BigArray; use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, Storage}; use secret_toolkit::storage::Item; +use crate::gas_tracker::GasTracker; use crate::msg::QueryAnswer; use crate::state::{safe_add, safe_add_u64,}; use crate::btbe::{merge_dwb_entry, stored_balance}; use crate::transaction_history::{Tx, TRANSACTIONS}; +include!(concat!(env!("OUT_DIR"), "/config.rs")); + pub const KEY_DWB: &[u8] = b"dwb"; pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt"; pub const KEY_TX_NODES: &[u8] = b"dwb-tx-nodes"; @@ -31,9 +35,9 @@ fn store_new_tx_node(store: &mut dyn Storage, tx_node: TxNode) -> StdResult Ok(tx_nodes_serial_id) } -// 64 entries + 1 "dummy" entry prepended (idx: 0 in DelayedWriteBufferEntry array) +// n entries + 1 "dummy" entry prepended (idx: 0 in DelayedWriteBufferEntry array) // minimum allowable size: 3 -pub const DWB_LEN: u16 = 65; +pub const DWB_LEN: u16 = DWB_CAPACITY + 1; // maximum number of tx events allowed in an entry's linked list pub const DWB_MAX_TX_EVENTS: u16 = u16::MAX; @@ -80,32 +84,6 @@ impl DelayedWriteBuffer { }) } - /// settles an entry at a given index in the buffer - #[inline] - fn settle_entry( - &self, - store: &mut dyn Storage, - index: usize, - ) -> StdResult<()> { - merge_dwb_entry(store, self.entries[index], None) -/* - let account = entry.recipient()?; - - AccountTxsStore::append_bundle( - store, - &account, - entry.head_node()?, - entry.list_len()?, - )?; - - // get the address' stored balance - let mut balance = BalancesStore::load(store, &account); - safe_add(&mut balance, entry.amount()? as u128); - // add the amount from entry to the stored balance - BalancesStore::save(store, &account, balance) -*/ - } - /// settles a participant's account who may or may not have an entry in the buffer /// gets balance including any amount in the buffer, and then subtracts amount spent in this tx pub fn settle_sender_or_owner_account( @@ -115,13 +93,20 @@ impl DelayedWriteBuffer { tx_id: u64, amount_spent: u128, op_name: &str, + tracker: &mut GasTracker, ) -> StdResult<()> { + #[cfg(feature="gas_tracking")] + let mut group1 = tracker.group("settle_sender_or_owner_account.1"); + // release the address from the buffer let (balance, mut dwb_entry) = self.constant_time_release( store, address )?; + #[cfg(feature="gas_tracking")] + group1.log("constant_time_release"); + if balance.checked_sub(amount_spent).is_none() { return Err(StdError::generic_err(format!( "insufficient funds to {op_name}: balance={balance}, required={amount_spent}", @@ -130,21 +115,21 @@ impl DelayedWriteBuffer { dwb_entry.add_tx_node(store, tx_id)?; - merge_dwb_entry(store, dwb_entry, Some(amount_spent)) -/* - let head_node = entry.add_tx_node(store, tx_id)?; + #[cfg(feature="gas_tracking")] + group1.log("add_tx_node"); - AccountTxsStore::append_bundle( - store, - address, - head_node, - entry.list_len()?, - )?; - - BalancesStore::save(store, address, new_balance)?; - - Ok(()) -*/ + let mut entry = dwb_entry.clone(); + entry.set_recipient(address)?; + + #[cfg(feature="gas_tracking")] + group1.logf(format!("@entry=address:{}, amount:{}", entry.recipient()?, entry.amount()?)); + + let result = merge_dwb_entry(store, entry, Some(amount_spent), tracker); + + // #[cfg(feature="gas_tracking")] + // group2.log("merge_dwb_entry"); + + result } /// "releases" a given recipient from the buffer, removing their entry if one exists, in constant-time @@ -197,32 +182,53 @@ impl DelayedWriteBuffer { matched_index } - pub fn add_recipient( + pub fn add_recipient<'a>( &mut self, store: &mut dyn Storage, rng: &mut ContractPrng, recipient: &CanonicalAddr, tx_id: u64, - amount: u128 + amount: u128, + tracker: &mut GasTracker<'a>, ) -> StdResult<()> { + #[cfg(feature="gas_tracking")] + let mut group1 = tracker.group("add_recipient.1"); + // check if `recipient` is already a recipient in the delayed write buffer let recipient_index = self.recipient_match(recipient); + #[cfg(feature="gas_tracking")] + group1.log("recipient_match"); // the new entry will either derive from a prior entry for the recipient or the dummy entry let mut new_entry = self.entries[recipient_index].clone(); + new_entry.set_recipient(recipient)?; + #[cfg(feature="gas_tracking")] + group1.log("set_recipient"); + new_entry.add_tx_node(store, tx_id)?; + #[cfg(feature="gas_tracking")] + group1.log("add_tx_node"); + new_entry.add_amount(amount)?; + #[cfg(feature="gas_tracking")] + group1.log("add_amount"); // whether or not recipient is in the buffer (non-zero index) // casting to i32 will never overflow, so long as dwb length is limited to a u16 value let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32); + #[cfg(feature="gas_tracking")] + group1.logf(format!("@if_recipient_in_buffer: {}", if_recipient_in_buffer)); // whether or not the buffer is fully saturated yet let if_undersaturated = constant_time_is_not_zero(self.empty_space_counter as i32); + #[cfg(feature="gas_tracking")] + group1.logf(format!("@if_undersaturated: {}", if_undersaturated)); // find the next empty entry in the buffer let next_empty_index = (DWB_LEN - self.empty_space_counter) as usize; + #[cfg(feature="gas_tracking")] + group1.logf(format!("@next_empty_index: {}", next_empty_index)); // which entry to settle (not yet considering if recipient's entry has capacity in history list) // if recipient is in buffer or buffer is undersaturated then settle the dummy entry @@ -231,12 +237,18 @@ impl DelayedWriteBuffer { if_recipient_in_buffer, 0, constant_time_if_else(if_undersaturated, 0, random_in_range(rng, 1, DWB_LEN as u32)? as usize)); + #[cfg(feature="gas_tracking")] + group1.logf(format!("@presumptive_settle_index: {}", presumptive_settle_index)); // check if we have any open slots in the linked list let if_list_can_grow = constant_time_is_not_zero((DWB_MAX_TX_EVENTS - self.entries[recipient_index].list_len()?) as i32); + #[cfg(feature="gas_tracking")] + group1.logf(format!("@if_list_can_grow: {}", if_list_can_grow)); // if we would overflow the list by updating the existing entry, then just settle that recipient let actual_settle_index = constant_time_if_else(if_list_can_grow, presumptive_settle_index, recipient_index); + #[cfg(feature="gas_tracking")] + group1.logf(format!("@actual_settle_index: {}", actual_settle_index)); // where to write the new/replacement entry // if recipient is in buffer then update it @@ -246,9 +258,18 @@ impl DelayedWriteBuffer { if_recipient_in_buffer, recipient_index, constant_time_if_else(if_undersaturated, next_empty_index, actual_settle_index)); + #[cfg(feature="gas_tracking")] + group1.logf(format!("@write_index: {}", write_index)); // settle the entry - self.settle_entry(store, actual_settle_index)?; + let dwb_entry = self.entries[actual_settle_index]; + merge_dwb_entry(store, dwb_entry, None, tracker)?; + + #[cfg(feature="gas_tracking")] + let mut group2 = tracker.group("add_recipient.2"); + + #[cfg(feature="gas_tracking")] + group2.log("merge_dwb_entry"); // write the new entry, which either overwrites the existing one for the same recipient, // replaces a randomly settled one, or inserts into an "empty" slot in the buffer @@ -260,6 +281,8 @@ impl DelayedWriteBuffer { constant_time_if_else(if_recipient_in_buffer, 0, 1), 0 ) as u16; + #[cfg(feature="gas_tracking")] + group2.logf(format!("@empty_space_counter: {}", self.empty_space_counter)); Ok(()) } @@ -496,7 +519,7 @@ fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { (then * condition as usize) | (els * (1 - condition as usize)) } -/// FOR TESTING ONLY! REMOVE +#[cfg(feature="gas_tracking")] pub fn log_dwb(storage: &dyn Storage) -> StdResult { let dwb = DWB.load(storage)?; to_binary(&QueryAnswer::Dwb { dwb: format!("{:?}", dwb) }) diff --git a/src/gas_tracker.rs b/src/gas_tracker.rs index 48c3a3ee..ec5cf7d3 100644 --- a/src/gas_tracker.rs +++ b/src/gas_tracker.rs @@ -81,4 +81,8 @@ impl<'a, 'b> GasGroup<'a, 'b> { self.tracker.logs.push(log_entry); self.index += 1; } + + pub fn logf(&mut self, comment: String) { + self.log(comment.as_str()) + } } \ No newline at end of file diff --git a/src/msg.rs b/src/msg.rs index dd23e3d2..d5e8ecb6 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -382,7 +382,7 @@ pub enum QueryMsg { query: QueryWithPermit, }, - /// FOR TESTING ONLY! REMOVE + #[cfg(feature="gas_tracking")] Dwb { }, } @@ -508,7 +508,7 @@ pub enum QueryAnswer { minters: Vec, }, - /// FOR TESTING ONLY! REMOVE + #[cfg(feature="gas_tracking")] Dwb { dwb: String, }, diff --git a/tests/dwb/bun.lockb b/tests/dwb/bun.lockb index 0aebc0e6d936a91f9be007e2f4bd00d73ffabe23..4af0d1fd9004eb82497ddff8852b2727c9b9859f 100755 GIT binary patch delta 19520 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 vyRcLlH { const [atu8_raw, atu8_signdoc, si_txn] = await create_and_sign_tx_direct( @@ -25,7 +27,7 @@ export async function exec(k_wallet: Wallet, atu8_msg: EncodedGoogleProtobufAny, xg_gas_limit ); - return await broadcast_result(k_wallet, atu8_raw, si_txn); + return await broadcast_result(k_wallet, atu8_raw, si_txn, k_tef); } export async function upload_code(k_wallet: Wallet, atu8_wasm: Uint8Array): Promise { @@ -56,7 +58,7 @@ export async function upload_code(k_wallet: Wallet, atu8_wasm: Uint8Array): Prom k_wallet.addr, atu8_bytecode ) - ), 30_000_000n); + ), 30_000000n); if(xc_code) throw Error(sx_res); @@ -65,8 +67,8 @@ export async function upload_code(k_wallet: Wallet, atu8_wasm: Uint8Array): Prom export async function instantiate_contract(k_wallet: Wallet, sg_code_id: WeakUintStr, h_init_msg: JsonObject): Promise { const [,, g_reg] = await querySecretRegistrationTxKey(P_LOCALSECRET_LCD); - const [atu8_cons_pk] = destructSecretRegistrationKey(g_reg); - const k_wasm = await SecretWasm(atu8_cons_pk); + const [atu8_cons_pk] = destructSecretRegistrationKey(g_reg!); + const k_wasm = SecretWasm(atu8_cons_pk!); const [,, g_hash] = await querySecretComputeCodeHashByCodeId(P_LOCALSECRET_LCD, sg_code_id); // @ts-expect-error imported types versioning diff --git a/tests/dwb/src/dwb.ts b/tests/dwb/src/dwb.ts index 51c4eca8..bd1e112c 100644 --- a/tests/dwb/src/dwb.ts +++ b/tests/dwb/src/dwb.ts @@ -63,7 +63,7 @@ export class DwbValidator { const { empty_space_counter: sg_empty, entries: a_entries, - } = parse_dwb_dump(g_dwb_res!.dwb as string); + } = parse_dwb_dump((g_dwb_res as {dwb: string}).dwb); // update cached entries this._a_entries.length = 0; @@ -172,77 +172,77 @@ export class DwbValidator { } -const g_dwb = parse_dwb_dump(` - DelayedWriteBuffer { -empty_space_counter: 61, - entries: [ - DelayedWriteBufferEntry([30, 64, 27, 13, 80, 9, 191, 112, 225, 11, 76, 117, 251, 233, 171, 52, 62, 116, 221, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([252, 120, 243, 61, 153, 55, 155, 238, 217, 219, 75, 240, 232, 43, 128, 39, 177, 94, 70, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([78, 34, 145, 19, 199, 90, 194, 255, 187, 156, 147, 189, 154, 40, 119, 128, 77, 51, 242, 84, 0, 0, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, 3, 0, 1]), - DelayedWriteBufferEntry([236, 133, 74, 220, 71, 232, 157, 194, 70, 160, 113, 10, 155, 74, 105, 192, 216, 151, 180, 80, 0, 0, 0, 0, 0, 30, 132, 128, 0, 0, 0, 0, 7, 0, 2]), - DelayedWriteBufferEntry([171, 152, 150, 130, 223, 89, 19, 108, 106, 73, 34, 29, 160, 38, 68, 217, 164, 90, 53, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ] -} -`); - -console.log(g_dwb.entries.length); +// const g_dwb = parse_dwb_dump(` +// DelayedWriteBuffer { +// empty_space_counter: 61, +// entries: [ +// DelayedWriteBufferEntry([30, 64, 27, 13, 80, 9, 191, 112, 225, 11, 76, 117, 251, 233, 171, 52, 62, 116, 221, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([252, 120, 243, 61, 153, 55, 155, 238, 217, 219, 75, 240, 232, 43, 128, 39, 177, 94, 70, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([78, 34, 145, 19, 199, 90, 194, 255, 187, 156, 147, 189, 154, 40, 119, 128, 77, 51, 242, 84, 0, 0, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, 3, 0, 1]), +// DelayedWriteBufferEntry([236, 133, 74, 220, 71, 232, 157, 194, 70, 160, 113, 10, 155, 74, 105, 192, 216, 151, 180, 80, 0, 0, 0, 0, 0, 30, 132, 128, 0, 0, 0, 0, 7, 0, 2]), +// DelayedWriteBufferEntry([171, 152, 150, 130, 223, 89, 19, 108, 106, 73, 34, 29, 160, 38, 68, 217, 164, 90, 53, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), +// DelayedWriteBufferEntry([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +// ] +// } +// `); + +// console.log(g_dwb.entries.length); diff --git a/tests/dwb/src/gas-checker.ts b/tests/dwb/src/gas-checker.ts index c5491e9b..774da151 100644 --- a/tests/dwb/src/gas-checker.ts +++ b/tests/dwb/src/gas-checker.ts @@ -1,18 +1,26 @@ import type {GroupedGasLogs} from './snip'; -import {entries} from '@blake.regalia/belt'; +import {entries, stringify_json} from '@blake.regalia/belt'; -import {SX_ANSI_GREEN, SX_ANSI_MAGENTA, SX_ANSI_RESET} from './helper'; +import {SX_ANSI_GREEN, SX_ANSI_RED, SX_ANSI_MAGENTA, SX_ANSI_RESET, SX_ANSI_YELLOW} from './helper'; + +const bigint_abs = (xg_a: bigint, xg_b=0n): bigint => xg_a > xg_b? xg_a - xg_b: xg_b - xg_a; + +const delta_color = (xg_delta: bigint, nl_pad=0) => (bigint_abs(xg_delta) >= 1n + ? bigint_abs(xg_delta) > 2n + ? SX_ANSI_RED + : SX_ANSI_YELLOW + : '')+((xg_delta > 0? '+': '')+xg_delta).padStart(nl_pad, ' ')+SX_ANSI_RESET; export class GasChecker { constructor(protected _h_baseline: GroupedGasLogs, protected _xg_used: bigint) {} - compare(h_local: GroupedGasLogs, xg_used: bigint) { + compare(h_local: GroupedGasLogs, xg_used: bigint): void { const {_h_baseline, _xg_used} = this; console.log(` ⚖️ Gas usage relative to baseline: ${xg_used === _xg_used ? `${SX_ANSI_GREEN}0` - : `${SX_ANSI_MAGENTA}${xg_used > _xg_used? '+': ''}${xg_used - _xg_used}` + : delta_color(xg_used - _xg_used) }${SX_ANSI_RESET}`); // each group @@ -29,19 +37,26 @@ export class GasChecker { // each log for(let i_log=1; i_log g.index === g_log_local.index)!; + const g_log_baseline = a_logs_baseline.find(g => g.index === i_local); - const xg_gap_baseline = g_log_baseline.gap; - const xg_gap_local = g_log_local.gap; + const xg_gap_baseline = g_log_baseline?.gap || 0n; // calculate delta const xg_delta = xg_gap_local - xg_gap_baseline; // non-zero delta - if(xg_delta) { - console.log(` ${si_group.slice(0, 20).padEnd(20, ' ')} │ ${((xg_delta > 0? '+': '')+xg_delta).padEnd(5, ' ')} │ ${g_log_local.comment}`); + if(xg_delta || '@' === s_comment_local[0]) { + console.log([ + ' '.repeat(8)+si_group.slice(0, 20).padEnd(20, ' '), + delta_color(xg_delta, 3), + ('@' === s_comment_local[0]? SX_ANSI_MAGENTA: '')+s_comment_local+SX_ANSI_RESET, + ].join(' │ ')); c_logs += 1; } } diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index 317b6b7c..439d33f4 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -2,11 +2,11 @@ import type {Snip24} from '@solar-republic/contractor'; import {readFileSync} from 'node:fs'; -import {bytes, bytes_to_base64, entries} from '@blake.regalia/belt'; -import {SecretApp, SecretContract, random_32} from '@solar-republic/neutrino'; +import {bytes, bytes_to_base64, entries, sha256, text_to_bytes} from '@blake.regalia/belt'; +import {SecretApp, SecretContract, Wallet, random_32} from '@solar-republic/neutrino'; import {BigNumber} from 'bignumber.js'; -import {N_DECIMALS, P_LOCALSECRET_LCD, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; +import {N_DECIMALS, P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; import {upload_code, instantiate_contract} from './contract'; import {DwbValidator} from './dwb'; import {GasChecker} from './gas-checker'; @@ -27,7 +27,7 @@ const sa_snip = await instantiate_contract(k_wallet_a, sg_code_id, { decimals: 6, admin: k_wallet_a.addr, initial_balances: entries({ - [k_wallet_a.addr]: 100_000000n, + [k_wallet_a.addr]: 10_000_000000n, }).map(([sa_account, xg_balance]) => ({ address: sa_account, amount: `${xg_balance}`, @@ -59,11 +59,12 @@ const H_APPS = { d: k_app_d, }; +// @ts-expect-error validator! const k_dwbv = new DwbValidator(k_app_a); console.log('# Initialized'); await k_dwbv.sync(); -await k_dwbv.print(); +k_dwbv.print(); console.log('\n'); async function transfer_chain(sx_chain: string) { @@ -78,6 +79,7 @@ async function transfer_chain(sx_chain: string) { console.log(sx_amount, si_from, si_to); + // @ts-expect-error secret app const g_result = await transfer(k_dwbv, xg_amount, H_APPS[si_from[0].toLowerCase()], H_APPS[si_to[0].toLowerCase()], k_checker); if(!k_checker) { @@ -96,15 +98,7 @@ async function transfer_chain(sx_chain: string) { 1 TKN David => Alice -- re-adds Alice to buffer; settles David for 1st time `); - /* - All operations should be same gas from now on - Alice: 93 - Bob: 0 - Carol: 1 - David: 4 - */ - - console.log('--- should all be same gas ---'); + console.log('#'.repeat(80)+'\n--- Subsequent operations should incur almost exactly the same gas now ---\n'+'#'.repeat(80)+'\n'); await transfer_chain(` 1 TKN David => Bob @@ -114,4 +108,26 @@ async function transfer_chain(sx_chain: string) { 1 TKN Alice => Carol 1 TKN Carol => Bob -- yet again `); + + + let k_checker: GasChecker | null = null; + + for(let s_r1='a'; s_r1<='z'; s_r1=String.fromCharCode(s_r1.charCodeAt(0)+1)) { + for(let s_r2='a'; s_r2<='z'; s_r2=String.fromCharCode(s_r2.charCodeAt(0)+1)) { + const si_receiver = s_r1+s_r2; + + const k_wallet = await Wallet(await sha256(text_to_bytes(si_receiver)), 'secretdev-1', P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, 'secret'); + + const k_app_receiver = SecretApp(k_wallet, k_contract, X_GAS_PRICE); + + console.log(`Alice --> ${si_receiver}`); + + // @ts-expect-error secret app + const g_result = await transfer(k_dwbv, 1_000000n, k_app_a, k_app_receiver, k_checker); + + if(!k_checker) { + k_checker = new GasChecker(g_result.tracking, g_result.gasUsed); + } + } + } } diff --git a/tests/dwb/src/snip.ts b/tests/dwb/src/snip.ts index 26c78c2b..a2b18bb0 100644 --- a/tests/dwb/src/snip.ts +++ b/tests/dwb/src/snip.ts @@ -2,14 +2,15 @@ import type {DwbValidator} from './dwb'; import type {GasChecker} from './gas-checker'; import type {Dict, Nilable} from '@blake.regalia/belt'; import type {SecretContractInterface} from '@solar-republic/contractor'; -import type {SecretApp} from '@solar-republic/neutrino'; +import type {SecretApp, WeakSecretAccAddr} from '@solar-republic/neutrino'; import type {CwUint128, SecretQueryPermit, WeakUintStr} from '@solar-republic/types'; -import {entries} from '@blake.regalia/belt'; +import {entries, is_bigint, stringify_json} from '@blake.regalia/belt'; +import {queryCosmosBankBalance} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/query'; import {sign_secret_query_permit} from '@solar-republic/neutrino'; import BigNumber from 'bignumber.js'; -import {H_ADDRS, N_DECIMALS} from './constants'; +import {H_ADDRS, N_DECIMALS, P_LOCALSECRET_LCD} from './constants'; import {fail} from './helper'; @@ -53,7 +54,12 @@ type TokenBalance = SecretContractInterface<{ }; }>; -export async function balance(k_app: SecretApp) { +export async function scrt_balance(sa_owner: WeakSecretAccAddr): Promise { + const [,, g_res] = await queryCosmosBankBalance(P_LOCALSECRET_LCD, sa_owner, 'uscrt'); + return BigInt(g_res?.balance?.amount || '0'); +} + +export async function snip_balance(k_app: SecretApp) { const g_permit = await sign_secret_query_permit(k_app.wallet, 'snip-balance', [k_app.contract.addr], ['balance']); return await k_app.query('balance', {}, g_permit as unknown as null); } @@ -68,36 +74,56 @@ export async function transfer( const sa_owner = k_app_owner.wallet.addr; const sa_recipient = k_app_recipient.wallet.addr; + // scrt balance of owner before transfer + // @ts-expect-error canonical addr + const xg_scrt_balance_owner_before = await scrt_balance(sa_owner); + // query balance of owner and recipient const [ [g_balance_owner_before], [g_balance_recipient_before], ] = await Promise.all([ - balance(k_app_owner), - balance(k_app_recipient), + // @ts-expect-error secret app + snip_balance(k_app_owner), + // @ts-expect-error secret app + snip_balance(k_app_recipient), ]); // execute transfer const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_owner.exec('transfer', { amount: `${xg_amount}` as CwUint128, recipient: sa_recipient, - }, 100_000n); - - if(xc_code) { - throw Error(sx_res); - } + }, 1000_000n); - // console.log(h_events); + // section header + console.log(`# Transfer ${BigNumber(xg_amount+'').shiftedBy(-N_DECIMALS).toFixed()} TKN ${H_ADDRS[sa_owner] || sa_owner} => ${H_ADDRS[sa_recipient] || sa_recipient} | ⏹ ${k_dwbv.empty} spaces | ⛽️ ${g_meta?.gas_used || '0'} gas used`); // query balance of owner and recipient again const [ [g_balance_owner_after], [g_balance_recipient_after], ] = await Promise.all([ - balance(k_app_owner), - balance(k_app_recipient), + // @ts-expect-error secret app + snip_balance(k_app_owner), + // @ts-expect-error secret app + snip_balance(k_app_recipient), ]); + if(xc_code) { + console.warn('Diagnostics', { + scrt_balance_before: xg_scrt_balance_owner_before, + // @ts-expect-error canonical addr + scrt_balance_after: await scrt_balance(sa_owner), + snip_balance_before: g_balance_owner_before?.amount, + snip_balance_after: g_balance_owner_after?.amount, + meta: stringify_json(g_meta), + events: h_events, + exec: g_exec, + }); + + throw Error(`Failed to execute transfer from ${k_app_owner.wallet.addr} [${xc_code}]: ${sx_res}`); + } + // sync the buffer await k_dwbv.sync(); @@ -105,12 +131,11 @@ export async function transfer( // const sg_gas_used = g_meta?.gas_used; // console.log(` ⏹ ${k_dwbv.empty} spaces`); - // section header - console.log(`# Transfer ${BigNumber(xg_amount+'').shiftedBy(-N_DECIMALS).toFixed()} TKN ${H_ADDRS[sa_owner] || sa_owner} => ${H_ADDRS[sa_recipient]} | ⏹ ${k_dwbv.empty} spaces | ⛽️ ${g_meta!.gas_used} gas used`); + // console.log(stringify_json(h_events, null, ' ')); const h_tracking: GroupedGasLogs = {}; for(const [si_key, a_values] of entries(h_events!)) { - const m_key = /^wasm\.gas\.(\w+)$/.exec(si_key); + const m_key = /^wasm\.gas\.(.+)$/.exec(si_key); if(m_key) { const [, si_group] = m_key; @@ -136,8 +161,6 @@ export async function transfer( } } - // console.log(h_tracking); - if(k_checker) { k_checker.compare(h_tracking, BigInt(g_meta!.gas_used)); } @@ -154,13 +177,13 @@ export async function transfer( throw fail(`Failed to fetch balances`); } - // expect exact amount difference + // expect exact amount difference for owner const xg_owner_loss = BigInt(g_balance_owner_before.amount as string) - BigInt(g_balance_owner_after.amount); if(xg_owner_loss !== xg_amount) { fail(`Owner's balance changed by ${-xg_owner_loss}, but the amount sent was ${xg_amount}`); } - // expect exact amount difference + // expect exact amount difference for recipient const xg_recipient_gain = BigInt(g_balance_recipient_after.amount) - BigInt(g_balance_recipient_before.amount); if(xg_recipient_gain !== xg_amount) { fail(`Recipient's balance changed by ${xg_recipient_gain}, but the amount sent was ${xg_amount}`); diff --git a/tests/dwb/tsconfig.json b/tests/dwb/tsconfig.json index 6107ae94..79f379b2 100644 --- a/tests/dwb/tsconfig.json +++ b/tests/dwb/tsconfig.json @@ -5,5 +5,6 @@ "compilerOptions": { "moduleResolution": "Bundler", + "outDir": "dist", }, } \ No newline at end of file diff --git a/tests/dwb/tsconfig.tsc-esm-fix.json b/tests/dwb/tsconfig.tsc-esm-fix.json new file mode 100644 index 00000000..64db3f6f --- /dev/null +++ b/tests/dwb/tsconfig.tsc-esm-fix.json @@ -0,0 +1,7 @@ +{ + "extends": "node_modules/@blake.regalia/tsconfig/tsconfig.node.json", + "compilerOptions": { + "moduleResolution": "Bundler", + "outDir": "dist", + }, +} \ No newline at end of file From 8e2fc977b9d9ff683eca0fb01fa7e062676ebd89 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Fri, 2 Aug 2024 06:17:20 +0000 Subject: [PATCH 49/87] style: x cfg macro --- src/contract.rs | 25 ++++++++++--------------- src/dwb.rs | 11 ++++------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index dbde828c..54ffa683 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -901,9 +901,8 @@ fn try_mint( let mut resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); - if cfg!(feature="gas_tracking") { - resp = tracker.add_to_response(resp); - } + #[cfg(feature="gas_tracking")] + resp.add_gas_tracker(tracker); Ok(resp) } @@ -1138,9 +1137,8 @@ fn try_deposit( let mut resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?); - if cfg!(feature="gas_tracking") { - resp = tracker.add_to_response(resp); - } + #[cfg(feature="gas_tracking")] + resp.add_gas_tracker(tracker); Ok(resp) } @@ -1297,9 +1295,8 @@ fn try_transfer( #[cfg(feature="gas_tracking")] group1.log("rest"); - if cfg!(feature="gas_tracking") { - resp = tracker.add_to_response(resp); - } + #[cfg(feature="gas_tracking")] + resp.add_gas_tracker(tracker); Ok(resp) } @@ -1333,9 +1330,8 @@ fn try_batch_transfer( let mut resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); - if cfg!(feature="gas_tracking") { - resp = tracker.add_to_response(resp); - } + #[cfg(feature="gas_tracking")] + resp.add_gas_tracker(tracker); Ok(resp) } @@ -1450,9 +1446,8 @@ fn try_send( .add_messages(messages) .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); - if cfg!(feature="gas_tracking") { - resp = tracker.add_to_response(resp); - } + #[cfg(feature="gas_tracking")] + resp.add_gas_tracker(tracker); Ok(resp) } diff --git a/src/dwb.rs b/src/dwb.rs index 587f7d0d..2a26c00e 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -99,13 +99,13 @@ impl DelayedWriteBuffer { let mut group1 = tracker.group("settle_sender_or_owner_account.1"); // release the address from the buffer - let (balance, mut dwb_entry) = self.constant_time_release( + let (balance, mut dwb_entry) = self.release_dwb_recipient( store, address )?; #[cfg(feature="gas_tracking")] - group1.log("constant_time_release"); + group1.log("release_dwb_recipient"); if balance.checked_sub(amount_spent).is_none() { return Err(StdError::generic_err(format!( @@ -126,15 +126,12 @@ impl DelayedWriteBuffer { let result = merge_dwb_entry(store, entry, Some(amount_spent), tracker); - // #[cfg(feature="gas_tracking")] - // group2.log("merge_dwb_entry"); - result } - /// "releases" a given recipient from the buffer, removing their entry if one exists, in constant-time + /// "releases" a given recipient from the buffer, removing their entry if one exists /// returns the new balance and the buffer entry - fn constant_time_release( + fn release_dwb_recipient( &mut self, store: &mut dyn Storage, address: &CanonicalAddr From df34e046a57b4d79e561d6ff47fe31d2269618b9 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 2 Aug 2024 19:06:51 +1200 Subject: [PATCH 50/87] all tracker code using cfg(gas_tracking) --- Makefile | 2 +- src/btbe.rs | 1 + src/contract.rs | 160 +++++++++++++++++++++++++++++++++++++++++++----- src/dwb.rs | 18 +++++- 4 files changed, 161 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index bd27390a..94933a74 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ compile-optimized: _compile-optimized contract.wasm.gz _compile-optimized: RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown @# The following line is not necessary, may work only on linux (extra size optimization) - wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm + wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm .PHONY: compile-optimized-reproducible compile-optimized-reproducible: diff --git a/src/btbe.rs b/src/btbe.rs index 75fcbd1e..80ae03c1 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -439,6 +439,7 @@ pub fn merge_dwb_entry( storage: &mut dyn Storage, dwb_entry: DelayedWriteBufferEntry, amount_spent: Option, + #[cfg(feature="gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { #[cfg(feature="gas_tracking")] diff --git a/src/contract.rs b/src/contract.rs index 54ffa683..aa2e0d5d 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -101,6 +101,7 @@ pub fn instantiate( for balance in initial_balances { let amount = balance.amount.u128(); let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?; + #[cfg(feature="gas_tracking")] let mut tracker = GasTracker::new(deps.api); perform_mint( deps.storage, @@ -111,6 +112,7 @@ pub fn instantiate( msg.symbol.clone(), Some("Initial Balance".to_string()), &env.block, + #[cfg(feature="gas_tracking")] &mut tracker, )?; @@ -831,6 +833,7 @@ fn try_mint_impl( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, + #[cfg(feature="gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { let raw_amount = amount.u128(); @@ -846,6 +849,7 @@ fn try_mint_impl( denom, memo, block, + #[cfg(feature="gas_tracking")] tracker, )?; @@ -883,6 +887,7 @@ fn try_mint( let minted_amount = safe_add(&mut total_supply, amount.u128()); TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + #[cfg(feature="gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency @@ -895,6 +900,7 @@ fn try_mint( constants.symbol, memo, &env.block, + #[cfg(feature="gas_tracking")] &mut tracker, )?; @@ -937,6 +943,7 @@ fn try_batch_mint( let recipient = deps.api.addr_validate(action.recipient.as_str())?; + #[cfg(feature="gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); try_mint_impl( @@ -948,6 +955,7 @@ fn try_batch_mint( constants.symbol.clone(), action.memo, &env.block, + #[cfg(feature="gas_tracking")] &mut tracker, )?; } @@ -1122,6 +1130,7 @@ fn try_deposit( let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; + #[cfg(feature="gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); perform_deposit( @@ -1131,6 +1140,7 @@ fn try_deposit( raw_amount, "uscrt".to_string(), &env.block, + #[cfg(feature="gas_tracking")] &mut tracker, )?; @@ -1190,7 +1200,15 @@ fn try_redeem( let mut tracker = GasTracker::new(deps.api); // settle the signer's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, &sender_address, tx_id, amount_raw, "redeem", &mut tracker)?; + dwb.settle_sender_or_owner_account( + deps.storage, + &sender_address, + tx_id, + amount_raw, + "redeem", + #[cfg(feature="gas_tracking")] + &mut tracker, + )?; DWB.save(deps.storage, &dwb)?; @@ -1237,6 +1255,7 @@ fn try_transfer_impl( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, + #[cfg(feature="gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { let raw_sender = deps.api.addr_canonicalize(sender.as_str())?; @@ -1252,7 +1271,8 @@ fn try_transfer_impl( denom, memo, block, - tracker + #[cfg(feature="gas_tracking")] + tracker, )?; Ok(()) @@ -1272,6 +1292,7 @@ fn try_transfer( let symbol = CONFIG.load(deps.storage)?.symbol; + #[cfg(feature="gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); try_transfer_impl( @@ -1283,7 +1304,8 @@ fn try_transfer( symbol, memo, &env.block, - &mut tracker + #[cfg(feature="gas_tracking")] + &mut tracker, )?; #[cfg(feature="gas_tracking")] @@ -1310,6 +1332,7 @@ fn try_batch_transfer( ) -> StdResult { let symbol = CONFIG.load(deps.storage)?.symbol; + #[cfg(feature="gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); for action in actions { @@ -1323,7 +1346,8 @@ fn try_batch_transfer( symbol.clone(), action.memo, &env.block, - &mut tracker + #[cfg(feature="gas_tracking")] + &mut tracker, )?; } @@ -1379,7 +1403,8 @@ fn try_send_impl( memo: Option, msg: Option, block: &cosmwasm_std::BlockInfo, - tracker: &mut GasTracker + #[cfg(feature="gas_tracking")] + tracker: &mut GasTracker, ) -> StdResult<()> { try_transfer_impl( deps, @@ -1390,6 +1415,7 @@ fn try_send_impl( denom, memo.clone(), block, + #[cfg(feature="gas_tracking")] tracker, )?; @@ -1425,6 +1451,7 @@ fn try_send( let mut messages = vec![]; let symbol = CONFIG.load(deps.storage)?.symbol; + #[cfg(feature="gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); try_send_impl( @@ -1439,7 +1466,8 @@ fn try_send( memo, msg, &env.block, - &mut tracker + #[cfg(feature="gas_tracking")] + &mut tracker, )?; let mut resp = Response::new() @@ -1462,6 +1490,7 @@ fn try_batch_send( let mut messages = vec![]; let symbol = CONFIG.load(deps.storage)?.symbol; + #[cfg(feature="gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); for action in actions { @@ -1478,6 +1507,7 @@ fn try_batch_send( action.memo, action.msg, &env.block, + #[cfg(feature="gas_tracking")] &mut tracker, )?; } @@ -1548,6 +1578,7 @@ fn try_transfer_from_impl( use_allowance(deps.storage, env, owner, spender, raw_amount)?; + #[cfg(feature="gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); perform_transfer( @@ -1560,6 +1591,7 @@ fn try_transfer_from_impl( denom, memo, &env.block, + #[cfg(feature="gas_tracking")] &mut tracker, )?; @@ -1773,12 +1805,29 @@ fn try_burn_from( // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; + #[cfg(feature="gas_tracking")] let mut tracker = GasTracker::new(deps.api); // settle the owner's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, &raw_owner, tx_id, raw_amount, "burn", &mut tracker)?; + dwb.settle_sender_or_owner_account( + deps.storage, + &raw_owner, + tx_id, + raw_amount, + "burn", + #[cfg(feature="gas_tracking")] + &mut tracker, + )?; if raw_burner != raw_owner { // also settle sender's account - dwb.settle_sender_or_owner_account(deps.storage, &raw_burner, tx_id, 0, "burn", &mut tracker)?; + dwb.settle_sender_or_owner_account( + deps.storage, + &raw_burner, + tx_id, + 0, + "burn", + #[cfg(feature="gas_tracking")] + &mut tracker, + )?; } DWB.save(deps.storage, &dwb)?; @@ -1833,12 +1882,29 @@ fn try_batch_burn_from( // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; + #[cfg(feature="gas_tracking")] let mut tracker = GasTracker::new(deps.api); // settle the owner's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, &raw_owner, tx_id, amount, "burn", &mut tracker)?; + dwb.settle_sender_or_owner_account( + deps.storage, + &raw_owner, + tx_id, + amount, + "burn", + #[cfg(feature="gas_tracking")] + &mut tracker, + )?; if raw_spender != raw_owner { - dwb.settle_sender_or_owner_account(deps.storage, &raw_spender, tx_id, 0, "burn", &mut tracker)?; + dwb.settle_sender_or_owner_account( + deps.storage, + &raw_spender, + tx_id, + 0, + "burn", + #[cfg(feature="gas_tracking")] + &mut tracker, + )?; } DWB.save(deps.storage, &dwb)?; @@ -2042,10 +2108,19 @@ fn try_burn( // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; + #[cfg(feature="gas_tracking")] let mut tracker = GasTracker::new(deps.api); // settle the signer's account in buffer - dwb.settle_sender_or_owner_account(deps.storage, &raw_burn_address, tx_id, raw_amount, "burn", &mut tracker)?; + dwb.settle_sender_or_owner_account( + deps.storage, + &raw_burn_address, + tx_id, + raw_amount, + "burn", + #[cfg(feature="gas_tracking")] + &mut tracker, + )?; DWB.save(deps.storage, &dwb)?; @@ -2072,6 +2147,7 @@ fn perform_transfer( denom: String, memo: Option, block: &BlockInfo, + #[cfg(feature="gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { #[cfg(feature="gas_tracking")] @@ -2092,15 +2168,39 @@ fn perform_transfer( let transfer_str = "transfer"; // settle the owner's account - dwb.settle_sender_or_owner_account(store, from, tx_id, amount, transfer_str, tracker)?; + dwb.settle_sender_or_owner_account( + store, + from, + tx_id, + amount, + transfer_str, + #[cfg(feature="gas_tracking")] + tracker, + )?; // if this is a *_from action, settle the sender's account, too if sender != from { - dwb.settle_sender_or_owner_account(store, sender, tx_id, 0, transfer_str, tracker)?; + dwb.settle_sender_or_owner_account( + store, + sender, + tx_id, + 0, + transfer_str, + #[cfg(feature="gas_tracking")] + tracker, + )?; } // add the tx info for the recipient to the buffer - dwb.add_recipient(store, rng, to, tx_id, amount, tracker)?; + dwb.add_recipient( + store, + rng, + to, + tx_id, + amount, + #[cfg(feature="gas_tracking")] + tracker, + )?; #[cfg(feature="gas_tracking")] let mut group2 = tracker.group("perform_transfer.2"); @@ -2122,6 +2222,7 @@ fn perform_mint( denom: String, memo: Option, block: &BlockInfo, + #[cfg(feature="gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id @@ -2132,11 +2233,27 @@ fn perform_mint( // if minter is not recipient, settle them if minter != to { - dwb.settle_sender_or_owner_account(store, minter, tx_id, 0, "mint", tracker)?; + dwb.settle_sender_or_owner_account( + store, + minter, + tx_id, + 0, + "mint", + #[cfg(feature="gas_tracking")] + tracker, + )?; } // add the tx info for the recipient to the buffer - dwb.add_recipient(store, rng, to, tx_id, amount, tracker)?; + dwb.add_recipient( + store, + rng, + to, + tx_id, + amount, + #[cfg(feature="gas_tracking")] + tracker, + )?; DWB.save(store, &dwb)?; @@ -2150,6 +2267,7 @@ fn perform_deposit( amount: u128, denom: String, block: &BlockInfo, + #[cfg(feature="gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id @@ -2159,7 +2277,15 @@ fn perform_deposit( let mut dwb = DWB.load(store)?; // add the tx info for the recipient to the buffer - dwb.add_recipient(store, rng, to, tx_id, amount, tracker)?; + dwb.add_recipient( + store, + rng, + to, + tx_id, + amount, + #[cfg(feature="gas_tracking")] + tracker, + )?; DWB.save(store, &dwb)?; diff --git a/src/dwb.rs b/src/dwb.rs index 2a26c00e..d2ae91aa 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -93,6 +93,7 @@ impl DelayedWriteBuffer { tx_id: u64, amount_spent: u128, op_name: &str, + #[cfg(feature="gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { #[cfg(feature="gas_tracking")] @@ -124,7 +125,13 @@ impl DelayedWriteBuffer { #[cfg(feature="gas_tracking")] group1.logf(format!("@entry=address:{}, amount:{}", entry.recipient()?, entry.amount()?)); - let result = merge_dwb_entry(store, entry, Some(amount_spent), tracker); + let result = merge_dwb_entry( + store, + entry, + Some(amount_spent), + #[cfg(feature="gas_tracking")] + tracker + ); result } @@ -186,6 +193,7 @@ impl DelayedWriteBuffer { recipient: &CanonicalAddr, tx_id: u64, amount: u128, + #[cfg(feature="gas_tracking")] tracker: &mut GasTracker<'a>, ) -> StdResult<()> { #[cfg(feature="gas_tracking")] @@ -260,7 +268,13 @@ impl DelayedWriteBuffer { // settle the entry let dwb_entry = self.entries[actual_settle_index]; - merge_dwb_entry(store, dwb_entry, None, tracker)?; + merge_dwb_entry( + store, + dwb_entry, + None, + #[cfg(feature="gas_tracking")] + tracker + )?; #[cfg(feature="gas_tracking")] let mut group2 = tracker.group("add_recipient.2"); From 9a9c98c45645ffe27cd708785f1879aaf9ba08f9 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Fri, 2 Aug 2024 07:22:55 +0000 Subject: [PATCH 51/87] chore: upgrade neutrino --- tests/dwb/bun.lockb | Bin 104964 -> 104964 bytes tests/dwb/package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dwb/bun.lockb b/tests/dwb/bun.lockb index 4af0d1fd9004eb82497ddff8852b2727c9b9859f..7996f7887a96fd3f8edc269fb4591a32868b02bd 100755 GIT binary patch delta 153 zcmV;K0A~M$v<8H<29PcwtLD*y#&ktnazE(;E+jEtk^QqiR6yH18l)nbwed$f>@I+iKldpZfZ@XfwX`wsdX-aaDupgt^Gb$t zs1-NI<|PB5pSP}D)9$JQpx(G!J0KLUV%D?8W^l6y0bVgKH7+x^JQ@MSFPEZb0U);^ HN&)0gL0Uo- delta 153 zcmV;K0A~M$v<8H<29Pcwu9BG~Hx0o!W&%^;r|a^Hm-;n+ys)ywtsQup1XZJBu}-QH zlQbbHlb{g@vnUc>R6yx)zW8F=R>YQS6mph};e+@^1xg-_`8aCA+X~q&AY=~p#2d^v z6f0k;Hl+v7b_J3f;Kv{>3(3r>hW}r>3lX!$W^l6y0ssI20002DJQ@MSFPBnh0U);^ HN&)0gzaT); diff --git a/tests/dwb/package.json b/tests/dwb/package.json index dade6691..970ef07d 100644 --- a/tests/dwb/package.json +++ b/tests/dwb/package.json @@ -23,7 +23,7 @@ "@solar-republic/contractor": "^0.8.17", "@solar-republic/cosmos-grpc": "^0.17.1", "@solar-republic/crypto": "^0.2.14", - "@solar-republic/neutrino": "^1.5.2", + "@solar-republic/neutrino": "^1.5.3", "bignumber.js": "^9.1.2" } } From 3bbaa01694c6ee1f9d09a15b2e6666baa00c2297 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Fri, 2 Aug 2024 17:21:00 +0000 Subject: [PATCH 52/87] style: cleanup --- tests/dwb/src/main.ts | 20 +++++++++----------- tests/dwb/src/snip.ts | 6 ------ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index 439d33f4..b41ee8d3 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -112,22 +112,20 @@ async function transfer_chain(sx_chain: string) { let k_checker: GasChecker | null = null; - for(let s_r1='a'; s_r1<='z'; s_r1=String.fromCharCode(s_r1.charCodeAt(0)+1)) { - for(let s_r2='a'; s_r2<='z'; s_r2=String.fromCharCode(s_r2.charCodeAt(0)+1)) { - const si_receiver = s_r1+s_r2; + for(let i_sim=0; i_sim<700; i_sim++) { + const si_receiver = i_sim+''; - const k_wallet = await Wallet(await sha256(text_to_bytes(si_receiver)), 'secretdev-1', P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, 'secret'); + const k_wallet = await Wallet(await sha256(text_to_bytes(si_receiver)), 'secretdev-1', P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, 'secret'); - const k_app_receiver = SecretApp(k_wallet, k_contract, X_GAS_PRICE); + const k_app_receiver = SecretApp(k_wallet, k_contract, X_GAS_PRICE); - console.log(`Alice --> ${si_receiver}`); + console.log(`Alice --> ${si_receiver}`); - // @ts-expect-error secret app - const g_result = await transfer(k_dwbv, 1_000000n, k_app_a, k_app_receiver, k_checker); + // @ts-expect-error secret app + const g_result = await transfer(k_dwbv, i_sim % 2? 1_000000n: 2_000000n, k_app_a, k_app_receiver, k_checker); - if(!k_checker) { - k_checker = new GasChecker(g_result.tracking, g_result.gasUsed); - } + if(!k_checker) { + k_checker = new GasChecker(g_result.tracking, g_result.gasUsed); } } } diff --git a/tests/dwb/src/snip.ts b/tests/dwb/src/snip.ts index a2b18bb0..b1b6058f 100644 --- a/tests/dwb/src/snip.ts +++ b/tests/dwb/src/snip.ts @@ -127,12 +127,6 @@ export async function transfer( // sync the buffer await k_dwbv.sync(); - // // results - // const sg_gas_used = g_meta?.gas_used; - // console.log(` ⏹ ${k_dwbv.empty} spaces`); - - // console.log(stringify_json(h_events, null, ' ')); - const h_tracking: GroupedGasLogs = {}; for(const [si_key, a_values] of entries(h_events!)) { const m_key = /^wasm\.gas\.(.+)$/.exec(si_key); From d9abb0006994b9219fc31df7e9ab09f12065d683 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Fri, 2 Aug 2024 17:38:18 +0000 Subject: [PATCH 53/87] fix: gas tracking response --- src/contract.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index aa2e0d5d..69c310d9 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -904,11 +904,11 @@ fn try_mint( &mut tracker, )?; - let mut resp = Response::new() + let resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); #[cfg(feature="gas_tracking")] - resp.add_gas_tracker(tracker); + return Ok(resp.add_gas_tracker(tracker)); Ok(resp) } @@ -1144,11 +1144,11 @@ fn try_deposit( &mut tracker, )?; - let mut resp = Response::new() + let resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?); #[cfg(feature="gas_tracking")] - resp.add_gas_tracker(tracker); + return Ok(resp.add_gas_tracker(tracker)); Ok(resp) } @@ -1311,14 +1311,14 @@ fn try_transfer( #[cfg(feature="gas_tracking")] let mut group1 = tracker.group("try_transfer.rest"); - let mut resp = Response::new() + let resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); #[cfg(feature="gas_tracking")] group1.log("rest"); #[cfg(feature="gas_tracking")] - resp.add_gas_tracker(tracker); + return Ok(resp.add_gas_tracker(tracker)); Ok(resp) } @@ -1351,11 +1351,11 @@ fn try_batch_transfer( )?; } - let mut resp = Response::new() + let resp = Response::new() .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); #[cfg(feature="gas_tracking")] - resp.add_gas_tracker(tracker); + return Ok(resp.add_gas_tracker(tracker)); Ok(resp) } @@ -1470,12 +1470,12 @@ fn try_send( &mut tracker, )?; - let mut resp = Response::new() + let resp = Response::new() .add_messages(messages) .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); #[cfg(feature="gas_tracking")] - resp.add_gas_tracker(tracker); + return Ok(resp.add_gas_tracker(tracker)); Ok(resp) } From bae3fd86404f478d77b0a945ad2a59f6570307d2 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 3 Aug 2024 07:55:38 +1200 Subject: [PATCH 54/87] dev: snip50 --- src/contract.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++- src/msg.rs | 85 +++++++++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 3 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 69c310d9..b35fe3cd 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -16,6 +16,7 @@ use crate::dwb::{DelayedWriteBuffer, DWB, TX_NODES}; use crate::dwb::log_dwb; use crate::gas_tracker::{GasTracker, LoggingExt}; +use crate::msg::Evaporator; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, @@ -168,6 +169,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S let contract_status = CONTRACT_STATUS.load(deps.storage)?; + let api = deps.api; match contract_status { ContractStatusLevel::StopAll | ContractStatusLevel::StopAllButRedeems => { let response = match msg { @@ -355,7 +357,9 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S } }; - pad_handle_result(response, RESPONSE_BLOCK_SIZE) + let padded_result = pad_handle_result(response, RESPONSE_BLOCK_SIZE); + msg.evaporate_to_target(api)?; + padded_result } #[entry_point] @@ -2630,6 +2634,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1000), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2675,6 +2680,7 @@ mod tests { recipient: "charlie".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2714,6 +2720,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(500), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2787,6 +2794,7 @@ mod tests { recipient: "ernie".to_string(), amount: Uint128::new(200), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2830,6 +2838,7 @@ mod tests { recipient: "dora".to_string(), amount: Uint128::new(50), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -2871,6 +2880,7 @@ mod tests { recipient, amount: Uint128::new(1), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2895,6 +2905,7 @@ mod tests { recipient, amount: Uint128::new(1), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2916,6 +2927,7 @@ mod tests { recipient, amount: Uint128::new(1), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -2938,6 +2950,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(i.into()), memo: None, + gas_target: None, padding: None, }; @@ -2959,6 +2972,7 @@ mod tests { recipient: "dora".to_string(), amount: Uint128::new(1), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -2978,6 +2992,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(i.into()), memo: None, + gas_target: None, padding: None, }; @@ -2995,6 +3010,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: "key".to_string(), + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3306,6 +3322,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(10000), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -3330,6 +3347,7 @@ mod tests { let handle_msg = ExecuteMsg::RegisterReceive { code_hash: "this_is_a_hash_of_a_code".to_string(), + gas_target: None, padding: None, }; let info = mock_info("contract", &[]); @@ -3345,6 +3363,7 @@ mod tests { amount: Uint128::new(100), memo: Some("my memo".to_string()), padding: None, + gas_target: None, msg: Some(to_binary("hey hey you you").unwrap()), }; let info = mock_info("bob", &[]); @@ -3393,6 +3412,7 @@ mod tests { let handle_msg = ExecuteMsg::RegisterReceive { code_hash: "this_is_a_hash_of_a_code".to_string(), + gas_target: None, padding: None, }; let info = mock_info("contract", &[]); @@ -3423,6 +3443,7 @@ mod tests { let handle_msg = ExecuteMsg::CreateViewingKey { entropy: "".to_string(), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -3464,6 +3485,7 @@ mod tests { // Set VK let handle_msg = ExecuteMsg::SetViewingKey { key: "hi lol".to_string(), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -3484,6 +3506,7 @@ mod tests { let actual_vk = "x".to_string().repeat(VIEWING_KEY_SIZE); let handle_msg = ExecuteMsg::SetViewingKey { key: actual_vk.clone(), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -3508,6 +3531,7 @@ mod tests { ) -> Result { let handle_msg = ExecuteMsg::RevokePermit { permit_name: permit_name.to_string(), + gas_target: None, padding: None, }; let info = mock_info(user_address, &[]); @@ -3715,6 +3739,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2500), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3729,6 +3754,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + gas_target: None, expiration: Some(1_571_797_420), }; let info = mock_info("bob", &[]); @@ -3745,6 +3771,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2500), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3760,6 +3787,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2000), memo: None, + gas_target: None, padding: None, }; @@ -3795,6 +3823,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2000), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3828,6 +3857,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3858,6 +3888,7 @@ mod tests { amount: Uint128::new(2500), memo: None, msg: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3871,6 +3902,7 @@ mod tests { let handle_msg = ExecuteMsg::IncreaseAllowance { spender: "alice".to_string(), amount: Uint128::new(2000), + gas_target: None, padding: None, expiration: None, }; @@ -3890,6 +3922,7 @@ mod tests { amount: Uint128::new(2500), memo: None, msg: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3902,6 +3935,7 @@ mod tests { // Sanity check let handle_msg = ExecuteMsg::RegisterReceive { code_hash: "lolz".to_string(), + gas_target: None, padding: None, }; let info = mock_info("contract", &[]); @@ -3928,6 +3962,7 @@ mod tests { amount: Uint128::new(2000), memo: Some("my memo".to_string()), msg: Some(send_msg), + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -3973,6 +4008,7 @@ mod tests { amount: Uint128::new(1), memo: None, msg: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4017,6 +4053,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4031,6 +4068,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4045,6 +4083,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + gas_target: None, expiration: None, }; let info = mock_info("bob", &[]); @@ -4060,6 +4099,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4074,6 +4114,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2000), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4100,6 +4141,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(1), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4160,6 +4202,7 @@ mod tests { .collect(); let handle_msg = ExecuteMsg::BatchBurnFrom { actions, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4187,6 +4230,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(allowance_size), padding: None, + gas_target: None, expiration: None, }; let info = mock_info(*name, &[]); @@ -4201,6 +4245,7 @@ mod tests { owner: "name".to_string(), amount: Uint128::new(2500), memo: None, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4223,6 +4268,7 @@ mod tests { let handle_msg = ExecuteMsg::BatchBurnFrom { actions, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4257,6 +4303,7 @@ mod tests { let handle_msg = ExecuteMsg::BatchBurnFrom { actions, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4290,6 +4337,7 @@ mod tests { .collect(); let handle_msg = ExecuteMsg::BatchBurnFrom { actions, + gas_target: None, padding: None, }; let info = mock_info("alice", &[]); @@ -4316,6 +4364,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + gas_target: None, expiration: None, }; let info = mock_info("bob", &[]); @@ -4344,6 +4393,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + gas_target: None, expiration: None, }; let info = mock_info("bob", &[]); @@ -4360,6 +4410,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(50), padding: None, + gas_target: None, expiration: None, }; let info = mock_info("bob", &[]); @@ -4398,6 +4449,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + gas_target: None, expiration: None, }; let info = mock_info("bob", &[]); @@ -4426,6 +4478,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + gas_target: None, expiration: None, }; let info = mock_info("bob", &[]); @@ -4462,6 +4515,7 @@ mod tests { let handle_msg = ExecuteMsg::ChangeAdmin { address: "bob".to_string(), + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4492,6 +4546,7 @@ mod tests { let handle_msg = ExecuteMsg::SetContractStatus { level: ContractStatusLevel::StopAll, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4562,6 +4617,7 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: None, + gas_target: None, padding: None, }; let info = mock_info("butler", &[]); @@ -4575,6 +4631,7 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: None, + gas_target: None, padding: None, }; let info = mock_info("butler", &[]); @@ -4592,6 +4649,7 @@ mod tests { amount: Uint128::new(1000), denom: None, padding: None, + gas_target: None, }; let info = mock_info("butler", &[]); @@ -4607,6 +4665,7 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: Option::from("uscrt".to_string()), + gas_target: None, padding: None, }; let info = mock_info("butler", &[]); @@ -4657,6 +4716,7 @@ mod tests { ); // test when deposit disabled let handle_msg = ExecuteMsg::Deposit { + gas_target: None, padding: None, }; let info = mock_info( @@ -4672,6 +4732,7 @@ mod tests { assert!(error.contains("Tried to deposit an unsupported coin uscrt")); let handle_msg = ExecuteMsg::Deposit { + gas_target: None, padding: None, }; @@ -4700,6 +4761,7 @@ mod tests { let create_vk_msg = ExecuteMsg::CreateViewingKey { entropy: "34".to_string(), + gas_target: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -4755,6 +4817,7 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -4769,6 +4832,7 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(burn_amount), memo: None, + gas_target: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -4819,6 +4883,7 @@ mod tests { recipient: "lebron".to_string(), amount: Uint128::new(mint_amount), memo: None, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4834,6 +4899,7 @@ mod tests { recipient: "lebron".to_string(), amount: Uint128::new(mint_amount), memo: None, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4873,6 +4939,7 @@ mod tests { let pause_msg = ExecuteMsg::SetContractStatus { level: ContractStatusLevel::StopAllButRedeems, + gas_target: None, padding: None, }; let info = mock_info("not_admin", &[]); @@ -4884,6 +4951,7 @@ mod tests { let mint_msg = ExecuteMsg::AddMinters { minters: vec!["not_admin".to_string()], + gas_target: None, padding: None, }; let info = mock_info("not_admin", &[]); @@ -4895,6 +4963,7 @@ mod tests { let mint_msg = ExecuteMsg::RemoveMinters { minters: vec!["admin".to_string()], + gas_target: None, padding: None, }; let info = mock_info("not_admin", &[]); @@ -4906,6 +4975,7 @@ mod tests { let mint_msg = ExecuteMsg::SetMinters { minters: vec!["not_admin".to_string()], + gas_target: None, padding: None, }; let info = mock_info("not_admin", &[]); @@ -4917,6 +4987,7 @@ mod tests { let change_admin_msg = ExecuteMsg::ChangeAdmin { address: "not_admin".to_string(), + gas_target: None, padding: None, }; let info = mock_info("not_admin", &[]); @@ -4949,6 +5020,7 @@ mod tests { let pause_msg = ExecuteMsg::SetContractStatus { level: ContractStatusLevel::StopAllButRedeems, + gas_target: None, padding: None, }; @@ -4966,6 +5038,7 @@ mod tests { recipient: "account".to_string(), amount: Uint128::new(123), memo: None, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -4981,6 +5054,7 @@ mod tests { let withdraw_msg = ExecuteMsg::Redeem { amount: Uint128::new(5000), denom: Option::from("uscrt".to_string()), + gas_target: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -5008,6 +5082,7 @@ mod tests { let pause_msg = ExecuteMsg::SetContractStatus { level: ContractStatusLevel::StopAll, + gas_target: None, padding: None, }; @@ -5025,6 +5100,7 @@ mod tests { recipient: "account".to_string(), amount: Uint128::new(123), memo: None, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5040,6 +5116,7 @@ mod tests { let withdraw_msg = ExecuteMsg::Redeem { amount: Uint128::new(5000), denom: Option::from("uscrt".to_string()), + gas_target: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -5084,6 +5161,7 @@ mod tests { // try when mint disabled let handle_msg = ExecuteMsg::SetMinters { minters: vec!["bob".to_string()], + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5095,6 +5173,7 @@ mod tests { let handle_msg = ExecuteMsg::SetMinters { minters: vec!["bob".to_string()], + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5106,6 +5185,7 @@ mod tests { let handle_msg = ExecuteMsg::SetMinters { minters: vec!["bob".to_string()], + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5118,6 +5198,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5130,6 +5211,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5171,6 +5253,7 @@ mod tests { // try when mint disabled let handle_msg = ExecuteMsg::AddMinters { minters: vec!["bob".to_string()], + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5182,6 +5265,7 @@ mod tests { let handle_msg = ExecuteMsg::AddMinters { minters: vec!["bob".to_string()], + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5193,6 +5277,7 @@ mod tests { let handle_msg = ExecuteMsg::AddMinters { minters: vec!["bob".to_string()], + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5205,6 +5290,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5217,6 +5303,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5257,6 +5344,7 @@ mod tests { // try when mint disabled let handle_msg = ExecuteMsg::RemoveMinters { minters: vec!["bob".to_string()], + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5268,6 +5356,7 @@ mod tests { let handle_msg = ExecuteMsg::RemoveMinters { minters: vec!["admin".to_string()], + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5279,6 +5368,7 @@ mod tests { let handle_msg = ExecuteMsg::RemoveMinters { minters: vec!["admin".to_string()], + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5291,6 +5381,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5304,6 +5395,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5316,6 +5408,7 @@ mod tests { // Removing another extra time to ensure nothing funky happens let handle_msg = ExecuteMsg::RemoveMinters { minters: vec!["admin".to_string()], + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5328,6 +5421,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -5341,6 +5435,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -5378,6 +5473,7 @@ mod tests { let create_vk_msg = ExecuteMsg::CreateViewingKey { entropy: "34".to_string(), + gas_target: None, padding: None, }; let info = mock_info("giannis", &[]); @@ -5785,6 +5881,7 @@ mod tests { spender: "lebron".to_string(), amount: Uint128::new(2000), padding: None, + gas_target: None, expiration: None, }; let info = mock_info("giannis", &[]); @@ -5816,6 +5913,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: vk1.clone(), + gas_target: None, padding: None, }; let info = mock_info("lebron", &[]); @@ -5834,6 +5932,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: vk2.clone(), + gas_target: None, padding: None, }; let info = mock_info("giannis", &[]); @@ -5909,6 +6008,7 @@ mod tests { for i in 0..num_owners { let handle_msg = ExecuteMsg::SetViewingKey { key: vk.clone(), + gas_target: None, padding: None, }; let info = mock_info(format!("owner{}", i).as_str(), &[]); @@ -5932,6 +6032,7 @@ mod tests { spender: format!("spender{}", j), amount: Uint128::new(50), padding: None, + gas_target: None, expiration: None, }; let info = mock_info(format!("owner{}", i).as_str(), &[]); @@ -5945,6 +6046,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: vk.clone(), + gas_target: None, padding: None, }; let info = mock_info(format!("spender{}", j).as_str(), &[]); @@ -6130,6 +6232,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: "key".to_string(), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -6188,6 +6291,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: "key".to_string(), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -6199,6 +6303,7 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(1), memo: Some("my burn message".to_string()), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -6214,6 +6319,7 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: Option::from("uscrt".to_string()), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -6230,6 +6336,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: Some("my mint message".to_string()), + gas_target: None, padding: None, }; let info = mock_info("admin", &[]); @@ -6239,6 +6346,7 @@ mod tests { assert!(ensure_success(handle_result.unwrap())); let handle_msg = ExecuteMsg::Deposit { + gas_target: None, padding: None, }; let info = mock_info( @@ -6260,6 +6368,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1000), memo: Some("my transfer message #1".to_string()), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -6273,6 +6382,7 @@ mod tests { recipient: "banana".to_string(), amount: Uint128::new(500), memo: Some("my transfer message #2".to_string()), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); @@ -6286,6 +6396,7 @@ mod tests { recipient: "mango".to_string(), amount: Uint128::new(2500), memo: Some("my transfer message #3".to_string()), + gas_target: None, padding: None, }; let info = mock_info("bob", &[]); diff --git a/src/msg.rs b/src/msg.rs index d5e8ecb6..3d31afa7 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -91,9 +91,11 @@ pub enum ExecuteMsg { Redeem { amount: Uint128, denom: Option, + gas_target: Option, padding: Option, }, Deposit { + gas_target: Option, padding: Option, }, @@ -102,6 +104,7 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, + gas_target: Option, padding: Option, }, Send { @@ -110,31 +113,38 @@ pub enum ExecuteMsg { amount: Uint128, msg: Option, memo: Option, + gas_target: Option, padding: Option, }, BatchTransfer { actions: Vec, + gas_target: Option, padding: Option, }, BatchSend { actions: Vec, + gas_target: Option, padding: Option, }, Burn { amount: Uint128, memo: Option, + gas_target: Option, padding: Option, }, RegisterReceive { code_hash: String, + gas_target: Option, padding: Option, }, CreateViewingKey { entropy: String, + gas_target: Option, padding: Option, }, SetViewingKey { key: String, + gas_target: Option, padding: Option, }, @@ -143,12 +153,14 @@ pub enum ExecuteMsg { spender: String, amount: Uint128, expiration: Option, + gas_target: Option, padding: Option, }, DecreaseAllowance { spender: String, amount: Uint128, expiration: Option, + gas_target: Option, padding: Option, }, TransferFrom { @@ -156,6 +168,7 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, + gas_target: Option, padding: Option, }, SendFrom { @@ -165,24 +178,29 @@ pub enum ExecuteMsg { amount: Uint128, msg: Option, memo: Option, + gas_target: Option, padding: Option, }, BatchTransferFrom { actions: Vec, + gas_target: Option, padding: Option, }, BatchSendFrom { actions: Vec, + gas_target: Option, padding: Option, }, BurnFrom { owner: String, amount: Uint128, memo: Option, + gas_target: Option, padding: Option, }, BatchBurnFrom { actions: Vec, + gas_target: Option, padding: Option, }, @@ -191,42 +209,56 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, + gas_target: Option, padding: Option, }, BatchMint { actions: Vec, + gas_target: Option, padding: Option, }, AddMinters { minters: Vec, + gas_target: Option, padding: Option, }, RemoveMinters { minters: Vec, + gas_target: Option, padding: Option, }, SetMinters { minters: Vec, + gas_target: Option, padding: Option, }, // Admin ChangeAdmin { address: String, + gas_target: Option, padding: Option, }, SetContractStatus { level: ContractStatusLevel, + gas_target: Option, padding: Option, }, /// Add deposit/redeem support for these coin denoms - AddSupportedDenoms { denoms: Vec }, + AddSupportedDenoms { + denoms: Vec, + gas_target: Option, + }, /// Remove deposit/redeem support for these coin denoms - RemoveSupportedDenoms { denoms: Vec }, + RemoveSupportedDenoms { + denoms: Vec, + gas_target: Option, + }, // Permit RevokePermit { permit_name: String, + gas_target: Option, padding: Option, }, } @@ -335,6 +367,55 @@ pub enum ExecuteAnswer { }, } +pub trait Evaporator { + fn evaporate_to_target(&self, api: &dyn Api) -> StdResult<()>; +} + +impl Evaporator for ExecuteMsg { + fn evaporate_to_target(&self, api: &dyn Api) -> StdResult<()> { + match self { + ExecuteMsg::Redeem { gas_target, .. } + | ExecuteMsg::Deposit { gas_target, .. } + | ExecuteMsg::Transfer { gas_target, .. } + | ExecuteMsg::Send { gas_target, .. } + | ExecuteMsg::BatchTransfer { gas_target, .. } + | ExecuteMsg::BatchSend { gas_target, .. } + | ExecuteMsg::Burn { gas_target, .. } + | ExecuteMsg::RegisterReceive { gas_target, .. } + | ExecuteMsg::CreateViewingKey { gas_target, .. } + | ExecuteMsg::SetViewingKey { gas_target, .. } + | ExecuteMsg::IncreaseAllowance { gas_target, .. } + | ExecuteMsg::DecreaseAllowance { gas_target, .. } + | ExecuteMsg::TransferFrom { gas_target, .. } + | ExecuteMsg::SendFrom { gas_target, .. } + | ExecuteMsg::BatchTransferFrom { gas_target, .. } + | ExecuteMsg::BatchSendFrom { gas_target, .. } + | ExecuteMsg::BurnFrom { gas_target, .. } + | ExecuteMsg::BatchBurnFrom { gas_target, .. } + | ExecuteMsg::Mint { gas_target, .. } + | ExecuteMsg::BatchMint { gas_target, .. } + | ExecuteMsg::AddMinters { gas_target, .. } + | ExecuteMsg::RemoveMinters { gas_target, .. } + | ExecuteMsg::SetMinters { gas_target, .. } + | ExecuteMsg::ChangeAdmin { gas_target, .. } + | ExecuteMsg::SetContractStatus { gas_target, .. } + | ExecuteMsg::AddSupportedDenoms { gas_target, .. } + | ExecuteMsg::RemoveSupportedDenoms { gas_target, .. } + | ExecuteMsg::RevokePermit { gas_target, .. } => match gas_target { + Some(gas_target) => { + let gas_used = api.check_gas()? as u32; + if gas_used < *gas_target { + let evaporate_amount = gas_target - gas_used; + api.gas_evaporate(evaporate_amount)?; + } + Ok(()) + } + None => Ok(()), + }, + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] #[cfg_attr(test, derive(Eq, PartialEq))] #[serde(rename_all = "snake_case")] From 5d8d4b62b63314870815779cff705999df9f6beb Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 3 Aug 2024 15:28:36 +1200 Subject: [PATCH 55/87] unit tests btbe --- src/btbe.rs | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++-- src/dwb.rs | 45 +++--------- 2 files changed, 210 insertions(+), 40 deletions(-) diff --git a/src/btbe.rs b/src/btbe.rs index 80ae03c1..73c9534b 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -56,7 +56,7 @@ pub struct StoredEntry( ); impl StoredEntry { - fn new(address: CanonicalAddr) -> StdResult { + fn new(address: &CanonicalAddr) -> StdResult { let address = address.as_slice(); if address.len() != BTBE_BUCKET_ADDRESS_BYTES { @@ -71,7 +71,7 @@ impl StoredEntry { } fn from(storage: &mut dyn Storage, dwb_entry: &DelayedWriteBufferEntry, amount_spent: Option) -> StdResult { - let mut entry = StoredEntry::new(dwb_entry.recipient()?)?; + let mut entry = StoredEntry::new(&dwb_entry.recipient()?)?; let amount_spent = amount_u64(amount_spent)?; @@ -238,7 +238,7 @@ impl BtbeBucket { Ok(Self { capacity: BTBE_CAPACITY, entries: [ - StoredEntry::new(CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; BTBE_CAPACITY as usize + StoredEntry::new(&CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; BTBE_CAPACITY as usize ] }) } @@ -279,6 +279,7 @@ impl BtbeBucket { } #[derive(Serialize, Deserialize, Clone, Copy, Debug)] +#[cfg_attr(test, derive(PartialEq))] pub struct BitwiseTrieNode { pub left: u64, pub right: u64, @@ -342,7 +343,7 @@ fn entry_belongs_in_left_node(secret: &[u8], entry: StoredEntry, bit_pos: u8) -> return Ok(U256::from(0) == (key_u256 >> (255 - bit_pos)) & U256::from(1)); } -/// Locates a btbe node given an address; returns tuple of (node, bit position) +/// Locates a btbe node given an address; returns tuple of (node, node_id, bit position) pub fn locate_btbe_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult<(BitwiseTrieNode, u64, u8)> { // load internal contract secret let secret = INTERNAL_SECRET.load(storage)?; @@ -437,7 +438,7 @@ pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> St // `spent_amount` is any required subtraction due to being sender of tx pub fn merge_dwb_entry( storage: &mut dyn Storage, - dwb_entry: DelayedWriteBufferEntry, + dwb_entry: &DelayedWriteBufferEntry, amount_spent: Option, #[cfg(feature="gas_tracking")] tracker: &mut GasTracker, @@ -594,3 +595,197 @@ pub fn initialize_btbe(storage: &mut dyn Storage) -> StdResult<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use std::any::Any; + + use crate::contract::instantiate; + use crate::dwb::ZERO_ADDR; + use crate::msg::{InitialBalance, InstantiateMsg, QueryAnswer}; + use cosmwasm_std::{from_binary, testing::*, Addr, Api, Binary, OwnedDeps, QueryResponse, Response, Uint128}; + + use super::*; + + fn init_helper( + initial_balances: Vec, + ) -> ( + StdResult, + OwnedDeps, + ) { + let mut deps = mock_dependencies_with_balance(&[]); + let env = mock_env(); + let info = mock_info("instantiator", &[]); + + let init_msg = InstantiateMsg { + name: "sec-sec".to_string(), + admin: Some("admin".to_string()), + symbol: "SECSEC".to_string(), + decimals: 8, + initial_balances: Some(initial_balances), + prng_seed: Binary::from("lolz fun yay".as_bytes()), + config: None, + supported_denoms: None, + }; + + (instantiate(deps.as_mut(), env, info, init_msg), deps) + } + + fn extract_error_msg(error: StdResult) -> String { + match error { + Ok(response) => { + let bin_err = (&response as &dyn Any) + .downcast_ref::() + .expect("An error was expected, but no error could be extracted"); + match from_binary(bin_err).unwrap() { + QueryAnswer::ViewingKeyError { msg } => msg, + _ => panic!("Unexpected query answer"), + } + } + Err(err) => match err { + StdError::GenericErr { msg, .. } => msg, + _ => panic!("Unexpected result from init"), + }, + } + } + + #[test] + fn test_stored_entry() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let _env = mock_env(); + let _info = mock_info("bob", &[]); + + let canonical = deps + .api + .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str()) + .unwrap(); + let entry = StoredEntry::new(&canonical).unwrap(); + assert_eq!(entry.address().unwrap(), canonical); + assert_eq!(entry.balance().unwrap(), 0_u64); + + let dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap(); + + // expect error if trying to spend too much + let entry = StoredEntry::from(&mut deps.storage, &dwb_entry, Some(1)); + let error = extract_error_msg(entry); + assert!(error.contains("insufficient funds")); + + let entry = StoredEntry::from(&mut deps.storage, &dwb_entry, None).unwrap(); + assert_eq!(entry.address().unwrap(), canonical); + assert_eq!(entry.balance().unwrap(), 0_u64); + } + + #[test] + fn test_btbe() { + let (init_result, mut deps) = init_helper(vec![InitialBalance { + address: "bob".to_string(), + amount: Uint128::new(5000), + }]); + assert!( + init_result.is_ok(), + "Init failed: {}", + init_result.err().unwrap() + ); + let _env = mock_env(); + let _info = mock_info("bob", &[]); + + let _ = initialize_btbe(&mut deps.storage).unwrap(); + + let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap(); + assert_eq!(btbe_node_count, 1); + + for i in 1..=128 { + let canonical = deps + .api + .addr_canonicalize(Addr::unchecked(format!("{i}zzzzzz")).as_str()) + .unwrap(); + let entry = StoredEntry::new(&canonical).unwrap(); + assert_eq!(entry.address().unwrap(), canonical); + assert_eq!(entry.balance().unwrap(), 0_u64); + + let dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap(); + + let _result = merge_dwb_entry(&mut deps.storage, &dwb_entry, None); + + let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap(); + assert_eq!(btbe_node_count, 1); + + let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &canonical).unwrap(); + assert_eq!(node, BitwiseTrieNode { + left: 0, + right: 0, + bucket: 2, + }); + assert_eq!(node_id, 1); + assert_eq!(bit_pos, 0); + } + + // btbe trie should split nodes when get to 129th entry + let canonical = deps + .api + .addr_canonicalize(Addr::unchecked(format!("bob")).as_str()) + .unwrap(); + let entry = StoredEntry::new(&canonical).unwrap(); + assert_eq!(entry.address().unwrap(), canonical); + assert_eq!(entry.balance().unwrap(), 0_u64); + + let dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap(); + + let _result = merge_dwb_entry(&mut deps.storage, &dwb_entry, None); + + let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap(); + assert_eq!(btbe_node_count, 3); + let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &canonical).unwrap(); + assert_eq!(node, BitwiseTrieNode { + left: 0, + right: 0, + bucket: 3, + }); + assert_eq!(node_id, 3); + assert_eq!(bit_pos, 1); + + // have other addresses been moved to new nodes + let first = deps + .api + .addr_canonicalize(Addr::unchecked(format!("1zzzzzz")).as_str()) + .unwrap(); + let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &first).unwrap(); + assert_eq!(node, BitwiseTrieNode { + left: 0, + right: 0, + bucket: 2, + }); + assert_eq!(node_id, 2); + assert_eq!(bit_pos, 1); + + let second = deps + .api + .addr_canonicalize(Addr::unchecked(format!("2zzzzzz")).as_str()) + .unwrap(); + let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &second).unwrap(); + assert_eq!(node, BitwiseTrieNode { + left: 0, + right: 0, + bucket: 3, + }); + assert_eq!(node_id, 3); + assert_eq!(bit_pos, 1); + + let canonical_entry = stored_entry(&deps.storage, &canonical).unwrap().unwrap(); + assert_eq!(canonical_entry.balance().unwrap(), 0); + let first_entry = stored_entry(&deps.storage, &first).unwrap().unwrap(); + assert_eq!(first_entry.balance().unwrap(), 0); + let second_entry = stored_entry(&deps.storage, &second).unwrap().unwrap(); + assert_eq!(second_entry.balance().unwrap(), 0); + let not_entry = stored_entry(&deps.storage, &deps.api.addr_canonicalize(Addr::unchecked("alice".to_string()).as_str()).unwrap()).unwrap(); + assert_eq!(not_entry, None); + } +} \ No newline at end of file diff --git a/src/dwb.rs b/src/dwb.rs index d2ae91aa..57e1f9a6 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -49,14 +49,6 @@ pub struct DelayedWriteBuffer { pub entries: [DelayedWriteBufferEntry; DWB_LEN as usize], } -//#[inline] -//fn random_addr(rng: &mut ContractPrng) -> CanonicalAddr { -// #[cfg(test)] -// return CanonicalAddr::from(&[rng.rand_bytes(), rng.rand_bytes()].concat()[0..DWB_RECIPIENT_BYTES]); // because mock canonical addr is 54 bytes -// #[cfg(not(test))] -// CanonicalAddr::from(&rng.rand_bytes()[0..DWB_RECIPIENT_BYTES]) // canonical addr is 20 bytes (less than 32) -//} - pub fn random_in_range(rng: &mut ContractPrng, a: u32, b: u32) -> StdResult { if b <= a { return Err(StdError::generic_err("invalid range")); @@ -79,7 +71,7 @@ impl DelayedWriteBuffer { empty_space_counter: DWB_LEN - 1, // first entry is a dummy entry for constant-time writing entries: [ - DelayedWriteBufferEntry::new(CanonicalAddr::from(&ZERO_ADDR))?; DWB_LEN as usize + DelayedWriteBufferEntry::new(&CanonicalAddr::from(&ZERO_ADDR))?; DWB_LEN as usize ] }) } @@ -127,7 +119,7 @@ impl DelayedWriteBuffer { let result = merge_dwb_entry( store, - entry, + &entry, Some(amount_spent), #[cfg(feature="gas_tracking")] tracker @@ -153,7 +145,7 @@ impl DelayedWriteBuffer { let entry = self.entries[matched_entry_idx]; // create a new entry to replace the released one, giving it the same address to avoid introducing random addresses - let replacement_entry = DelayedWriteBufferEntry::new(entry.recipient()?)?; + let replacement_entry = DelayedWriteBufferEntry::new(&entry.recipient()?)?; // add entry amount to the stored balance for the address (will be 0 if dummy) safe_add(&mut balance, entry.amount()? as u128); @@ -164,16 +156,6 @@ impl DelayedWriteBuffer { Ok((balance, entry)) } - //fn unique_random_entry(&self, rng: &mut ContractPrng) -> StdResult { - // // produce a new random address - // let mut replacement_address = random_addr(rng); - // // ensure random addr is not already in dwb (extremely unlikely!!) - // while self.recipient_match(&replacement_address) > 0 { - // replacement_address = random_addr(rng); - // } - // DelayedWriteBufferEntry::new(replacement_address) - //} - // returns matched index for a given address pub fn recipient_match(&self, address: &CanonicalAddr) -> usize { let mut matched_index: usize = 0; @@ -270,7 +252,7 @@ impl DelayedWriteBuffer { let dwb_entry = self.entries[actual_settle_index]; merge_dwb_entry( store, - dwb_entry, + &dwb_entry, None, #[cfg(feature="gas_tracking")] tracker @@ -342,7 +324,7 @@ pub struct DelayedWriteBufferEntry( ); impl DelayedWriteBufferEntry { - pub fn new(recipient: CanonicalAddr) -> StdResult { + pub fn new(recipient: &CanonicalAddr) -> StdResult { let recipient = recipient.as_slice(); if recipient.len() != DWB_RECIPIENT_BYTES { return Err(StdError::generic_err("dwb: invalid recipient length")); @@ -499,7 +481,10 @@ impl TxNode { } } - +/// A tx bundle is 1 or more tx nodes added to an account's history. +/// The bundle points to a linked list of transaction nodes, which each reference +/// a transaction record by its global id. +/// used with add_suffix(canonical addr of account) #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TxBundle { /// TX_NODES idx - pointer to the head tx node in the linked list @@ -510,16 +495,6 @@ pub struct TxBundle { pub offset: u32, } -/// A tx bundle is 1 or more tx nodes added to an account's history. -/// The bundle points to a linked list of transaction nodes, which each reference -/// a transaction record by its global id. -/// used with add_suffix(canonical addr of account) -//pub static ACCOUNT_TXS: AppendStore = AppendStore::new(KEY_ACCOUNT_TXS); - -/// Keeps track of the total count of txs for an account (not tx bundles) -/// used with add_suffix(canonical addr of account) -//pub static ACCOUNT_TX_COUNT: Item = Item::new(KEY_ACCOUNT_TX_COUNT); - #[inline] fn constant_time_is_not_zero(value: i32) -> u32 { (((value | -value) >> 31) & 1) as u32 @@ -584,7 +559,7 @@ mod tests { let _info = mock_info("bob", &[]); let recipient = CanonicalAddr::from(ZERO_ADDR); - let mut dwb_entry = DelayedWriteBufferEntry::new(recipient).unwrap(); + let mut dwb_entry = DelayedWriteBufferEntry::new(&recipient).unwrap(); assert_eq!(dwb_entry, DelayedWriteBufferEntry([0u8; DWB_ENTRY_BYTES])); assert_eq!(dwb_entry.recipient().unwrap(), CanonicalAddr::from(ZERO_ADDR)); From 2291658b349e7db05e36e378c3cd4476c831e420 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 3 Aug 2024 15:52:37 +1200 Subject: [PATCH 56/87] cargo fmt --- build.rs | 8 +- src/batch.rs | 1 - src/btbe.rs | 331 ++++++++---- src/contract.rs | 1031 ++++++++++++++++++------------------ src/dwb.rs | 209 ++++---- src/gas_tracker.rs | 24 +- src/lib.rs | 8 +- src/msg.rs | 10 +- src/state.rs | 2 +- src/strings.rs | 3 +- src/transaction_history.rs | 36 +- 11 files changed, 893 insertions(+), 770 deletions(-) diff --git a/build.rs b/build.rs index 6cef931c..eddad235 100644 --- a/build.rs +++ b/build.rs @@ -9,7 +9,7 @@ fn main() { let btbe_capacity = env::var("BTBE_CAPACITY").unwrap_or_else(|_| "128".to_string()); // path to destination config.rs file - let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); + let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); let dest_path = Path::new(&out_dir).join("config.rs"); // write constants @@ -17,7 +17,7 @@ fn main() { write!(file, "pub const DWB_CAPACITY: u16 = {};\n", dwb_capacity).unwrap(); write!(file, "pub const BTBE_CAPACITY: u16 = {};\n", btbe_capacity).unwrap(); - // monitor - println!("cargo:rerun-if-env-changed=DWB_CAPACITY"); - println!("cargo:rerun-if-env-changed=BTBE_CAPACITY"); + // monitor + println!("cargo:rerun-if-env-changed=DWB_CAPACITY"); + println!("cargo:rerun-if-env-changed=BTBE_CAPACITY"); } diff --git a/src/batch.rs b/src/batch.rs index 5c175cbe..47b4bb09 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -58,4 +58,3 @@ pub struct BurnFromAction { pub amount: Uint128, pub memo: Option, } - diff --git a/src/btbe.rs b/src/btbe.rs index 73c9534b..94caa568 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -3,14 +3,21 @@ include!(concat!(env!("OUT_DIR"), "/config.rs")); use constant_time_eq::constant_time_eq; +use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; use primitive_types::U256; -use secret_toolkit::{notification::hkdf_sha_256, serialization::{Bincode2, Serde}, storage::Item}; -use serde::{Serialize, Deserialize,}; +use secret_toolkit::{ + notification::hkdf_sha_256, + serialization::{Bincode2, Serde}, + storage::Item, +}; +use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; -use crate::{dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}, gas_tracker::GasTracker}; use crate::state::{safe_add_u64, INTERNAL_SECRET}; +use crate::{ + dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}, + gas_tracker::GasTracker, +}; pub const KEY_BTBE_ENTRY_HISTORY: &[u8] = b"btbe-entry-hist"; pub const KEY_BTBE_BUCKETS_COUNT: &[u8] = b"btbe-buckets-cnt"; @@ -29,31 +36,35 @@ const U128_BYTES: usize = 16; const BTBE_BUCKET_ADDRESS_BYTES: usize = 54; #[cfg(not(test))] const BTBE_BUCKET_ADDRESS_BYTES: usize = 20; -const BTBE_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u128) -const BTBE_BUCKET_HISTORY_BYTES: usize = 4; // Max 4 (u32) +const BTBE_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u128) +const BTBE_BUCKET_HISTORY_BYTES: usize = 4; // Max 4 (u32) const_assert!(BTBE_BUCKET_BALANCE_BYTES <= U128_BYTES); const_assert!(BTBE_BUCKET_HISTORY_BYTES <= U32_BYTES); -const BTBE_BUCKET_ENTRY_BYTES: usize = BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES; +const BTBE_BUCKET_ENTRY_BYTES: usize = + BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES; /// canonical address bytes corresponding to the 33-byte null public key, in hexadecimal #[cfg(test)] -const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x62,0x55,0xA7,0x84,0x51,0xEE,0xB4,0xB1,0x29,0xED,0x8E,0xAC,0xFF,0xA2,0xFE,0xEF, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]; +const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [ + 0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC, + 0xFF, 0xA2, 0xFE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; #[cfg(not(test))] -const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [0x29,0xCF,0xC6,0x37,0x62,0x55,0xA7,0x84,0x51,0xEE,0xB4,0xB1,0x29,0xED,0x8E,0xAC,0xFF,0xA2,0xFE,0xEF]; +const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [ + 0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC, + 0xFF, 0xA2, 0xFE, 0xEF, +]; /// A `StoredEntry` consists of the address, balance, and tx bundle history length in a byte array representation. -/// The methods of the struct implementation also handle pushing and getting the tx bundle history in a simplified +/// The methods of the struct implementation also handle pushing and getting the tx bundle history in a simplified /// append store. #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] #[cfg_attr(test, derive(Eq))] -pub struct StoredEntry( - #[serde(with = "BigArray")] - [u8; BTBE_BUCKET_ENTRY_BYTES], -); +pub struct StoredEntry(#[serde(with = "BigArray")] [u8; BTBE_BUCKET_ENTRY_BYTES]); impl StoredEntry { fn new(address: &CanonicalAddr) -> StdResult { @@ -65,12 +76,14 @@ impl StoredEntry { let mut result = [0u8; BTBE_BUCKET_ENTRY_BYTES]; result[..BTBE_BUCKET_ADDRESS_BYTES].copy_from_slice(address); - Ok(Self { - 0: result, - }) + Ok(Self { 0: result }) } - fn from(storage: &mut dyn Storage, dwb_entry: &DelayedWriteBufferEntry, amount_spent: Option) -> StdResult { + fn from( + storage: &mut dyn Storage, + dwb_entry: &DelayedWriteBufferEntry, + amount_spent: Option, + ) -> StdResult { let mut entry = StoredEntry::new(&dwb_entry.recipient()?)?; let amount_spent = amount_u64(amount_spent)?; @@ -80,18 +93,20 @@ impl StoredEntry { new_balance } else { return Err(StdError::generic_err(format!( - "insufficient funds while creating StoredEntry; balance:{}, amount_spent:{}", dwb_entry.amount()?, amount_spent, + "insufficient funds while creating StoredEntry; balance:{}, amount_spent:{}", + dwb_entry.amount()?, + amount_spent, ))); }; entry.set_balance(balance)?; entry.push_tx_bundle( - storage, + storage, &TxBundle { head_node: dwb_entry.head_node()?, list_len: dwb_entry.list_len()?, offset: 0, - } + }, )?; Ok(entry) @@ -145,14 +160,16 @@ impl StoredEntry { } pub fn merge_dwb_entry( - &mut self, - storage: &mut dyn Storage, - dwb_entry: &DelayedWriteBufferEntry, - amount_spent: Option + &mut self, + storage: &mut dyn Storage, + dwb_entry: &DelayedWriteBufferEntry, + amount_spent: Option, ) -> StdResult<()> { let history_len = self.history_len()?; if history_len == 0 { - return Err(StdError::generic_err("use `from` to create new entry from dwb_entry")); + return Err(StdError::generic_err( + "use `from` to create new entry from dwb_entry", + )); } let mut balance = self.balance()?; @@ -165,7 +182,8 @@ impl StoredEntry { new_balance } else { return Err(StdError::generic_err(format!( - "insufficient funds while merging entry; balance:{}, amount_spent:{}", balance, amount_spent + "insufficient funds while merging entry; balance:{}, amount_spent:{}", + balance, amount_spent ))); }; @@ -196,17 +214,37 @@ impl StoredEntry { /// tries to get the element at pos fn get_tx_bundle_at_unchecked(&self, storage: &dyn Storage, pos: u32) -> StdResult { - let bundle_data = storage.get(&[KEY_BTBE_ENTRY_HISTORY, self.address_slice(), pos.to_be_bytes().as_slice()].concat()); - let bundle_data = bundle_data.ok_or_else(|| { return StdError::generic_err("tx bundle not found"); } )?; - Bincode2::deserialize( - &bundle_data - ) + let bundle_data = storage.get( + &[ + KEY_BTBE_ENTRY_HISTORY, + self.address_slice(), + pos.to_be_bytes().as_slice(), + ] + .concat(), + ); + let bundle_data = bundle_data.ok_or_else(|| { + return StdError::generic_err("tx bundle not found"); + })?; + Bincode2::deserialize(&bundle_data) } /// Sets data at a given index - fn set_tx_bundle_at_unchecked(&self, storage: &mut dyn Storage, pos: u32, bundle: &TxBundle) -> StdResult<()> { + fn set_tx_bundle_at_unchecked( + &self, + storage: &mut dyn Storage, + pos: u32, + bundle: &TxBundle, + ) -> StdResult<()> { let bundle_data = Bincode2::serialize(bundle)?; - storage.set(&[KEY_BTBE_ENTRY_HISTORY, self.address_slice(), pos.to_be_bytes().as_slice()].concat(), &bundle_data); + storage.set( + &[ + KEY_BTBE_ENTRY_HISTORY, + self.address_slice(), + pos.to_be_bytes().as_slice(), + ] + .concat(), + &bundle_data, + ); Ok(()) } @@ -237,9 +275,8 @@ impl BtbeBucket { pub fn new() -> StdResult { Ok(Self { capacity: BTBE_CAPACITY, - entries: [ - StoredEntry::new(&CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; BTBE_CAPACITY as usize - ] + entries: [StoredEntry::new(&CanonicalAddr::from(&IMPOSSIBLE_ADDR))?; + BTBE_CAPACITY as usize], }) } @@ -250,7 +287,7 @@ impl BtbeBucket { return false; } - // has capacity for a new entry; save entry to bucket + // has capacity for a new entry; save entry to bucket self.entries[(BTBE_CAPACITY - self.capacity) as usize] = entry.clone(); // update capacity @@ -261,7 +298,10 @@ impl BtbeBucket { } /// Searches the bucket for an entry containing the given address - pub fn constant_time_find_address(&self, address: &CanonicalAddr) -> Option<(usize, StoredEntry)> { + pub fn constant_time_find_address( + &self, + address: &CanonicalAddr, + ) -> Option<(usize, StoredEntry)> { let address = address.as_slice(); // contant-time only applies to this part, so that the index of the entry cannot be distinguished @@ -301,7 +341,9 @@ impl BitwiseTrieNode { BTBE_BUCKETS_COUNT.save(storage, &buckets_count)?; // save bucket to storage - BTBE_BUCKETS.add_suffix(&bucket_id.to_be_bytes()).save(storage, &bucket)?; + BTBE_BUCKETS + .add_suffix(&bucket_id.to_be_bytes()) + .save(storage, &bucket)?; // create new node Ok(Self { @@ -314,27 +356,40 @@ impl BitwiseTrieNode { // loads the node's bucket from storage pub fn bucket(self, storage: &dyn Storage) -> StdResult { if self.bucket == 0 { - return Err(StdError::generic_err("btbe: attempted to load bucket of branch node")); + return Err(StdError::generic_err( + "btbe: attempted to load bucket of branch node", + )); } // load bucket from storage - BTBE_BUCKETS.add_suffix(&self.bucket.to_be_bytes()).load(storage) + BTBE_BUCKETS + .add_suffix(&self.bucket.to_be_bytes()) + .load(storage) } // stores the bucket associated with this node fn set_and_save_bucket(self, storage: &mut dyn Storage, bucket: BtbeBucket) -> StdResult<()> { if self.bucket == 0 { - return Err(StdError::generic_err("btbe: attempted to store a bucket to a branch node")); + return Err(StdError::generic_err( + "btbe: attempted to store a bucket to a branch node", + )); } - BTBE_BUCKETS.add_suffix(&self.bucket.to_be_bytes()).save(storage, &bucket) + BTBE_BUCKETS + .add_suffix(&self.bucket.to_be_bytes()) + .save(storage, &bucket) } } /// Determines whether a given entry belongs in the left node (true) or right node (false) fn entry_belongs_in_left_node(secret: &[u8], entry: StoredEntry, bit_pos: u8) -> StdResult { // create key bytes - let key_bytes = hkdf_sha_256(&Some(BUCKETING_SALT_BYTES.to_vec()), secret, entry.address_slice(), 32)?; + let key_bytes = hkdf_sha_256( + &Some(BUCKETING_SALT_BYTES.to_vec()), + secret, + entry.address_slice(), + 32, + )?; // convert to u258 let key_u256 = U256::from_big_endian(&key_bytes); @@ -344,17 +399,27 @@ fn entry_belongs_in_left_node(secret: &[u8], entry: StoredEntry, bit_pos: u8) -> } /// Locates a btbe node given an address; returns tuple of (node, node_id, bit position) -pub fn locate_btbe_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult<(BitwiseTrieNode, u64, u8)> { +pub fn locate_btbe_node( + storage: &dyn Storage, + address: &CanonicalAddr, +) -> StdResult<(BitwiseTrieNode, u64, u8)> { // load internal contract secret let secret = INTERNAL_SECRET.load(storage)?; let secret = secret.as_slice(); // create key bytes - let hash = hkdf_sha_256(&Some(BUCKETING_SALT_BYTES.to_vec()), secret, address.as_slice(), 32)?; - + let hash = hkdf_sha_256( + &Some(BUCKETING_SALT_BYTES.to_vec()), + secret, + address.as_slice(), + 32, + )?; + // start at root of trie let mut node_id: u64 = 1; - let mut node = BTBE_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).load(storage)?; + let mut node = BTBE_TRIE_NODES + .add_suffix(&node_id.to_be_bytes()) + .load(storage)?; let mut bit_pos: u8 = 0; // while the node has children @@ -366,10 +431,16 @@ pub fn locate_btbe_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdRe bit_pos += 1; // choose left or right child depending on bit value - node_id = if bit_value == 0 { node.left } else { node.right }; + node_id = if bit_value == 0 { + node.left + } else { + node.right + }; // load child node - node = BTBE_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).load(storage)?; + node = BTBE_TRIE_NODES + .add_suffix(&node_id.to_be_bytes()) + .load(storage)?; } Ok((node, node_id, bit_pos)) @@ -378,17 +449,23 @@ pub fn locate_btbe_node(storage: &dyn Storage, address: &CanonicalAddr) -> StdRe /// Does a binary search on the append store to find the bundle where the `start_idx` tx can be found. /// For a paginated search `start_idx` = `page` * `page_size`. /// Returns the bundle index, the bundle, and the index in the bundle list to start at -pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_idx: u32) -> StdResult> { +pub fn find_start_bundle( + storage: &dyn Storage, + account: &CanonicalAddr, + start_idx: u32, +) -> StdResult> { let (node, _, _) = locate_btbe_node(storage, account)?; let bucket = node.bucket(storage)?; if let Some((_, entry)) = bucket.constant_time_find_address(account) { let mut left = 0u32; let mut right = entry.history_len()?; - + while left <= right { let mid = (left + right) / 2; let mid_bundle = entry.get_tx_bundle_at(storage, mid)?; - if start_idx >= mid_bundle.offset && start_idx < mid_bundle.offset + (mid_bundle.list_len as u32) { + if start_idx >= mid_bundle.offset + && start_idx < mid_bundle.offset + (mid_bundle.list_len as u32) + { // we have the correct bundle // which index in list to start at? let start_at = (mid_bundle.list_len as u32) - (start_idx - mid_bundle.offset) - 1; @@ -405,7 +482,10 @@ pub fn find_start_bundle(storage: &dyn Storage, account: &CanonicalAddr, start_i } /// gets the StoredEntry for a given account -pub fn stored_entry(storage: &dyn Storage, account: &CanonicalAddr) -> StdResult> { +pub fn stored_entry( + storage: &dyn Storage, + account: &CanonicalAddr, +) -> StdResult> { let (node, _, _) = locate_btbe_node(storage, account)?; let bucket = node.bucket(storage)?; Ok(bucket.constant_time_find_address(account).map(|b| b.1)) @@ -433,17 +513,15 @@ pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> St Ok(0) } - // merges a dwb entry into the current node's bucket // `spent_amount` is any required subtraction due to being sender of tx pub fn merge_dwb_entry( storage: &mut dyn Storage, dwb_entry: &DelayedWriteBufferEntry, amount_spent: Option, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("merge_dwb_entry.1"); // locate the node that the given entry belongs in @@ -456,14 +534,21 @@ pub fn merge_dwb_entry( let mut bucket_id = node.bucket; // search for an existing entry - if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(&dwb_entry.recipient()?) { + if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(&dwb_entry.recipient()?) + { // found existing entry // merge amount and history from dwb entry found_entry.merge_dwb_entry(storage, &dwb_entry, amount_spent)?; bucket.entries[idx] = found_entry; - #[cfg(feature="gas_tracking")] - group1.logf(format!("@merged {} into node #{}, bucket #{} at position {} ", dwb_entry.recipient()?, node_id, bucket_id, idx)); + #[cfg(feature = "gas_tracking")] + group1.logf(format!( + "@merged {} into node #{}, bucket #{} at position {} ", + dwb_entry.recipient()?, + node_id, + bucket_id, + idx + )); // save updated bucket to storage node.set_and_save_bucket(storage, bucket)?; @@ -476,11 +561,18 @@ pub fn merge_dwb_entry( let secret = INTERNAL_SECRET.load(storage)?; let secret = secret.as_slice(); - loop { // looping as many times as needed until the bucket has capacity for a new entry + loop { + // looping as many times as needed until the bucket has capacity for a new entry // try to add to the current bucket if bucket.add_entry(&btbe_entry) { - #[cfg(feature="gas_tracking")] - group1.logf(format!("@inserted into node #{}, bucket #{} (bitpos: {}) at position {}", node_id, bucket_id, bit_pos, BTBE_CAPACITY - bucket.capacity - 1)); + #[cfg(feature = "gas_tracking")] + group1.logf(format!( + "@inserted into node #{}, bucket #{} (bitpos: {}) at position {}", + node_id, + bucket_id, + bit_pos, + BTBE_CAPACITY - bucket.capacity - 1 + )); // bucket has capacity and it added the new entry // save bucket to storage @@ -506,7 +598,9 @@ pub fn merge_dwb_entry( // save left node's bucket to storage, recycling this node's bucket ID let left_bucket_id = node.bucket; - BTBE_BUCKETS.add_suffix(&left_bucket_id.to_be_bytes()).save(storage, &left_bucket)?; + BTBE_BUCKETS + .add_suffix(&left_bucket_id.to_be_bytes()) + .save(storage, &left_bucket)?; // global count of buckets let mut buckets_count = BTBE_BUCKETS_COUNT.load(storage).unwrap_or_default(); @@ -514,7 +608,9 @@ pub fn merge_dwb_entry( // bucket ID for right node buckets_count += 1; let right_bucket_id = buckets_count; - BTBE_BUCKETS.add_suffix(&right_bucket_id.to_be_bytes()).save(storage, &right_bucket)?; + BTBE_BUCKETS + .add_suffix(&right_bucket_id.to_be_bytes()) + .save(storage, &right_bucket)?; // save updated count BTBE_BUCKETS_COUNT.save(storage, &buckets_count)?; @@ -546,8 +642,12 @@ pub fn merge_dwb_entry( }; // save left and right node to storage - BTBE_TRIE_NODES.add_suffix(&left_id.to_be_bytes()).save(storage, &left)?; - BTBE_TRIE_NODES.add_suffix(&right_id.to_be_bytes()).save(storage, &right)?; + BTBE_TRIE_NODES + .add_suffix(&left_id.to_be_bytes()) + .save(storage, &left)?; + BTBE_TRIE_NODES + .add_suffix(&right_id.to_be_bytes()) + .save(storage, &right)?; // convert this into a branch node node.left = left_id; @@ -555,10 +655,15 @@ pub fn merge_dwb_entry( node.bucket = 0; // save node - BTBE_TRIE_NODES.add_suffix(&node_id.to_be_bytes()).save(storage, &node)?; + BTBE_TRIE_NODES + .add_suffix(&node_id.to_be_bytes()) + .save(storage, &node)?; - #[cfg(feature="gas_tracking")] - group1.logf(format!("@split node #{}, bucket #{} at bitpos {}, ", node_id, bucket_id, bit_pos)); + #[cfg(feature = "gas_tracking")] + group1.logf(format!( + "@split node #{}, bucket #{} at bitpos {}, ", + node_id, bucket_id, bit_pos + )); // route entry if entry_belongs_in_left_node(secret, btbe_entry, bit_pos)? { @@ -586,12 +691,14 @@ pub fn merge_dwb_entry( pub fn initialize_btbe(storage: &mut dyn Storage) -> StdResult<()> { let bucket = BtbeBucket::new()?; let node = BitwiseTrieNode::new_leaf(storage, bucket)?; - + // save count BTBE_TRIE_NODES_COUNT.save(storage, &1)?; - + // save root node to storage - BTBE_TRIE_NODES.add_suffix(&1_u64.to_be_bytes()).save(storage, &node)?; + BTBE_TRIE_NODES + .add_suffix(&1_u64.to_be_bytes()) + .save(storage, &node)?; Ok(()) } @@ -601,9 +708,10 @@ mod tests { use std::any::Any; use crate::contract::instantiate; - use crate::dwb::ZERO_ADDR; use crate::msg::{InitialBalance, InstantiateMsg, QueryAnswer}; - use cosmwasm_std::{from_binary, testing::*, Addr, Api, Binary, OwnedDeps, QueryResponse, Response, Uint128}; + use cosmwasm_std::{ + from_binary, testing::*, Addr, Api, Binary, OwnedDeps, QueryResponse, Response, Uint128, + }; use super::*; @@ -719,11 +827,14 @@ mod tests { assert_eq!(btbe_node_count, 1); let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &canonical).unwrap(); - assert_eq!(node, BitwiseTrieNode { - left: 0, - right: 0, - bucket: 2, - }); + assert_eq!( + node, + BitwiseTrieNode { + left: 0, + right: 0, + bucket: 2, + } + ); assert_eq!(node_id, 1); assert_eq!(bit_pos, 0); } @@ -744,25 +855,31 @@ mod tests { let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap(); assert_eq!(btbe_node_count, 3); let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &canonical).unwrap(); - assert_eq!(node, BitwiseTrieNode { - left: 0, - right: 0, - bucket: 3, - }); + assert_eq!( + node, + BitwiseTrieNode { + left: 0, + right: 0, + bucket: 3, + } + ); assert_eq!(node_id, 3); assert_eq!(bit_pos, 1); - + // have other addresses been moved to new nodes let first = deps .api .addr_canonicalize(Addr::unchecked(format!("1zzzzzz")).as_str()) .unwrap(); let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &first).unwrap(); - assert_eq!(node, BitwiseTrieNode { - left: 0, - right: 0, - bucket: 2, - }); + assert_eq!( + node, + BitwiseTrieNode { + left: 0, + right: 0, + bucket: 2, + } + ); assert_eq!(node_id, 2); assert_eq!(bit_pos, 1); @@ -771,11 +888,14 @@ mod tests { .addr_canonicalize(Addr::unchecked(format!("2zzzzzz")).as_str()) .unwrap(); let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &second).unwrap(); - assert_eq!(node, BitwiseTrieNode { - left: 0, - right: 0, - bucket: 3, - }); + assert_eq!( + node, + BitwiseTrieNode { + left: 0, + right: 0, + bucket: 3, + } + ); assert_eq!(node_id, 3); assert_eq!(bit_pos, 1); @@ -785,7 +905,14 @@ mod tests { assert_eq!(first_entry.balance().unwrap(), 0); let second_entry = stored_entry(&deps.storage, &second).unwrap().unwrap(); assert_eq!(second_entry.balance().unwrap(), 0); - let not_entry = stored_entry(&deps.storage, &deps.api.addr_canonicalize(Addr::unchecked("alice".to_string()).as_str()).unwrap()).unwrap(); + let not_entry = stored_entry( + &deps.storage, + &deps + .api + .addr_canonicalize(Addr::unchecked("alice".to_string()).as_str()) + .unwrap(), + ) + .unwrap(); assert_eq!(not_entry, None); } -} \ No newline at end of file +} diff --git a/src/contract.rs b/src/contract.rs index b35fe3cd..a1f08fc6 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,7 +1,8 @@ /// This contract implements SNIP-20 standard: /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md use cosmwasm_std::{ - entry_point, to_binary, Addr, Api, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128 + entry_point, to_binary, Addr, Api, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, + Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128, }; use secret_toolkit::notification::hkdf_sha_256; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; @@ -11,24 +12,28 @@ use secret_toolkit_crypto::{sha_256, ContractPrng}; use crate::batch; -use crate::dwb::{DelayedWriteBuffer, DWB, TX_NODES}; -#[cfg(feature="gas_tracking")] +#[cfg(feature = "gas_tracking")] use crate::dwb::log_dwb; +use crate::dwb::{DelayedWriteBuffer, DWB, TX_NODES}; +use crate::btbe::{ + find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count, +}; use crate::gas_tracker::{GasTracker, LoggingExt}; use crate::msg::Evaporator; use crate::msg::{ - AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, - ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, + AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, + InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, }; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, AllowancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, PRNG, TOTAL_SUPPLY + safe_add, AllowancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, + CONTRACT_STATUS, INTERNAL_SECRET, PRNG, TOTAL_SUPPLY, }; -use crate::btbe::{find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count}; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ - store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx + store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, + store_transfer_action, Tx, }; /// We make sure that responses from `handle` are padded to a multiple of this size. @@ -102,18 +107,18 @@ pub fn instantiate( for balance in initial_balances { let amount = balance.amount.u128(); let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker = GasTracker::new(deps.api); perform_mint( - deps.storage, - &mut rng, - &raw_admin, - &balance_address, + deps.storage, + &mut rng, + &raw_admin, + &balance_address, amount, msg.symbol.clone(), Some("Initial Balance".to_string()), &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -176,11 +181,9 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::SetContractStatus { level, .. } => { set_contract_status(deps, info, level) } - ExecuteMsg::Redeem { - amount, - denom, - .. - } if contract_status == ContractStatusLevel::StopAllButRedeems => { + ExecuteMsg::Redeem { amount, denom, .. } + if contract_status == ContractStatusLevel::StopAllButRedeems => + { try_redeem(deps, env, info, amount, denom) } _ => Err(StdError::generic_err( @@ -194,14 +197,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S let response = match msg.clone() { // Native - ExecuteMsg::Deposit { .. } => { - try_deposit(deps, env, info, &mut rng) - } - ExecuteMsg::Redeem { - amount, - denom, - .. - } => try_redeem(deps, env, info, amount, denom), + ExecuteMsg::Deposit { .. } => try_deposit(deps, env, info, &mut rng), + ExecuteMsg::Redeem { amount, denom, .. } => try_redeem(deps, env, info, amount, denom), // Base ExecuteMsg::Transfer { @@ -209,15 +206,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => try_transfer( - deps, - env, - info, - &mut rng, - recipient, - amount, - memo, - ), + } => try_transfer(deps, env, info, &mut rng, recipient, amount, memo), ExecuteMsg::Send { recipient, recipient_code_hash, @@ -239,14 +228,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::BatchTransfer { actions, .. } => { try_batch_transfer(deps, env, info, &mut rng, actions) } - ExecuteMsg::BatchSend { actions, .. } => { - try_batch_send(deps, env, info, &mut rng, actions) - } - ExecuteMsg::Burn { - amount, - memo, - .. - } => try_burn(deps, env, info, amount, memo), + ExecuteMsg::BatchSend { actions, .. } => try_batch_send(deps, env, info, &mut rng, actions), + ExecuteMsg::Burn { amount, memo, .. } => try_burn(deps, env, info, amount, memo), ExecuteMsg::RegisterReceive { code_hash, .. } => { try_register_receive(deps, info, code_hash) } @@ -272,16 +255,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => try_transfer_from( - deps, - &env, - info, - &mut rng, - owner, - recipient, - amount, - memo, - ), + } => try_transfer_from(deps, &env, info, &mut rng, owner, recipient, amount, memo), ExecuteMsg::SendFrom { owner, recipient, @@ -313,17 +287,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => try_burn_from( - deps, - &env, - info, - owner, - amount, - memo, - ), - ExecuteMsg::BatchBurnFrom { actions, .. } => { - try_batch_burn_from(deps, &env, info, actions) - } + } => try_burn_from(deps, &env, info, owner, amount, memo), + ExecuteMsg::BatchBurnFrom { actions, .. } => try_batch_burn_from(deps, &env, info, actions), // Mint ExecuteMsg::Mint { @@ -331,18 +296,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => try_mint( - deps, - env, - info, - &mut rng, - recipient, - amount, - memo, - ), - ExecuteMsg::BatchMint { actions, .. } => { - try_batch_mint(deps, env, info, &mut rng, actions) - } + } => try_mint(deps, env, info, &mut rng, recipient, amount, memo), + ExecuteMsg::BatchMint { actions, .. } => try_batch_mint(deps, env, info, &mut rng, actions), // Other ExecuteMsg::ChangeAdmin { address, .. } => change_admin(deps, info, address), @@ -372,9 +327,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::ExchangeRate {} => query_exchange_rate(deps.storage), QueryMsg::Minters { .. } => query_minters(deps), QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query), - - #[cfg(feature="gas_tracking")] - QueryMsg::Dwb { } => log_dwb(deps.storage), + + #[cfg(feature = "gas_tracking")] + QueryMsg::Dwb {} => log_dwb(deps.storage), _ => viewing_keys_queries(deps, msg), }, @@ -409,10 +364,7 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result< QueryWithPermit::TransferHistory { .. } => { return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)); } - QueryWithPermit::TransactionHistory { - page, - page_size, - } => { + QueryWithPermit::TransactionHistory { page, page_size } => { if !permit.check_permission(&TokenPermissions::History) { return Err(StdError::generic_err(format!( "No permission to query history, got permissions {:?}", @@ -420,12 +372,7 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result< ))); } - query_transactions( - deps, - account, - page.unwrap_or(0), - page_size, - ) + query_transactions(deps, account, page.unwrap_or(0), page_size) } QueryWithPermit::Allowance { owner, spender } => { if !permit.check_permission(&TokenPermissions::Allowance) { @@ -502,18 +449,13 @@ pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult { QueryMsg::Balance { address, .. } => query_balance(deps, address), QueryMsg::TransferHistory { .. } => { return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)); - }, + } QueryMsg::TransactionHistory { address, page, page_size, .. - } => query_transactions( - deps, - address, - page.unwrap_or(0), - page_size, - ), + } => query_transactions(deps, address, page.unwrap_or(0), page_size), QueryMsg::Allowance { owner, spender, .. } => query_allowance(deps, owner, spender), QueryMsg::AllowancesGiven { owner, @@ -623,10 +565,13 @@ pub fn query_transactions( let dwb_index = dwb.recipient_match(&account_raw); let mut txs_in_dwb = vec![]; let txs_in_dwb_count = dwb.entries[dwb_index].list_len()?; - if dwb_index > 0 && txs_in_dwb_count > 0 && start < txs_in_dwb_count as u32 { // skip if start is after buffer entries + if dwb_index > 0 && txs_in_dwb_count > 0 && start < txs_in_dwb_count as u32 { + // skip if start is after buffer entries let head_node_index = dwb.entries[dwb_index].head_node()?; if head_node_index > 0 { - let head_node = TX_NODES.add_suffix(&head_node_index.to_be_bytes()).load(deps.storage)?; + let head_node = TX_NODES + .add_suffix(&head_node_index.to_be_bytes()) + .load(deps.storage)?; txs_in_dwb = head_node.to_vec(deps.storage, deps.api)?; } } @@ -658,10 +603,14 @@ pub fn query_transactions( let mut bundle_idx = tx_bundles_idx_len - 1; loop { let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; - let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; let list_len = tx_bundle.list_len as u32; if txs_left <= list_len { - txs.extend_from_slice(&head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize]); + txs.extend_from_slice( + &head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize], + ); break; } txs.extend(head_node.to_vec(deps.storage, deps.api)?); @@ -681,20 +630,24 @@ pub fn query_transactions( // bundle tx offsets are chronological, but we need reverse chronological // so get the settled start index as if order is reversed //println!("OPTION 3"); - let settled_start = settled_tx_count.saturating_sub(start - txs_in_dwb_count).saturating_sub(1); - - if let Some((bundle_idx, tx_bundle, start_at)) = find_start_bundle( - deps.storage, - &account_raw, - settled_start - )? { + let settled_start = settled_tx_count + .saturating_sub(start - txs_in_dwb_count) + .saturating_sub(1); + + if let Some((bundle_idx, tx_bundle, start_at)) = + find_start_bundle(deps.storage, &account_raw, settled_start)? + { let mut txs_left = end - start; - let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; let list_len = tx_bundle.list_len as u32; if start_at + txs_left <= list_len { // this first bundle has all the txs we need - txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..(start_at + txs_left) as usize].to_vec(); + txs = head_node.to_vec(deps.storage, deps.api)? + [start_at as usize..(start_at + txs_left) as usize] + .to_vec(); } else { // get the rest of the txs in this bundle and then go back through history txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); @@ -705,11 +658,17 @@ pub fn query_transactions( let mut bundle_idx = bundle_idx - 1; if let Some(entry) = account_stored_entry { loop { - let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; - let head_node = TX_NODES.add_suffix(&tx_bundle.head_node.to_be_bytes()).load(deps.storage)?; + let tx_bundle = + entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; let list_len = tx_bundle.list_len as u32; if txs_left <= list_len { - txs.extend_from_slice(&head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize]); + txs.extend_from_slice( + &head_node.to_vec(deps.storage, deps.api)? + [0..txs_left as usize], + ); break; } txs.extend(head_node.to_vec(deps.storage, deps.api)?); @@ -721,7 +680,7 @@ pub fn query_transactions( } } } - } + } } } } @@ -837,8 +796,7 @@ fn try_mint_impl( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { let raw_amount = amount.u128(); let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; @@ -853,7 +811,7 @@ fn try_mint_impl( denom, memo, block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] tracker, )?; @@ -891,7 +849,7 @@ fn try_mint( let minted_amount = safe_add(&mut total_supply, amount.u128()); TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency @@ -904,14 +862,13 @@ fn try_mint( constants.symbol, memo, &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; - let resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); + let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); Ok(resp) @@ -947,7 +904,7 @@ fn try_batch_mint( let recipient = deps.api.addr_validate(action.recipient.as_str())?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); try_mint_impl( @@ -959,7 +916,7 @@ fn try_batch_mint( constants.symbol.clone(), action.memo, &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; } @@ -1134,24 +1091,23 @@ fn try_deposit( let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); perform_deposit( deps.storage, - rng, + rng, &sender_address, raw_amount, "uscrt".to_string(), &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; - let resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?); + let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?); - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); Ok(resp) @@ -1191,12 +1147,7 @@ fn try_redeem( let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; let amount_raw = amount.u128(); - let tx_id = store_redeem_action( - deps.storage, - amount.u128(), - constants.symbol, - &env.block, - )?; + let tx_id = store_redeem_action(deps.storage, amount.u128(), constants.symbol, &env.block)?; // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; @@ -1205,12 +1156,12 @@ fn try_redeem( // settle the signer's account in buffer dwb.settle_sender_or_owner_account( - deps.storage, - &sender_address, - tx_id, + deps.storage, + &sender_address, + tx_id, amount_raw, - "redeem", - #[cfg(feature="gas_tracking")] + "redeem", + #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -1259,8 +1210,7 @@ fn try_transfer_impl( denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { let raw_sender = deps.api.addr_canonicalize(sender.as_str())?; let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; @@ -1275,7 +1225,7 @@ fn try_transfer_impl( denom, memo, block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] tracker, )?; @@ -1296,7 +1246,7 @@ fn try_transfer( let symbol = CONFIG.load(deps.storage)?.symbol; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); try_transfer_impl( @@ -1308,20 +1258,19 @@ fn try_transfer( symbol, memo, &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("try_transfer.rest"); - let resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); - - #[cfg(feature="gas_tracking")] + let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + + #[cfg(feature = "gas_tracking")] group1.log("rest"); - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); Ok(resp) @@ -1336,7 +1285,7 @@ fn try_batch_transfer( ) -> StdResult { let symbol = CONFIG.load(deps.storage)?.symbol; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); for action in actions { @@ -1350,15 +1299,14 @@ fn try_batch_transfer( symbol.clone(), action.memo, &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; } - let resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); Ok(resp) @@ -1407,8 +1355,7 @@ fn try_send_impl( memo: Option, msg: Option, block: &cosmwasm_std::BlockInfo, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { try_transfer_impl( deps, @@ -1419,7 +1366,7 @@ fn try_send_impl( denom, memo.clone(), block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] tracker, )?; @@ -1455,7 +1402,7 @@ fn try_send( let mut messages = vec![]; let symbol = CONFIG.load(deps.storage)?.symbol; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); try_send_impl( @@ -1470,7 +1417,7 @@ fn try_send( memo, msg, &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -1478,7 +1425,7 @@ fn try_send( .add_messages(messages) .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); Ok(resp) @@ -1494,7 +1441,7 @@ fn try_batch_send( let mut messages = vec![]; let symbol = CONFIG.load(deps.storage)?.symbol; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); for action in actions { @@ -1511,7 +1458,7 @@ fn try_batch_send( action.memo, action.msg, &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; } @@ -1582,7 +1529,7 @@ fn try_transfer_from_impl( use_allowance(deps.storage, env, owner, spender, raw_amount)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); perform_transfer( @@ -1595,7 +1542,7 @@ fn try_transfer_from_impl( denom, memo, &env.block, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -1797,40 +1744,41 @@ fn try_burn_from( let raw_burner = deps.api.addr_canonicalize(info.sender.as_str())?; let tx_id = store_burn_action( - deps.storage, + deps.storage, raw_owner.clone(), - raw_burner.clone(), + raw_burner.clone(), raw_amount, constants.symbol, - memo, - &env.block + memo, + &env.block, )?; // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker = GasTracker::new(deps.api); // settle the owner's account in buffer dwb.settle_sender_or_owner_account( - deps.storage, - &raw_owner, - tx_id, - raw_amount, - "burn", - #[cfg(feature="gas_tracking")] + deps.storage, + &raw_owner, + tx_id, + raw_amount, + "burn", + #[cfg(feature = "gas_tracking")] &mut tracker, )?; - if raw_burner != raw_owner { // also settle sender's account + if raw_burner != raw_owner { + // also settle sender's account dwb.settle_sender_or_owner_account( - deps.storage, - &raw_burner, - tx_id, + deps.storage, + &raw_burner, + tx_id, 0, - "burn", - #[cfg(feature="gas_tracking")] - &mut tracker, + "burn", + #[cfg(feature = "gas_tracking")] + &mut tracker, )?; } @@ -1874,43 +1822,43 @@ fn try_batch_burn_from( use_allowance(deps.storage, env, &owner, &info.sender, amount)?; let tx_id = store_burn_action( - deps.storage, + deps.storage, raw_owner.clone(), - raw_spender.clone(), + raw_spender.clone(), amount, constants.symbol.clone(), - action.memo.clone(), - &env.block + action.memo.clone(), + &env.block, )?; - + // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker = GasTracker::new(deps.api); - + // settle the owner's account in buffer dwb.settle_sender_or_owner_account( - deps.storage, - &raw_owner, - tx_id, - amount, + deps.storage, + &raw_owner, + tx_id, + amount, "burn", - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] &mut tracker, )?; if raw_spender != raw_owner { dwb.settle_sender_or_owner_account( - deps.storage, - &raw_spender, - tx_id, - 0, - "burn", - #[cfg(feature="gas_tracking")] + deps.storage, + &raw_spender, + tx_id, + 0, + "burn", + #[cfg(feature = "gas_tracking")] &mut tracker, )?; } - + DWB.save(deps.storage, &dwb)?; // remove from supply @@ -2100,29 +2048,29 @@ fn try_burn( let raw_burn_address = deps.api.addr_canonicalize(info.sender.as_str())?; let tx_id = store_burn_action( - deps.storage, + deps.storage, + raw_burn_address.clone(), raw_burn_address.clone(), - raw_burn_address.clone(), raw_amount, constants.symbol, - memo, - &env.block + memo, + &env.block, )?; // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut tracker = GasTracker::new(deps.api); // settle the signer's account in buffer dwb.settle_sender_or_owner_account( - deps.storage, - &raw_burn_address, - tx_id, - raw_amount, - "burn", - #[cfg(feature="gas_tracking")] + deps.storage, + &raw_burn_address, + tx_id, + raw_amount, + "burn", + #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -2151,67 +2099,66 @@ fn perform_transfer( denom: String, memo: Option, block: &BlockInfo, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("perform_transfer.1"); // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_transfer_action(store, from, sender, to, amount, denom, memo, block)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.log("@store_transfer_action"); // load delayed write buffer let mut dwb = DWB.load(store)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.log("DWB.load"); let transfer_str = "transfer"; // settle the owner's account dwb.settle_sender_or_owner_account( - store, - from, - tx_id, - amount, + store, + from, + tx_id, + amount, transfer_str, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] tracker, )?; // if this is a *_from action, settle the sender's account, too if sender != from { dwb.settle_sender_or_owner_account( - store, - sender, - tx_id, - 0, - transfer_str, - #[cfg(feature="gas_tracking")] + store, + sender, + tx_id, + 0, + transfer_str, + #[cfg(feature = "gas_tracking")] tracker, )?; } // add the tx info for the recipient to the buffer dwb.add_recipient( - store, - rng, - to, - tx_id, - amount, - #[cfg(feature="gas_tracking")] + store, + rng, + to, + tx_id, + amount, + #[cfg(feature = "gas_tracking")] tracker, )?; - - #[cfg(feature="gas_tracking")] + + #[cfg(feature = "gas_tracking")] let mut group2 = tracker.group("perform_transfer.2"); DWB.save(store, &dwb)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group2.log("DWB.save"); Ok(()) @@ -2226,8 +2173,7 @@ fn perform_mint( denom: String, memo: Option, block: &BlockInfo, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_mint_action(store, minter, to, amount, denom, memo, block)?; @@ -2238,24 +2184,24 @@ fn perform_mint( // if minter is not recipient, settle them if minter != to { dwb.settle_sender_or_owner_account( - store, - minter, - tx_id, - 0, + store, + minter, + tx_id, + 0, "mint", - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] tracker, )?; } // add the tx info for the recipient to the buffer dwb.add_recipient( - store, - rng, - to, - tx_id, - amount, - #[cfg(feature="gas_tracking")] + store, + rng, + to, + tx_id, + amount, + #[cfg(feature = "gas_tracking")] tracker, )?; @@ -2271,8 +2217,7 @@ fn perform_deposit( amount: u128, denom: String, block: &BlockInfo, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { // first store the tx information in the global append list of txs and get the new tx id let tx_id = store_deposit_action(store, amount, denom, block)?; @@ -2282,12 +2227,12 @@ fn perform_deposit( // add the tx info for the recipient to the buffer dwb.add_recipient( - store, - rng, - to, - tx_id, + store, + rng, + to, + tx_id, amount, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] tracker, )?; @@ -2343,9 +2288,8 @@ mod tests { use std::any::Any; use cosmwasm_std::{ - testing::*, Api, - from_binary, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, - SubMsg, Timestamp, TransactionInfo, WasmMsg, + from_binary, testing::*, Api, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, + QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, WasmMsg, }; use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; @@ -2591,7 +2535,7 @@ mod tests { init_result.err().unwrap() ); - /* + /* let (init_result, _deps) = init_helper(vec![ InitialBalance { address: "lebron".to_string(), @@ -2653,7 +2597,10 @@ mod tests { .addr_canonicalize(Addr::unchecked("alice").as_str()) .unwrap(); - assert_eq!(5000 - 1000, stored_balance(&deps.storage, &bob_addr).unwrap()); + assert_eq!( + 5000 - 1000, + stored_balance(&deps.storage, &bob_addr).unwrap() + ); // alice has not been settled yet assert_ne!(1000, stored_balance(&deps.storage, &alice_addr).unwrap()); @@ -2665,10 +2612,10 @@ mod tests { let alice_entry = dwb.entries[2]; assert_eq!(1, alice_entry.list_len().unwrap()); assert_eq!(1000, alice_entry.amount().unwrap()); - // the id of the head_node + // the id of the head_node assert_eq!(4, alice_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); - assert_eq!(2, tx_count); + assert_eq!(2, tx_count); //let tx_node1 = TX_NODES.add_suffix(&1u64.to_be_bytes()).load(&deps.storage).unwrap(); //println!("tx node 1: {tx_node1:?}"); @@ -2696,7 +2643,10 @@ mod tests { .addr_canonicalize(Addr::unchecked("charlie").as_str()) .unwrap(); - assert_eq!(5000 - 1000 - 100, stored_balance(&deps.storage, &bob_addr).unwrap()); + assert_eq!( + 5000 - 1000 - 100, + stored_balance(&deps.storage, &bob_addr).unwrap() + ); // alice has not been settled yet assert_ne!(1000, stored_balance(&deps.storage, &alice_addr).unwrap()); // charlie has not been settled yet @@ -2710,10 +2660,10 @@ mod tests { let charlie_entry = dwb.entries[3]; assert_eq!(1, charlie_entry.list_len().unwrap()); assert_eq!(100, charlie_entry.amount().unwrap()); - // the id of the head_node + // the id of the head_node assert_eq!(6, charlie_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); - assert_eq!(3, tx_count); + assert_eq!(3, tx_count); // send another 500 to alice from bob let handle_msg = ExecuteMsg::Transfer { @@ -2731,7 +2681,10 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(5000 - 1000 - 100 - 500, stored_balance(&deps.storage, &bob_addr).unwrap()); + assert_eq!( + 5000 - 1000 - 100 - 500, + stored_balance(&deps.storage, &bob_addr).unwrap() + ); // make sure alice has not been settled yet assert_ne!(1500, stored_balance(&deps.storage, &alice_addr).unwrap()); @@ -2743,49 +2696,50 @@ mod tests { let alice_entry = dwb.entries[2]; assert_eq!(2, alice_entry.list_len().unwrap()); assert_eq!(1500, alice_entry.amount().unwrap()); - // the id of the head_node + // the id of the head_node assert_eq!(8, alice_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); - assert_eq!(4, tx_count); + assert_eq!(4, tx_count); // convert head_node to vec - let alice_nodes = TX_NODES.add_suffix( - &alice_entry - .head_node().unwrap() - .to_be_bytes()).load(&deps.storage).unwrap() - .to_vec(&deps.storage, &deps.api).unwrap(); + let alice_nodes = TX_NODES + .add_suffix(&alice_entry.head_node().unwrap().to_be_bytes()) + .load(&deps.storage) + .unwrap() + .to_vec(&deps.storage, &deps.api) + .unwrap(); let expected_alice_nodes: Vec = vec![ - Tx { - id: 4, - action: TxAction::Transfer { + Tx { + id: 4, + action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, + recipient: Addr::unchecked("alice"), + }, coins: Coin { - amount: Uint128::from(500_u128), + amount: Uint128::from(500_u128), denom: "SECSEC".to_string(), }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 2, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 2, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), }, coins: Coin { - amount: Uint128::from(1000_u128), + amount: Uint128::from(1000_u128), denom: "SECSEC".to_string(), }, - memo: None, - block_time: 1571797419, - block_height: 12345 - } + memo: None, + block_time: 1571797419, + block_height: 12345, + }, ]; assert_eq!(alice_nodes, expected_alice_nodes); @@ -2810,7 +2764,10 @@ mod tests { .addr_canonicalize(Addr::unchecked("ernie").as_str()) .unwrap(); - assert_eq!(5000 - 1000 - 100 - 500 - 200, stored_balance(&deps.storage, &bob_addr).unwrap()); + assert_eq!( + 5000 - 1000 - 100 - 500 - 200, + stored_balance(&deps.storage, &bob_addr).unwrap() + ); // alice has not been settled yet assert_ne!(1500, stored_balance(&deps.storage, &alice_addr).unwrap()); // charlie has not been settled yet @@ -2827,10 +2784,10 @@ mod tests { let ernie_entry = dwb.entries[4]; assert_eq!(1, ernie_entry.list_len().unwrap()); assert_eq!(200, ernie_entry.amount().unwrap()); - // the id of the head_node + // the id of the head_node assert_eq!(10, ernie_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); - assert_eq!(5, tx_count); + assert_eq!(5, tx_count); // now alice sends 50 to dora // this should settle alice and create entry for dora @@ -2854,7 +2811,10 @@ mod tests { .unwrap(); // alice has been settled - assert_eq!(1500 - 50, stored_balance(&deps.storage, &alice_addr).unwrap()); + assert_eq!( + 1500 - 50, + stored_balance(&deps.storage, &alice_addr).unwrap() + ); // dora has not been settled assert_ne!(50, stored_balance(&deps.storage, &dora_addr).unwrap()); @@ -2867,7 +2827,7 @@ mod tests { let dora_entry = dwb.entries[5]; assert_eq!(1, dora_entry.list_len().unwrap()); assert_eq!(50, dora_entry.amount().unwrap()); - // the id of the head_node + // the id of the head_node assert_eq!(12, dora_entry.head_node().unwrap()); let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(6, tx_count); @@ -2885,13 +2845,16 @@ mod tests { }; let info = mock_info("bob", &[]); let mut env = mock_env(); - env.block.random = Some(Binary::from(&[255-i; 32])); + env.block.random = Some(Binary::from(&[255 - i; 32])); let handle_result = execute(deps.as_mut(), env, info, handle_msg); let result = handle_result.unwrap(); assert!(ensure_success(result)); } - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59, stored_balance(&deps.storage, &bob_addr).unwrap()); + assert_eq!( + 5000 - 1000 - 100 - 500 - 200 - 59, + stored_balance(&deps.storage, &bob_addr).unwrap() + ); let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2916,7 +2879,10 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1, stored_balance(&deps.storage, &bob_addr).unwrap()); + assert_eq!( + 5000 - 1000 - 100 - 500 - 200 - 59 - 1, + stored_balance(&deps.storage, &bob_addr).unwrap() + ); //let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2938,7 +2904,10 @@ mod tests { let result = handle_result.unwrap(); assert!(ensure_success(result)); - assert_eq!(5000 - 1000 - 100 - 500 - 200 - 59 - 1 - 1, stored_balance(&deps.storage, &bob_addr).unwrap()); + assert_eq!( + 5000 - 1000 - 100 - 500 - 200 - 59 - 1 - 1, + stored_balance(&deps.storage, &bob_addr).unwrap() + ); //let dwb = DWB.load(&deps.storage).unwrap(); //println!("DWB: {dwb:?}"); @@ -2956,14 +2925,17 @@ mod tests { let info = mock_info("bob", &[]); let mut env = mock_env(); - env.block.random = Some(Binary::from(&[125-i; 32])); + env.block.random = Some(Binary::from(&[125 - i; 32])); let handle_result = execute(deps.as_mut(), env, info, handle_msg); let result = handle_result.unwrap(); assert!(ensure_success(result)); // alice should not settle - assert_eq!(1500 - 50, stored_balance(&deps.storage, &alice_addr).unwrap()); + assert_eq!( + 1500 - 50, + stored_balance(&deps.storage, &alice_addr).unwrap() + ); } // alice sends 1 to dora to settle @@ -2998,7 +2970,7 @@ mod tests { let info = mock_info("bob", &[]); let mut env = mock_env(); - env.block.random = Some(Binary::from(&[200-i; 32])); + env.block.random = Some(Binary::from(&[200 - i; 32])); let handle_result = execute(deps.as_mut(), env, info, handle_msg); let result = handle_result.unwrap(); @@ -3035,7 +3007,7 @@ mod tests { assert_eq!(balance, Uint128::new(3999)); // now we use alice to check query transaction history pagination works - + // // check last 3 transactions for alice (all in dwb) // @@ -3052,55 +3024,56 @@ mod tests { }; //println!("transfers: {transfers:?}"); let expected_transfers = vec![ - Tx { - id: 168, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(50u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 167, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(49u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 166, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(48u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 + Tx { + id: 168, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(50u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 167, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(49u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 166, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(48u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, }, ]; assert_eq!(transfers, expected_transfers); // - // check 6 transactions for alice that span over end of the 50 in dwb and settled + // check 6 transactions for alice that span over end of the 50 in dwb and settled // page: 8, page size: 6 // start is index 48 // @@ -3117,96 +3090,96 @@ mod tests { }; //println!("transfers: {transfers:?}"); let expected_transfers = vec![ - Tx { - id: 120, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(2u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 119, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { + Tx { + id: 120, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { denom: "SECSEC".to_string(), - amount: Uint128::from(1u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 118, - action: TxAction::Transfer { - from: Addr::unchecked("alice"), - sender: Addr::unchecked("alice"), - recipient: Addr::unchecked("dora") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(1u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 117, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(50u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 116, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(49u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 115, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(48u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - } + amount: Uint128::from(2u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 119, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(1u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 118, + action: TxAction::Transfer { + from: Addr::unchecked("alice"), + sender: Addr::unchecked("alice"), + recipient: Addr::unchecked("dora"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(1u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 117, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(50u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 116, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(49u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 115, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(48u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, ]; assert_eq!(transfers, expected_transfers); @@ -3231,82 +3204,81 @@ mod tests { }; //println!("transfers: {transfers:?}"); let expected_transfers = vec![ - Tx { - id: 69, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(2u128) - }, - memo: None, - block_time: - 1571797419, - block_height: 12345 - }, - Tx { - id: 68, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(1u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 6, - action: TxAction::Transfer { - from: Addr::unchecked("alice"), - sender: Addr::unchecked("alice"), - recipient: Addr::unchecked("dora") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(50u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 4, - action: TxAction::Transfer { + Tx { + id: 69, + action: TxAction::Transfer { from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(500u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - }, - Tx { - id: 2, - action: TxAction::Transfer { - from: Addr::unchecked("bob"), - sender: Addr::unchecked("bob"), - recipient: Addr::unchecked("alice") - }, - coins: Coin { - denom: "SECSEC".to_string(), - amount: Uint128::from(1000u128) - }, - memo: None, - block_time: 1571797419, - block_height: 12345 - } + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(2u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 68, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(1u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 6, + action: TxAction::Transfer { + from: Addr::unchecked("alice"), + sender: Addr::unchecked("alice"), + recipient: Addr::unchecked("dora"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(50u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 4, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(500u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, + Tx { + id: 2, + action: TxAction::Transfer { + from: Addr::unchecked("bob"), + sender: Addr::unchecked("bob"), + recipient: Addr::unchecked("alice"), + }, + coins: Coin { + denom: "SECSEC".to_string(), + amount: Uint128::from(1000u128), + }, + memo: None, + block_time: 1571797419, + block_height: 12345, + }, ]; //let transfers_len = transfers.len(); //println!("transfers.len(): {transfers_len}"); @@ -3803,9 +3775,15 @@ mod tests { height: 12_345, time: Timestamp::from_seconds(1_571_797_420), chain_id: "cosmos-testnet-14002".to_string(), - random: Some(Binary::from(&[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31])), + random: Some(Binary::from(&[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + ])), }, - transaction: Some(TransactionInfo { index: 3, hash: "1010".to_string()}), + transaction: Some(TransactionInfo { + index: 3, + hash: "1010".to_string(), + }), contract: ContractInfo { address: Addr::unchecked(MOCK_CONTRACT_ADDR.to_string()), code_hash: "".to_string(), @@ -5654,10 +5632,10 @@ mod tests { let init_config: InitConfig = from_binary(&Binary::from( format!( "{{\"public_total_supply\":{}, - \"enable_deposit\":{}, - \"enable_redeem\":{}, - \"enable_mint\":{}, - \"enable_burn\":{}}}", + \"enable_deposit\":{}, + \"enable_redeem\":{}, + \"enable_mint\":{}, + \"enable_burn\":{}}}", true, true, false, false, false ) .as_bytes(), @@ -6534,5 +6512,4 @@ mod tests { assert_eq!(transfers, expected_transfers); } - } diff --git a/src/dwb.rs b/src/dwb.rs index 57e1f9a6..9703db14 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,16 +1,16 @@ -use std::env::{var}; use constant_time_eq::constant_time_eq; +use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, Storage}; use rand::RngCore; +use secret_toolkit::storage::Item; use secret_toolkit_crypto::ContractPrng; -use serde::{Serialize, Deserialize,}; +use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, Storage}; -use secret_toolkit::storage::Item; +use std::env::var; +use crate::btbe::{merge_dwb_entry, stored_balance}; use crate::gas_tracker::GasTracker; use crate::msg::QueryAnswer; -use crate::state::{safe_add, safe_add_u64,}; -use crate::btbe::{merge_dwb_entry, stored_balance}; +use crate::state::{safe_add, safe_add_u64}; use crate::transaction_history::{Tx, TRANSACTIONS}; include!(concat!(env!("OUT_DIR"), "/config.rs")); @@ -18,8 +18,6 @@ include!(concat!(env!("OUT_DIR"), "/config.rs")); pub const KEY_DWB: &[u8] = b"dwb"; pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt"; pub const KEY_TX_NODES: &[u8] = b"dwb-tx-nodes"; -pub const KEY_ACCOUNT_TXS: &[u8] = b"dwb-acc-txs"; -pub const KEY_ACCOUNT_TX_COUNT: &[u8] = b"dwb-acc-tx-cnt"; pub static DWB: Item = Item::new(KEY_DWB); // use with add_suffix tx id (u64) @@ -30,8 +28,10 @@ pub static TX_NODES_COUNT: Item = Item::new(KEY_TX_NODES_COUNT); fn store_new_tx_node(store: &mut dyn Storage, tx_node: TxNode) -> StdResult { // tx nodes ids serialized start at 1 let tx_nodes_serial_id = TX_NODES_COUNT.load(store).unwrap_or_default() + 1; - TX_NODES.add_suffix(&tx_nodes_serial_id.to_be_bytes()).save(store, &tx_node)?; - TX_NODES_COUNT.save(store,&(tx_nodes_serial_id))?; + TX_NODES + .add_suffix(&tx_nodes_serial_id.to_be_bytes()) + .save(store, &tx_node)?; + TX_NODES_COUNT.save(store, &(tx_nodes_serial_id))?; Ok(tx_nodes_serial_id) } @@ -59,8 +59,8 @@ pub fn random_in_range(rng: &mut ContractPrng, a: u32, b: u32) -> StdResult loop { // this loop will almost always run only once since range_size << u64::MAX let random_u64 = rng.next_u64(); - if random_u64 < threshold { - return Ok((random_u64 % range_size) as u32 + a) + if random_u64 < threshold { + return Ok((random_u64 % range_size) as u32 + a); } } } @@ -70,9 +70,8 @@ impl DelayedWriteBuffer { Ok(Self { empty_space_counter: DWB_LEN - 1, // first entry is a dummy entry for constant-time writing - entries: [ - DelayedWriteBufferEntry::new(&CanonicalAddr::from(&ZERO_ADDR))?; DWB_LEN as usize - ] + entries: [DelayedWriteBufferEntry::new(&CanonicalAddr::from(&ZERO_ADDR))?; + DWB_LEN as usize], }) } @@ -85,19 +84,15 @@ impl DelayedWriteBuffer { tx_id: u64, amount_spent: u128, op_name: &str, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("settle_sender_or_owner_account.1"); // release the address from the buffer - let (balance, mut dwb_entry) = self.release_dwb_recipient( - store, - address - )?; + let (balance, mut dwb_entry) = self.release_dwb_recipient(store, address)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.log("release_dwb_recipient"); if balance.checked_sub(amount_spent).is_none() { @@ -108,21 +103,25 @@ impl DelayedWriteBuffer { dwb_entry.add_tx_node(store, tx_id)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.log("add_tx_node"); let mut entry = dwb_entry.clone(); entry.set_recipient(address)?; - #[cfg(feature="gas_tracking")] - group1.logf(format!("@entry=address:{}, amount:{}", entry.recipient()?, entry.amount()?)); + #[cfg(feature = "gas_tracking")] + group1.logf(format!( + "@entry=address:{}, amount:{}", + entry.recipient()?, + entry.amount()? + )); let result = merge_dwb_entry( - store, - &entry, + store, + &entry, Some(amount_spent), - #[cfg(feature="gas_tracking")] - tracker + #[cfg(feature = "gas_tracking")] + tracker, ); result @@ -131,9 +130,9 @@ impl DelayedWriteBuffer { /// "releases" a given recipient from the buffer, removing their entry if one exists /// returns the new balance and the buffer entry fn release_dwb_recipient( - &mut self, - store: &mut dyn Storage, - address: &CanonicalAddr + &mut self, + store: &mut dyn Storage, + address: &CanonicalAddr, ) -> StdResult<(u128, DelayedWriteBufferEntry)> { // get the address' stored balance let mut balance = stored_balance(store, address)?; @@ -175,66 +174,79 @@ impl DelayedWriteBuffer { recipient: &CanonicalAddr, tx_id: u64, amount: u128, - #[cfg(feature="gas_tracking")] - tracker: &mut GasTracker<'a>, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker<'a>, ) -> StdResult<()> { - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("add_recipient.1"); // check if `recipient` is already a recipient in the delayed write buffer let recipient_index = self.recipient_match(recipient); - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.log("recipient_match"); // the new entry will either derive from a prior entry for the recipient or the dummy entry let mut new_entry = self.entries[recipient_index].clone(); new_entry.set_recipient(recipient)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.log("set_recipient"); new_entry.add_tx_node(store, tx_id)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.log("add_tx_node"); new_entry.add_amount(amount)?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.log("add_amount"); // whether or not recipient is in the buffer (non-zero index) // casting to i32 will never overflow, so long as dwb length is limited to a u16 value let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32); - #[cfg(feature="gas_tracking")] - group1.logf(format!("@if_recipient_in_buffer: {}", if_recipient_in_buffer)); + #[cfg(feature = "gas_tracking")] + group1.logf(format!( + "@if_recipient_in_buffer: {}", + if_recipient_in_buffer + )); // whether or not the buffer is fully saturated yet let if_undersaturated = constant_time_is_not_zero(self.empty_space_counter as i32); - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.logf(format!("@if_undersaturated: {}", if_undersaturated)); // find the next empty entry in the buffer let next_empty_index = (DWB_LEN - self.empty_space_counter) as usize; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group1.logf(format!("@next_empty_index: {}", next_empty_index)); // which entry to settle (not yet considering if recipient's entry has capacity in history list) // if recipient is in buffer or buffer is undersaturated then settle the dummy entry // otherwise, settle a random entry let presumptive_settle_index = constant_time_if_else( - if_recipient_in_buffer, 0, - constant_time_if_else(if_undersaturated, 0, - random_in_range(rng, 1, DWB_LEN as u32)? as usize)); - #[cfg(feature="gas_tracking")] - group1.logf(format!("@presumptive_settle_index: {}", presumptive_settle_index)); + if_recipient_in_buffer, + 0, + constant_time_if_else( + if_undersaturated, + 0, + random_in_range(rng, 1, DWB_LEN as u32)? as usize, + ), + ); + #[cfg(feature = "gas_tracking")] + group1.logf(format!( + "@presumptive_settle_index: {}", + presumptive_settle_index + )); // check if we have any open slots in the linked list - let if_list_can_grow = constant_time_is_not_zero((DWB_MAX_TX_EVENTS - self.entries[recipient_index].list_len()?) as i32); - #[cfg(feature="gas_tracking")] + let if_list_can_grow = constant_time_is_not_zero( + (DWB_MAX_TX_EVENTS - self.entries[recipient_index].list_len()?) as i32, + ); + #[cfg(feature = "gas_tracking")] group1.logf(format!("@if_list_can_grow: {}", if_list_can_grow)); // if we would overflow the list by updating the existing entry, then just settle that recipient - let actual_settle_index = constant_time_if_else(if_list_can_grow, presumptive_settle_index, recipient_index); - #[cfg(feature="gas_tracking")] + let actual_settle_index = + constant_time_if_else(if_list_can_grow, presumptive_settle_index, recipient_index); + #[cfg(feature = "gas_tracking")] group1.logf(format!("@actual_settle_index: {}", actual_settle_index)); // where to write the new/replacement entry @@ -242,10 +254,11 @@ impl DelayedWriteBuffer { // otherwise, if buffer is undersaturated then put new entry at next open slot // otherwise, the buffer is saturated so replace the entry that is getting settled let write_index = constant_time_if_else( - if_recipient_in_buffer, recipient_index, - constant_time_if_else(if_undersaturated, next_empty_index, - actual_settle_index)); - #[cfg(feature="gas_tracking")] + if_recipient_in_buffer, + recipient_index, + constant_time_if_else(if_undersaturated, next_empty_index, actual_settle_index), + ); + #[cfg(feature = "gas_tracking")] group1.logf(format!("@write_index: {}", write_index)); // settle the entry @@ -254,14 +267,14 @@ impl DelayedWriteBuffer { store, &dwb_entry, None, - #[cfg(feature="gas_tracking")] - tracker + #[cfg(feature = "gas_tracking")] + tracker, )?; - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] let mut group2 = tracker.group("add_recipient.2"); - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] group2.log("merge_dwb_entry"); // write the new entry, which either overwrites the existing one for the same recipient, @@ -272,14 +285,16 @@ impl DelayedWriteBuffer { self.empty_space_counter -= constant_time_if_else( if_undersaturated, constant_time_if_else(if_recipient_in_buffer, 0, 1), - 0 + 0, ) as u16; - #[cfg(feature="gas_tracking")] - group2.logf(format!("@empty_space_counter: {}", self.empty_space_counter)); + #[cfg(feature = "gas_tracking")] + group2.logf(format!( + "@empty_space_counter: {}", + self.empty_space_counter + )); Ok(()) } - } const U16_BYTES: usize = 2; @@ -290,20 +305,21 @@ const U128_BYTES: usize = 16; const DWB_RECIPIENT_BYTES: usize = 54; // because mock_api creates rando canonical addr that is 54 bytes long #[cfg(not(test))] const DWB_RECIPIENT_BYTES: usize = 20; -const DWB_AMOUNT_BYTES: usize = 8; // Max 16 (u128) -const DWB_HEAD_NODE_BYTES: usize = 5; // Max 8 (u64) -const DWB_LIST_LEN_BYTES: usize = 2; // u16 +const DWB_AMOUNT_BYTES: usize = 8; // Max 16 (u128) +const DWB_HEAD_NODE_BYTES: usize = 5; // Max 8 (u64) +const DWB_LIST_LEN_BYTES: usize = 2; // u16 const_assert!(DWB_AMOUNT_BYTES <= U128_BYTES); const_assert!(DWB_HEAD_NODE_BYTES <= U64_BYTES); const_assert!(DWB_LIST_LEN_BYTES <= U16_BYTES); -const DWB_ENTRY_BYTES: usize = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES + DWB_LIST_LEN_BYTES; +const DWB_ENTRY_BYTES: usize = + DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES + DWB_LIST_LEN_BYTES; pub const ZERO_ADDR: [u8; DWB_RECIPIENT_BYTES] = [0u8; DWB_RECIPIENT_BYTES]; /// A delayed write buffer entry consists of the following bytes in this order: -/// +/// /// // recipient canonical address /// recipient - 20 bytes /// // for sscrt w/ 6 decimals u64 is good for > 18 trillion tokens, far exceeding supply @@ -314,14 +330,11 @@ pub const ZERO_ADDR: [u8; DWB_RECIPIENT_BYTES] = [0u8; DWB_RECIPIENT_BYTES]; /// head_node - 5 bytes /// // length of list (limited to 65535) /// list_len - 2 byte -/// +/// /// total: 35 bytes #[derive(Serialize, Deserialize, Clone, Copy, Debug)] #[cfg_attr(test, derive(Eq, PartialEq))] -pub struct DelayedWriteBufferEntry( - #[serde(with = "BigArray")] - [u8; DWB_ENTRY_BYTES] -); +pub struct DelayedWriteBufferEntry(#[serde(with = "BigArray")] [u8; DWB_ENTRY_BYTES]); impl DelayedWriteBufferEntry { pub fn new(recipient: &CanonicalAddr) -> StdResult { @@ -331,9 +344,7 @@ impl DelayedWriteBufferEntry { } let mut result = [0u8; DWB_ENTRY_BYTES]; result[..DWB_RECIPIENT_BYTES].copy_from_slice(recipient); - Ok(Self { - 0: result - }) + Ok(Self { 0: result }) } pub fn recipient_slice(&self) -> &[u8] { @@ -423,7 +434,7 @@ impl DelayedWriteBufferEntry { self.set_head_node(new_node)?; // increment the node list length self.set_list_len(self.list_len()? + 1)?; - + Ok(new_node) } @@ -505,18 +516,20 @@ fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { (then * condition as usize) | (els * (1 - condition as usize)) } -#[cfg(feature="gas_tracking")] +#[cfg(feature = "gas_tracking")] pub fn log_dwb(storage: &dyn Storage) -> StdResult { let dwb = DWB.load(storage)?; - to_binary(&QueryAnswer::Dwb { dwb: format!("{:?}", dwb) }) + to_binary(&QueryAnswer::Dwb { + dwb: format!("{:?}", dwb), + }) } #[cfg(test)] mod tests { - use cosmwasm_std::{testing::*, Binary, Response, Uint128, OwnedDeps}; use crate::contract::instantiate; - use crate::msg::{InstantiateMsg, InitialBalance}; + use crate::msg::{InitialBalance, InstantiateMsg}; use crate::transaction_history::{append_new_stored_tx, StoredTxAction}; + use cosmwasm_std::{testing::*, Binary, OwnedDeps, Response, Uint128}; use super::*; @@ -562,7 +575,10 @@ mod tests { let mut dwb_entry = DelayedWriteBufferEntry::new(&recipient).unwrap(); assert_eq!(dwb_entry, DelayedWriteBufferEntry([0u8; DWB_ENTRY_BYTES])); - assert_eq!(dwb_entry.recipient().unwrap(), CanonicalAddr::from(ZERO_ADDR)); + assert_eq!( + dwb_entry.recipient().unwrap(), + CanonicalAddr::from(ZERO_ADDR) + ); assert_eq!(dwb_entry.amount().unwrap(), 0u64); assert_eq!(dwb_entry.head_node().unwrap(), 0u64); assert_eq!(dwb_entry.list_len().unwrap(), 0u16); @@ -573,7 +589,10 @@ mod tests { dwb_entry.set_head_node(1).unwrap(); dwb_entry.set_list_len(1).unwrap(); - assert_eq!(dwb_entry.recipient().unwrap(), CanonicalAddr::from(&[1u8; DWB_RECIPIENT_BYTES])); + assert_eq!( + dwb_entry.recipient().unwrap(), + CanonicalAddr::from(&[1u8; DWB_RECIPIENT_BYTES]) + ); assert_eq!(dwb_entry.amount().unwrap(), 1u64); assert_eq!(dwb_entry.head_node().unwrap(), 1u64); assert_eq!(dwb_entry.list_len().unwrap(), 1u16); @@ -582,15 +601,19 @@ mod tests { let storage = deps.as_mut().storage; let from = CanonicalAddr::from(&[2u8; 20]); let sender = CanonicalAddr::from(&[2u8; 20]); - let to = CanonicalAddr::from(&[1u8;20]); - let action = StoredTxAction::transfer( - from.clone(), - sender.clone(), - to.clone() - ); - let tx_id = append_new_stored_tx(storage, &action, 1000u128, "uscrt".to_string(), Some("memo".to_string()), &env.block).unwrap(); + let to = CanonicalAddr::from(&[1u8; 20]); + let action = StoredTxAction::transfer(from.clone(), sender.clone(), to.clone()); + let tx_id = append_new_stored_tx( + storage, + &action, + 1000u128, + "uscrt".to_string(), + Some("memo".to_string()), + &env.block, + ) + .unwrap(); let result = dwb_entry.add_tx_node(storage, tx_id).unwrap(); assert_eq!(dwb_entry.head_node().unwrap(), result); } -} \ No newline at end of file +} diff --git a/src/gas_tracker.rs b/src/gas_tracker.rs index ec5cf7d3..8783e630 100644 --- a/src/gas_tracker.rs +++ b/src/gas_tracker.rs @@ -34,23 +34,20 @@ impl<'a> GasTracker<'a> { pub fn add_to_response(self, resp: Response) -> Response { let mut new_resp = resp.clone(); for log in self.logs.into_iter() { - new_resp = new_resp.add_attribute_plaintext( - log.0, - log.1 - ); + new_resp = new_resp.add_attribute_plaintext(log.0, log.1); } new_resp } } pub trait LoggingExt { - fn add_gas_tracker(&self, tracker: GasTracker) -> Response; + fn add_gas_tracker(&self, tracker: GasTracker) -> Response; } impl LoggingExt for Response { - fn add_gas_tracker(&self, tracker: GasTracker) -> Response { - tracker.add_to_response(self.to_owned()) - } + fn add_gas_tracker(&self, tracker: GasTracker) -> Response { + tracker.add_to_response(self.to_owned()) + } } pub struct GasGroup<'a, 'b> { @@ -67,7 +64,7 @@ impl<'a, 'b> GasGroup<'a, 'b> { index: 0, } } - + pub fn mark(&mut self) { self.log(""); } @@ -76,7 +73,12 @@ impl<'a, 'b> GasGroup<'a, 'b> { let gas = self.tracker.api.check_gas(); let log_entry = ( format!("gas.{}", self.name,), - format!("{}:{}:{}", self.index, gas.unwrap_or(0u64).to_string(), comment), + format!( + "{}:{}:{}", + self.index, + gas.unwrap_or(0u64).to_string(), + comment + ), ); self.tracker.logs.push(log_entry); self.index += 1; @@ -85,4 +87,4 @@ impl<'a, 'b> GasGroup<'a, 'b> { pub fn logf(&mut self, comment: String) { self.log(comment.as_str()) } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 1334fcbf..0713f569 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,12 +2,12 @@ extern crate static_assertions as sa; mod batch; +mod btbe; pub mod contract; +mod dwb; +mod gas_tracker; pub mod msg; pub mod receiver; pub mod state; -mod transaction_history; -mod dwb; -mod btbe; mod strings; -mod gas_tracker; +mod transaction_history; diff --git a/src/msg.rs b/src/msg.rs index 3d31afa7..b3afff7f 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -245,12 +245,12 @@ pub enum ExecuteMsg { padding: Option, }, /// Add deposit/redeem support for these coin denoms - AddSupportedDenoms { + AddSupportedDenoms { denoms: Vec, gas_target: Option, }, /// Remove deposit/redeem support for these coin denoms - RemoveSupportedDenoms { + RemoveSupportedDenoms { denoms: Vec, gas_target: Option, }, @@ -463,8 +463,8 @@ pub enum QueryMsg { query: QueryWithPermit, }, - #[cfg(feature="gas_tracking")] - Dwb { }, + #[cfg(feature = "gas_tracking")] + Dwb {}, } impl QueryMsg { @@ -589,7 +589,7 @@ pub enum QueryAnswer { minters: Vec, }, - #[cfg(feature="gas_tracking")] + #[cfg(feature = "gas_tracking")] Dwb { dwb: String, }, diff --git a/src/state.rs b/src/state.rs index 0529eccc..7874dd9c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -237,4 +237,4 @@ impl ReceiverHashStore { } } -pub static INTERNAL_SECRET: Item> = Item::new(b"internal-secret"); \ No newline at end of file +pub static INTERNAL_SECRET: Item> = Item::new(b"internal-secret"); diff --git a/src/strings.rs b/src/strings.rs index 39c87bc5..71a574be 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1 +1,2 @@ -pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str = "`transfer_history` query is UNSUPPORTED. Use `transaction_history` instead."; \ No newline at end of file +pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str = + "`transfer_history` query is UNSUPPORTED. Use `transaction_history` instead."; diff --git a/src/transaction_history.rs b/src/transaction_history.rs index 2997d8e7..8f7c72cd 100644 --- a/src/transaction_history.rs +++ b/src/transaction_history.rs @@ -1,7 +1,9 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, Api, BlockInfo, CanonicalAddr, Coin, StdError, StdResult, Storage, Uint128}; +use cosmwasm_std::{ + Addr, Api, BlockInfo, CanonicalAddr, Coin, StdError, StdResult, Storage, Uint128, +}; use secret_toolkit::storage::Item; @@ -180,17 +182,17 @@ impl StoredTxAction { TxCode::Mint => { let minter = self.address1.ok_or_else(mint_addr_err)?; let recipient = self.address2.ok_or_else(mint_addr_err)?; - TxAction::Mint { - minter: api.addr_humanize(&minter)?, - recipient: api.addr_humanize(&recipient)? + TxAction::Mint { + minter: api.addr_humanize(&minter)?, + recipient: api.addr_humanize(&recipient)?, } } TxCode::Burn => { let burner = self.address1.ok_or_else(burn_addr_err)?; let owner = self.address2.ok_or_else(burn_addr_err)?; - TxAction::Burn { + TxAction::Burn { burner: api.addr_humanize(&burner)?, - owner: api.addr_humanize(&owner)? + owner: api.addr_humanize(&owner)?, } } TxCode::Deposit => TxAction::Deposit {}, @@ -249,7 +251,9 @@ pub fn append_new_stored_tx( block_height: block.height, }; - TRANSACTIONS.add_suffix(&serial_id.to_be_bytes()).save(store, &stored_tx)?; + TRANSACTIONS + .add_suffix(&serial_id.to_be_bytes()) + .save(store, &stored_tx)?; TX_COUNT.save(store, &(serial_id))?; Ok(serial_id) } @@ -265,11 +269,7 @@ pub fn store_transfer_action( memo: Option, block: &BlockInfo, ) -> StdResult { - let action = StoredTxAction::transfer( - owner.clone(), - sender.clone(), - receiver.clone() - ); + let action = StoredTxAction::transfer(owner.clone(), sender.clone(), receiver.clone()); append_new_stored_tx(store, &action, amount, denom, memo, block) } @@ -282,10 +282,7 @@ pub fn store_mint_action( memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult { - let action = StoredTxAction::mint( - minter.clone(), - recipient.clone() - ); + let action = StoredTxAction::mint(minter.clone(), recipient.clone()); append_new_stored_tx(store, &action, amount, denom, memo, block) } @@ -299,10 +296,7 @@ pub fn store_burn_action( memo: Option, block: &cosmwasm_std::BlockInfo, ) -> StdResult { - let action = StoredTxAction::burn( - owner, - burner - ); + let action = StoredTxAction::burn(owner, burner); append_new_stored_tx(store, &action, amount, denom, memo, block) } @@ -324,4 +318,4 @@ pub fn store_redeem_action( ) -> StdResult { let action = StoredTxAction::redeem(); append_new_stored_tx(store, &action, amount, denom, None, block) -} \ No newline at end of file +} From 4a91f5c95ebc9257c6374205c3b54de988e133c5 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 3 Aug 2024 04:47:06 +0000 Subject: [PATCH 57/87] dev: transfer from --- Makefile | 2 +- tests/dwb/bun.lockb | Bin 104964 -> 105725 bytes tests/dwb/package.json | 4 +- tests/dwb/src/gas-checker.ts | 4 +- tests/dwb/src/main.ts | 110 ++++++++++++++++++++++++++++++++--- tests/dwb/src/snip.ts | 19 ++++-- 6 files changed, 119 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 94933a74..9c425118 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ _compile: .PHONY: compile-integration _compile-integration compile-integration: _compile-integration contract.wasm.gz _compile-integration: - DWB_CAPACITY=8 BTBE_CAPACITY=8 RUSTFLAGS='-C link-arg=-s' cargo build --features "gas_tracking" --release --target wasm32-unknown-unknown + DWB_CAPACITY=64 BTBE_CAPACITY=64 RUSTFLAGS='-C link-arg=-s' cargo build --features "gas_tracking" --release --target wasm32-unknown-unknown @# The following line is not necessary, may work only on linux (extra size optimization) wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm diff --git a/tests/dwb/bun.lockb b/tests/dwb/bun.lockb index 7996f7887a96fd3f8edc269fb4591a32868b02bd..48417527d80933b514bd493b29e9f0231a69aadb 100755 GIT binary patch delta 5246 zcmZu#3s@A_6`r%O%7Cj9kk_K3O;k*vycYy^0R@Et@j*oL87<02ARtkE5o5sET5X~+ zxlJ#owb4&wKtaWoAc%^^_-JC4DD@GIF^L+C#!xlY5V8N=nd#TG-S7MN-2dD&=bm%! zxwAX->%Dy*HTPK`8hj_?p!b$%-CyaWR5g}QcliIREhxpOb$!{&i1M>T7B{$+{)r^< z8TeN2P{Sls!_wnMqp`0f^<#;Beo;9qMv}ZGsj7Ujy=0+Xl4>lHWCni|(jT%QR+4-n zUj`op=@F;$5=g;|p&tl21F}D4HlztMMUyd*{lG_SG6>Qg+((s_RR#9>i}Fh(X-;{0 z$ifA7drX4rptN9KNJ0L>{5fR>_K*ekf+Gl2IC`n9d?9S5(nQtKy!?4(MFoz)^uEJ+?v6%;K9nOjmey8&Dl zCMEghg{WKvE(rpE3ob;hRzV8;e8{IDi^r<2|7S^dxUX-eesw3To`CgnNKv6rebqp+ z5VfdCu(lKWjq1lC%bT5#?fazOw2G zIL2C4_feU_kS&V3@OAJ3;L9>qeg;x(Wg(EAv&k}6 zL8es_3m;~oNh~lFcwEmW@uf%FEy<1I8|_!P6pt1eOM7VDkek zhT(DSX0p||0*#%<>PN=Q$1p~V{P&iP$W68?Dn?EQ^3&FVo@)PyiQ|Av8eo4FrH-3Rpj&l@+DAUA9ei(Oaz4D_*>)_Fw_CX5v95Z7NO4hSd1f5Bx#y+jaM*2D~M7(WsyGt6aFN@4DMrD zafa3S8V*Mq3&~7y#b72Y&4@RqqUt%$(FTmt#E7o2TE#~1u`b1C{4`bght@DEjTL8F zX&$T1w8^#DS&@*DHTYVLU0?~UG$7va)Oc2$Wi_klfS}&6OD%*^|2V9&SKjqTd9I| zO}5bv7Cgmf7(S5|PqE6cA*dI0JNX+hwGSEwTMSQTv(ZznauLp=+Fzm*w}YYTaE$gh z-U4%OjByrrWs5!m}*I=;?1ZFJNH7-pXU$-R+e~cmHiZtbqHVd%?*kJ%33iD2e~`*{E6*yA1Xb3$D#&=fT$2DmW__z*cWJvBd2PonULW=d#!x zCUzU_V-~j~mwf}aafd=DSr=G!orz`DDRhc$sLN#&>P_qsSO-h5&t(t5>gyFc!ybaw z>@>0GcPiA$YIo+c>AOtKv`e9LEN@pXbK7lV2f!{cc{lO{E8VToXRH~lumSlsD0G<> zH6Xt|$Pera^WTH~z?Sb(aC1BXw$y?A914BKDjdjfFY*Js#)9`EKd`lX6}rJLfURys zevJxsu{Djz??dDV_AQJ15cz>^{7|9WtP8BV3HdcCbcbzdLVo*@A6Pd_--rCb>h~#h zmpuflX-0m{ifFZFeqg2h73yKlV1=#7uT`N(tf&?F z9YB6ykD31g;G^ZEac4V#+D@fHaZ$Qe@QH~Wkw_i7}jXS-LM)c6tx@LzdQf>I5Zjj!Z?ng~0Lr&MK)}!h0H$tk> zbU$f25n`>Td!*_5K~7Y4l~S*!6mjFO=KMUyx0n}kn*kA&Pzc=sU?v740Mc{=F`kF9 zaBP4SIeP;5xp3B_k7kEIZqj^=g?(SA4tG`jNs<<8N;l0=WV=Mu$(pWEYm)U|e1s4@Hy}W zpxa-^SojtG?qGfCI}C0EUBDFJ7H|Xje~aIQUf6vDh=s-YTR<$X>nkw+9uU4nMS6hw zz>k2a#y#^`cnFB|DaMcS5t)cB_7aA_z=g;HP82y=P%Ggt@TE>6zTc<%}XtX5#`!TIJ;F+SsM`U#-8kZ)<@VaP< zpjrG%G_9Z`JU@m~=`{Z!hMqO}Sa@#?`NinZzi|N*i#t3=UBTs%YE1eQ@`;;2T=~Zv zo6kCD!r)hmvG7a_`B4h*h$SCaXtH^w1qtd8-GL9PJoX-{TY*cWLc>IY`eXU5xq-`X zEcx>;%^7B1<|lEfS%1E-=x#j!nXUCz7=(o)l31yRQ!M#Y0XGZbWWKIZl1Ch$6pJ9< z(BpJdr$5h6=kZ%_2HorB3jizyU-;}az?E=^5&0Y$!OMJ4p?UzTF>5bLlH)c z9mYp9cIM|}M7X0ZJSUDa&H8JBr#rVS=y-9@4Ci{z>S*z*#o^z^(O^=`V+gYFC&y4G zmGb#x$lnl?z~2}{erEl#eZ$rnb)O!&u@tVN)vB%HhhS*dpYtCyb-y?*y>OUwEff~F zFn)Ior5fM2;wH-wKz}lXCG%I|!mPi2*m-IHtzqTQ1YmV-Q-AJ(iutufnoGxdmK8z&`}IOs zRNbQ8pWnh|7G#Cf+{0H`DblRJhnP3y?xD`}PsSN2Iy5FiWHp7KvQi_0ft@pzO*XW# zO8G4tJtO>~DC*zx}aeHr5qwjVkDGH9Yoa4>oTo zS%jI0&{&cFGX7{RRmi`^4>0})RGs0K<7loyp2!1J$?p%T6rhjI`WugxyX~h&$7MIe z2W}^!_zjuEvs01IM!qr?F*$cU5*u_Ujr{qLGztjR-+{c<)ZSh+Jbbl*a4T{y|6i@c zSHYZ4^37@Rr?rmiNNXy+X{L=wWIyjW_o@u%tx{^s5P$c)`K9qx;`_a(v}-Q{-4hnY zT7$y*CO`6TS~`K!qPQWOhV!l2G!#9%VmZdPYBDxme}z^B^3~5#AP-b%a9`YaLl@3{ J8Gll%{{=IieS!c0 delta 4788 zcmai13shBQ7C!spRS$Zx>mz~ddz{Rrkf3-#@N z=HwPeFtubB zDt8GEdVr6Cl!}dj6#E{Ke}wEiJg~v1LK`VO$0WXVnGfpE*F&OtRbEe(3{6JyOc!Ea~q zYRImTWoDobYrxxq4;ks_pu$tcb%DJocyr}Y{YC~PRC zbhJJ*5a6t-B}EGhHEq)9KqCvVK@ZH+n83z%og_%{FH)YeDYY4C-l`CNtFx*;WV55H z!hJM?J9ZjGncU#>(K6N(eR};MO+!W%8fW!1s2x`(`Y4(k@b@LwlYF#<)02FTqXs{k z9@#xTyh-Ovw%C4;r# zrVfU){SZwX#+lvX^>G;CgjNu{Lk)V88;1MzCg>9VE8U$8-Hqv?0YARj))sNfo_Zx1 z;ztBo-3(_PSS-7`#XB9ahx?xKl+2YQeEMQ=1dB~pb~I=k>nT3Rm7$!G;?)P^*o$}K ztT2NrxFN--S3_42*by};=JZsbGX}>zhci>-oiAcE&Of@0QHqT8ID{(^V}o#m9^%S0 zpI!?ET2;|TGS+_r!@)+V!ea3*NI0^C#A6Jg`IZkJC<5;hrgrH{FcKYjJfrHT0)o^?{iXXk(tKW%tE^xr) zB<6#m>~M_Z@7w_9?~Jn>+A_v}A__6`p9rdEeZ0Xd+%2k>j{neEHR;~x@B)rMYL8~z z^d{E-@Kd{yO`_NgmT1dChg#|*RpxhYc4(swv+q5p3Qf@?&8N^H>r+~ zgB=4)SZmTAu3VeVkF0g^1+aY_w=SDw*SYxlbtWC)b6{t|GS{2*Hm_cv&8ybC_$Ju9 zoW3EO(>Azx;|7x&_&V4%u*q+j^d7H!Bb(R0;bPZDlMeAc8?$-hMi$Zw}fSGfVK9xP&)N!Ph-7xLSM z{J?H--#X-1hy3bHy2;1Ej)5iYHXXM#esuQ)eq=Wi+-(wZ+#V#j2MO*mDTvR3odwI> zYm$>!??r-pksw$rPTz+F_aVW3Ch2?~>>AkQ{U){Hb^DRvek6FnBp2UvU^HJow4C?4 zTfDmB;B6vx`>UXsw;X+p%Bm8YFO%~q-m>7Tq~^OwY+Uv~vXAkPs|_bLe^WL0_+JJF zuWCb@JjdFq#0=`F3SD%kdJcaQx&$V)e_>XJK|l^5&lNy)Er2jUo*x0xIf03aLa0Oe zTFb>XYpNCGI!kxm(zS*hW$AvhbUGwnW&d*ne`Ol~bhR7|#Qo2f4j&|1A#~#7CLrOo z1@MC?A2Xvh!lW$5H<~sN16km(bRifouyjF|t{rp>EnN#s*V=^vo-S_nw+?hi&8&9x zVUYe5l=vmnJSwXLbyfu(sCRIhJ7BpqR4jYBTVj4EPz&q;w%0UuAYY5|9Wj;=#c?q3 zF~+|Dl9r_VJ^f%U)1kzRIWPb?37i6K`!g7e-xJth`woK(z(0Yr zz*yj0;A`NwHa`cw*nI=Y#xgz+$mX`b4C9M{_>zjqIh_Yw2c#Nu^yQWK0sH&2x&$fR zkqxC|;@Iw>bWi5wy}AsD19?HNTH{-Iq%yL05^xicifsb?=M*RA7RGVFWWctSgI+F& z`+g`zwgTi3i);xvf!2TyNJ%#WQg+#-Ep)3O<=4P&mjob3Q#Kbnv9Vit99*h#8nAWS zL#Y)gN|dTvdMGi-h#a8OJyaevATqk7X1V zGAa`7U9Mg>D2&#qoe*8@5BPld^P^taI{6gr21E{s9E`UhMtyHkXczkm@u&O3`{(aF zsKX*Ua$rnk6fQ}scPvFyH785y8 z0ys5Tt%#%082b{}aQ??rKP-RwC@e4!znXo``|DfgtF4WLSN%Z^91uB3BXu$k!H1{^ zhC+Chdk92d)epjBUn;d3yANJiG4C^fpwZ%QlsY|xZf_A4tIbq*#Z%|5F`9i7osvGi zO|Q!FpZYiV*T*s$q}i9#XFJuL4_~%xb|8vKeAW8>g;76sL$i0Jmz8Jf#Xabg^D`GH4Wx;@!tJw)S1G#ET0`;U?b;*mck9gI02^8wF@4SWGF7IhR z);rEYIFhll+O2weX&bT1Oox;IUZ#5cDBRMii9UMHb1^Y+S3dkz-P~1AT#*u^&EjXS zYM1yw4sbpuC(=yM!@~nt$QKsuipW3WEXFbeCo(Epds>|eEW4RRVXA!+g*z^JRb&!{ zl3S%FA+lN}Q<6#OFr4@RRk_2czuugzX_fdc{?(*<7|o#nbRtH@3`Zx@RKAEw>iOX) zq{VuLNgupnQBTZMO!kILe<7p z823`|rurkikqSqFUa0;3QT)DYaT;bwZAtsJol>H&(@>1%Dl8qdR`dL}VW=BAI?!(0 z;dBcBf9)R_+&T@vfOyi< xg_a > xg_b? xg_a - xg_b: xg_b - xg_a; - const delta_color = (xg_delta: bigint, nl_pad=0) => (bigint_abs(xg_delta) >= 1n ? bigint_abs(xg_delta) > 2n ? SX_ANSI_RED diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index b41ee8d3..962439d6 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -1,9 +1,14 @@ -import type {Snip24} from '@solar-republic/contractor'; +import type {Dict} from '@blake.regalia/belt'; +import type {SecretAccAddr, Snip24} from '@solar-republic/contractor'; + +import type {CwUint128} from '@solar-republic/types'; import {readFileSync} from 'node:fs'; -import {bytes, bytes_to_base64, entries, sha256, text_to_bytes} from '@blake.regalia/belt'; -import {SecretApp, SecretContract, Wallet, random_32} from '@solar-republic/neutrino'; +import {bytes, bytes_to_base64, entries, sha256, text_to_bytes, bigint_greater} from '@blake.regalia/belt'; +import {encodeCosmosBankMsgSend, SI_MESSAGE_TYPE_COSMOS_BANK_MSG_SEND} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/tx'; +import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any'; +import {SecretApp, SecretContract, Wallet, broadcast_result, create_and_sign_tx_direct, random_32, type TxMeta} from '@solar-republic/neutrino'; import {BigNumber} from 'bignumber.js'; import {N_DECIMALS, P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; @@ -89,6 +94,7 @@ async function transfer_chain(sx_chain: string) { } { + // basic transfers between principals await transfer_chain(` 1 TKN Alice => Bob 2 TKN Alice => Carol @@ -98,8 +104,7 @@ async function transfer_chain(sx_chain: string) { 1 TKN David => Alice -- re-adds Alice to buffer; settles David for 1st time `); - console.log('#'.repeat(80)+'\n--- Subsequent operations should incur almost exactly the same gas now ---\n'+'#'.repeat(80)+'\n'); - + // extended transfers between principals await transfer_chain(` 1 TKN David => Bob 1 TKN David => Bob -- exact same transfer repeated @@ -110,22 +115,111 @@ async function transfer_chain(sx_chain: string) { `); + // gas checker ref let k_checker: GasChecker | null = null; - for(let i_sim=0; i_sim<700; i_sim++) { + // grant action from previous simultion + let f_grant: undefined | (() => Promise<[w_result: { + spender: SecretAccAddr; + owner: SecretAccAddr; + allowance: CwUint128; + } | undefined, xc_code: number, s_response: string, g_meta: TxMeta | undefined, h_events: Dict | undefined, si_txn: string | undefined]>); + + // number of simulations to perform + const N_SIMULATIONS = 300; + + // record maximum gas used for direct transfers + let xg_max_gas_used_transfer = 0n; + + // simulate many transfers + for(let i_sim=0; i_sim ${si_receiver}`); + // transfer some gas to sim account + const [atu8_raw,, si_txn] = await create_and_sign_tx_direct(k_wallet_b, [ + encodeGoogleProtobufAny( + SI_MESSAGE_TYPE_COSMOS_BANK_MSG_SEND, + encodeCosmosBankMsgSend(k_wallet_b.addr, k_wallet.addr, [[`${1_000000n}`, 'uscrt']]) + ), + ], [[`${5000n}`, 'uscrt']], 50_000n); + + // submit all in parallel + const [ + g_result_transfer, + [xc_send_gas, s_err_send_gas], + a_res_increase, + ] = await Promise.all([ + // @ts-expect-error secret app + transfer(k_dwbv, i_sim % 2? 1_000000n: 2_000000n, k_app_a, k_app_sim, k_checker), + broadcast_result(k_wallet, atu8_raw, si_txn), + f_grant?.(), + ]); + + // send gas error + if(xc_send_gas) { + throw Error(`Failed to transfer gas: ${s_err_send_gas}`); + } + + // increase allowance error + if(f_grant && a_res_increase?.[1]) { + throw Error(`Failed to increase allowance: ${a_res_increase[2]}`); + } + + // approve Alice as spender for future txs + f_grant = () => k_app_sim.exec('increase_allowance', { + spender: k_wallet_a.addr, + amount: `${1_000000n}` as CwUint128, + }, 60_000n); + + if(!k_checker) { + k_checker = new GasChecker(g_result_transfer.tracking, g_result_transfer.gasUsed); + } + + xg_max_gas_used_transfer = bigint_greater(xg_max_gas_used_transfer, g_result_transfer.gasUsed); + } + + // reset checker + k_checker = null; + + // record maximum gas used for transfer froms + let xg_max_gas_used_transfer_from = 0n; + + // perform transfer_from + for(let i_sim=N_SIMULATIONS-2; i_sim>0; i_sim--) { + const si_owner = i_sim+''; + const si_recipient = (i_sim - 1)+''; + + const k_wallet_owner = await Wallet(await sha256(text_to_bytes(si_owner)), 'secretdev-1', P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, 'secret'); + const k_wallet_recipient = await Wallet(await sha256(text_to_bytes(si_recipient)), 'secretdev-1', P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, 'secret'); + + const k_app_owner = SecretApp(k_wallet_owner, k_contract, X_GAS_PRICE); + const k_app_recipient = SecretApp(k_wallet_recipient, k_contract, X_GAS_PRICE); + + console.log(`${si_owner} --> ${si_recipient}`); + // @ts-expect-error secret app - const g_result = await transfer(k_dwbv, i_sim % 2? 1_000000n: 2_000000n, k_app_a, k_app_receiver, k_checker); + const g_result = await transfer(k_dwbv, 1_000000n, k_app_owner, k_app_recipient, k_checker, k_app_a); if(!k_checker) { k_checker = new GasChecker(g_result.tracking, g_result.gasUsed); } + + xg_max_gas_used_transfer_from = bigint_greater(xg_max_gas_used_transfer_from, g_result.gasUsed); } + + // report + console.log({ + xg_max_gas_used_transfer, + xg_max_gas_used_transfer_from, + }); + + // done + process.exit(0); } diff --git a/tests/dwb/src/snip.ts b/tests/dwb/src/snip.ts index b1b6058f..e223b76b 100644 --- a/tests/dwb/src/snip.ts +++ b/tests/dwb/src/snip.ts @@ -69,7 +69,8 @@ export async function transfer( xg_amount: bigint, k_app_owner: SecretApp, k_app_recipient: SecretApp, - k_checker?: Nilable + k_checker?: Nilable, + k_app_sender?: SecretApp ): Promise { const sa_owner = k_app_owner.wallet.addr; const sa_recipient = k_app_recipient.wallet.addr; @@ -90,13 +91,19 @@ export async function transfer( ]); // execute transfer - const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_owner.exec('transfer', { - amount: `${xg_amount}` as CwUint128, - recipient: sa_recipient, - }, 1000_000n); + const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = k_app_sender + ? await k_app_sender.exec('transfer_from', { + owner: k_app_owner.wallet.addr, + amount: `${xg_amount}` as CwUint128, + recipient: sa_recipient, + }, 1_000000n) + : await k_app_owner.exec('transfer', { + amount: `${xg_amount}` as CwUint128, + recipient: sa_recipient, + }, 1_000000n); // section header - console.log(`# Transfer ${BigNumber(xg_amount+'').shiftedBy(-N_DECIMALS).toFixed()} TKN ${H_ADDRS[sa_owner] || sa_owner} => ${H_ADDRS[sa_recipient] || sa_recipient} | ⏹ ${k_dwbv.empty} spaces | ⛽️ ${g_meta?.gas_used || '0'} gas used`); + console.log(`# Transfer ${BigNumber(xg_amount+'').shiftedBy(-N_DECIMALS).toFixed()} TKN ${H_ADDRS[sa_owner] || sa_owner}${k_app_sender? ` (via ${H_ADDRS[k_app_sender.wallet.addr] || k_app_sender.wallet.addr})`: ''} => ${H_ADDRS[sa_recipient] || sa_recipient} | ⏹ ${k_dwbv.empty} spaces | ⛽️ ${g_meta?.gas_used || '0'} gas used`); // query balance of owner and recipient again const [ From 3ad29b9af1b2d602d44f0163345685bc93437f71 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 3 Aug 2024 06:39:59 +0000 Subject: [PATCH 58/87] dev: gas evaporation --- .cargo/config | 12 +------ .cargo/config.toml | 11 ++++++ src/btbe.rs | 8 ++--- src/contract.rs | 5 +++ src/msg.rs | 69 +++++++++++++++++++----------------- tests/dwb/src/gas-checker.ts | 13 +++++-- tests/dwb/src/main.ts | 62 ++++++++++++++++++++++---------- tests/dwb/src/snip.ts | 4 +-- 8 files changed, 114 insertions(+), 70 deletions(-) mode change 100644 => 120000 .cargo/config create mode 100644 .cargo/config.toml diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 0e2770dc..00000000 --- a/.cargo/config +++ /dev/null @@ -1,11 +0,0 @@ -[alias] -# Temporarily removed the backtraces feature from the unit-test run due to compilation errors in -# the cosmwasm-std package: -# cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm", branch = "secret" } -# unit-test = "test --lib --features backtraces" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --example schema" - -[features] -gas_tracking = [] diff --git a/.cargo/config b/.cargo/config new file mode 120000 index 00000000..ab8b69cb --- /dev/null +++ b/.cargo/config @@ -0,0 +1 @@ +config.toml \ No newline at end of file diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..0e2770dc --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,11 @@ +[alias] +# Temporarily removed the backtraces feature from the unit-test run due to compilation errors in +# the cosmwasm-std package: +# cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm", branch = "secret" } +# unit-test = "test --lib --features backtraces" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --example schema" + +[features] +gas_tracking = [] diff --git a/src/btbe.rs b/src/btbe.rs index 94caa568..cf328a61 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -522,7 +522,7 @@ pub fn merge_dwb_entry( #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult<()> { #[cfg(feature = "gas_tracking")] - let mut group1 = tracker.group("merge_dwb_entry.1"); + let mut group1 = tracker.group("#merge_dwb_entry.1"); // locate the node that the given entry belongs in let (mut node, mut node_id, mut bit_pos) = locate_btbe_node(storage, &dwb_entry.recipient()?)?; @@ -543,7 +543,7 @@ pub fn merge_dwb_entry( #[cfg(feature = "gas_tracking")] group1.logf(format!( - "@merged {} into node #{}, bucket #{} at position {} ", + "merged {} into node #{}, bucket #{} at position {} ", dwb_entry.recipient()?, node_id, bucket_id, @@ -567,7 +567,7 @@ pub fn merge_dwb_entry( if bucket.add_entry(&btbe_entry) { #[cfg(feature = "gas_tracking")] group1.logf(format!( - "@inserted into node #{}, bucket #{} (bitpos: {}) at position {}", + "inserted into node #{}, bucket #{} (bitpos: {}) at position {}", node_id, bucket_id, bit_pos, @@ -661,7 +661,7 @@ pub fn merge_dwb_entry( #[cfg(feature = "gas_tracking")] group1.logf(format!( - "@split node #{}, bucket #{} at bitpos {}, ", + "split node #{}, bucket #{} at bitpos {}, ", node_id, bucket_id, bit_pos )); diff --git a/src/contract.rs b/src/contract.rs index a1f08fc6..e4fbb8aa 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -871,6 +871,7 @@ fn try_mint( #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); + #[cfg(not(feature = "gas_tracking"))] Ok(resp) } @@ -1110,6 +1111,7 @@ fn try_deposit( #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); + #[cfg(not(feature = "gas_tracking"))] Ok(resp) } @@ -1273,6 +1275,7 @@ fn try_transfer( #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); + #[cfg(not(feature = "gas_tracking"))] Ok(resp) } @@ -1309,6 +1312,7 @@ fn try_batch_transfer( #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); + #[cfg(not(feature = "gas_tracking"))] Ok(resp) } @@ -1428,6 +1432,7 @@ fn try_send( #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); + #[cfg(not(feature = "gas_tracking"))] Ok(resp) } diff --git a/src/msg.rs b/src/msg.rs index b3afff7f..f0ba0200 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -4,7 +4,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{batch, transaction_history::Tx}; -use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128}; +use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint64}; use secret_toolkit::permit::Permit; #[cfg_attr(test, derive(Eq, PartialEq))] @@ -91,11 +91,11 @@ pub enum ExecuteMsg { Redeem { amount: Uint128, denom: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, Deposit { - gas_target: Option, + gas_target: Option, padding: Option, }, @@ -104,7 +104,7 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, Send { @@ -113,38 +113,38 @@ pub enum ExecuteMsg { amount: Uint128, msg: Option, memo: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, BatchTransfer { actions: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, BatchSend { actions: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, Burn { amount: Uint128, memo: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, RegisterReceive { code_hash: String, - gas_target: Option, + gas_target: Option, padding: Option, }, CreateViewingKey { entropy: String, - gas_target: Option, + gas_target: Option, padding: Option, }, SetViewingKey { key: String, - gas_target: Option, + gas_target: Option, padding: Option, }, @@ -153,14 +153,14 @@ pub enum ExecuteMsg { spender: String, amount: Uint128, expiration: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, DecreaseAllowance { spender: String, amount: Uint128, expiration: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, TransferFrom { @@ -168,7 +168,7 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, SendFrom { @@ -178,29 +178,29 @@ pub enum ExecuteMsg { amount: Uint128, msg: Option, memo: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, BatchTransferFrom { actions: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, BatchSendFrom { actions: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, BurnFrom { owner: String, amount: Uint128, memo: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, BatchBurnFrom { actions: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, @@ -209,56 +209,56 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, - gas_target: Option, + gas_target: Option, padding: Option, }, BatchMint { actions: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, AddMinters { minters: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, RemoveMinters { minters: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, SetMinters { minters: Vec, - gas_target: Option, + gas_target: Option, padding: Option, }, // Admin ChangeAdmin { address: String, - gas_target: Option, + gas_target: Option, padding: Option, }, SetContractStatus { level: ContractStatusLevel, - gas_target: Option, + gas_target: Option, padding: Option, }, /// Add deposit/redeem support for these coin denoms AddSupportedDenoms { denoms: Vec, - gas_target: Option, + gas_target: Option, }, /// Remove deposit/redeem support for these coin denoms RemoveSupportedDenoms { denoms: Vec, - gas_target: Option, + gas_target: Option, }, // Permit RevokePermit { permit_name: String, - gas_target: Option, + gas_target: Option, padding: Option, }, } @@ -403,10 +403,13 @@ impl Evaporator for ExecuteMsg { | ExecuteMsg::RemoveSupportedDenoms { gas_target, .. } | ExecuteMsg::RevokePermit { gas_target, .. } => match gas_target { Some(gas_target) => { - let gas_used = api.check_gas()? as u32; - if gas_used < *gas_target { - let evaporate_amount = gas_target - gas_used; - api.gas_evaporate(evaporate_amount)?; + let gas_used = api.check_gas()?; + if gas_used < gas_target.u64() { + let evaporate_amount = gas_target.u64() - gas_used; + api.gas_evaporate(evaporate_amount as u32)?; + } + else { + api.gas_evaporate(1000u32); } Ok(()) } diff --git a/tests/dwb/src/gas-checker.ts b/tests/dwb/src/gas-checker.ts index f6385f64..a406afde 100644 --- a/tests/dwb/src/gas-checker.ts +++ b/tests/dwb/src/gas-checker.ts @@ -2,7 +2,7 @@ import type {GroupedGasLogs} from './snip'; import {entries, bigint_abs} from '@blake.regalia/belt'; -import {SX_ANSI_GREEN, SX_ANSI_RED, SX_ANSI_MAGENTA, SX_ANSI_RESET, SX_ANSI_YELLOW} from './helper'; +import {SX_ANSI_GREEN, SX_ANSI_RED, SX_ANSI_MAGENTA, SX_ANSI_RESET, SX_ANSI_YELLOW, SX_ANSI_CYAN} from './helper'; const delta_color = (xg_delta: bigint, nl_pad=0) => (bigint_abs(xg_delta) >= 1n ? bigint_abs(xg_delta) > 2n @@ -48,8 +48,17 @@ export class GasChecker { // calculate delta const xg_delta = xg_gap_local - xg_gap_baseline; + // comment only + if('#' === si_group[0]) { + console.log([ + ' '.repeat(8)+si_group.slice(0, 20).padEnd(20, ' '), + ' '.repeat(3), + SX_ANSI_CYAN+s_comment_local+SX_ANSI_RESET, + ].join(' │ ')); + c_logs += 1; + } // non-zero delta - if(xg_delta || '@' === s_comment_local[0]) { + else if(xg_delta || '@' === s_comment_local[0]) { console.log([ ' '.repeat(8)+si_group.slice(0, 20).padEnd(20, ' '), delta_color(xg_delta, 3), diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index 962439d6..7792efe5 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -1,21 +1,22 @@ -import type {Dict} from '@blake.regalia/belt'; -import type {SecretAccAddr, Snip24} from '@solar-republic/contractor'; +import type {Dict, JsonObject} from '@blake.regalia/belt'; -import type {CwUint128} from '@solar-republic/types'; +import type {SecretContractInterface, FungibleTransferCall, SecretAccAddr, Snip24} from '@solar-republic/contractor'; + +import type {CwUint128, WeakUint128Str} from '@solar-republic/types'; import {readFileSync} from 'node:fs'; -import {bytes, bytes_to_base64, entries, sha256, text_to_bytes, bigint_greater} from '@blake.regalia/belt'; +import {bytes, bytes_to_base64, entries, sha256, text_to_bytes, bigint_greater, bigint_abs} from '@blake.regalia/belt'; import {encodeCosmosBankMsgSend, SI_MESSAGE_TYPE_COSMOS_BANK_MSG_SEND} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/tx'; import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any'; -import {SecretApp, SecretContract, Wallet, broadcast_result, create_and_sign_tx_direct, random_32, type TxMeta} from '@solar-republic/neutrino'; +import {SecretApp, SecretContract, Wallet, broadcast_result, create_and_sign_tx_direct, random_32, type TxMeta, type WeakSecretAccAddr} from '@solar-republic/neutrino'; import {BigNumber} from 'bignumber.js'; import {N_DECIMALS, P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; import {upload_code, instantiate_contract} from './contract'; import {DwbValidator} from './dwb'; import {GasChecker} from './gas-checker'; -import {transfer} from './snip'; +import {transfer, type TransferResult} from './snip'; const S_CONTRACT_LABEL = 'snip2x-test_'+bytes_to_base64(crypto.getRandomValues(bytes(6))); @@ -50,7 +51,14 @@ const sa_snip = await instantiate_contract(k_wallet_a, sg_code_id, { console.debug(`Running tests against ${sa_snip}...`); // @ts-expect-error deep instantiation -const k_contract = await SecretContract(P_LOCALSECRET_LCD, sa_snip); +const k_contract = await SecretContract>(P_LOCALSECRET_LCD, sa_snip); const k_app_a = SecretApp(k_wallet_a, k_contract, X_GAS_PRICE); const k_app_b = SecretApp(k_wallet_b, k_contract, X_GAS_PRICE); @@ -64,7 +72,7 @@ const H_APPS = { d: k_app_d, }; -// @ts-expect-error validator! +// #ts-expect-error validator! const k_dwbv = new DwbValidator(k_app_a); console.log('# Initialized'); @@ -85,7 +93,7 @@ async function transfer_chain(sx_chain: string) { console.log(sx_amount, si_from, si_to); // @ts-expect-error secret app - const g_result = await transfer(k_dwbv, xg_amount, H_APPS[si_from[0].toLowerCase()], H_APPS[si_to[0].toLowerCase()], k_checker); + const g_result = await transfer(k_dwbv, xg_amount, H_APPS[si_from[0].toLowerCase()] as SecretApp, H_APPS[si_to[0].toLowerCase()] as SecretApp, k_checker); if(!k_checker) { k_checker = new GasChecker(g_result.tracking, g_result.gasUsed); @@ -93,6 +101,28 @@ async function transfer_chain(sx_chain: string) { } } +// evaporation +{ + const xg_gas_wanted = 25000n; + + const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_a.exec('transfer', { + amount: `${500000n}` as CwUint128, + recipient: k_wallet_b.addr, + gas_target: `${xg_gas_wanted}`, + }, xg_gas_wanted); + + if(xc_code) { + throw Error(`Failed evaporation test: ${sx_res}`); + } + + const xg_gas_used = BigInt(g_meta.gas_used); + if(bigint_abs(xg_gas_wanted, xg_gas_used) > 20n) { + throw Error(`Expected gas used to be ${xg_gas_wanted} but found ${xg_gas_used}`); + } + + console.log(g_meta); +} + { // basic transfers between principals await transfer_chain(` @@ -114,16 +144,11 @@ async function transfer_chain(sx_chain: string) { 1 TKN Carol => Bob -- yet again `); - // gas checker ref let k_checker: GasChecker | null = null; // grant action from previous simultion - let f_grant: undefined | (() => Promise<[w_result: { - spender: SecretAccAddr; - owner: SecretAccAddr; - allowance: CwUint128; - } | undefined, xc_code: number, s_response: string, g_meta: TxMeta | undefined, h_events: Dict | undefined, si_txn: string | undefined]>); + let f_grant: undefined | (() => Promise<[w_result: JsonObject | undefined, xc_code: number, s_response: string, g_meta: TxMeta | undefined, h_events: Dict | undefined, si_txn: string | undefined]>); // number of simulations to perform const N_SIMULATIONS = 300; @@ -152,11 +177,12 @@ async function transfer_chain(sx_chain: string) { // submit all in parallel const [ + // @ts-expect-error totally stupid g_result_transfer, [xc_send_gas, s_err_send_gas], a_res_increase, ] = await Promise.all([ - // @ts-expect-error secret app + // #ts-expect-error secret app transfer(k_dwbv, i_sim % 2? 1_000000n: 2_000000n, k_app_a, k_app_sim, k_checker), broadcast_result(k_wallet, atu8_raw, si_txn), f_grant?.(), @@ -179,7 +205,7 @@ async function transfer_chain(sx_chain: string) { }, 60_000n); if(!k_checker) { - k_checker = new GasChecker(g_result_transfer.tracking, g_result_transfer.gasUsed); + k_checker = new GasChecker((g_result_transfer as TransferResult).tracking, (g_result_transfer as TransferResult).gasUsed); } xg_max_gas_used_transfer = bigint_greater(xg_max_gas_used_transfer, g_result_transfer.gasUsed); @@ -204,7 +230,7 @@ async function transfer_chain(sx_chain: string) { console.log(`${si_owner} --> ${si_recipient}`); - // @ts-expect-error secret app + // #ts-expect-error secret app const g_result = await transfer(k_dwbv, 1_000000n, k_app_owner, k_app_recipient, k_checker, k_app_a); if(!k_checker) { diff --git a/tests/dwb/src/snip.ts b/tests/dwb/src/snip.ts index e223b76b..f9e814ea 100644 --- a/tests/dwb/src/snip.ts +++ b/tests/dwb/src/snip.ts @@ -96,11 +96,11 @@ export async function transfer( owner: k_app_owner.wallet.addr, amount: `${xg_amount}` as CwUint128, recipient: sa_recipient, - }, 1_000000n) + }, 250000n) : await k_app_owner.exec('transfer', { amount: `${xg_amount}` as CwUint128, recipient: sa_recipient, - }, 1_000000n); + }, 250000n); // section header console.log(`# Transfer ${BigNumber(xg_amount+'').shiftedBy(-N_DECIMALS).toFixed()} TKN ${H_ADDRS[sa_owner] || sa_owner}${k_app_sender? ` (via ${H_ADDRS[k_app_sender.wallet.addr] || k_app_sender.wallet.addr})`: ''} => ${H_ADDRS[sa_recipient] || sa_recipient} | ⏹ ${k_dwbv.empty} spaces | ⛽️ ${g_meta?.gas_used || '0'} gas used`); From 45399d092d4447ee5b770f4e39ccfc9a0f02860c Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 3 Aug 2024 07:40:33 +0000 Subject: [PATCH 59/87] fix: tweak gas evaporation params --- src/msg.rs | 3 --- tests/dwb/src/main.ts | 9 +++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/msg.rs b/src/msg.rs index f0ba0200..4fb3caf5 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -408,9 +408,6 @@ impl Evaporator for ExecuteMsg { let evaporate_amount = gas_target.u64() - gas_used; api.gas_evaporate(evaporate_amount as u32)?; } - else { - api.gas_evaporate(1000u32); - } Ok(()) } None => Ok(()), diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index 7792efe5..169d1019 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -103,20 +103,21 @@ async function transfer_chain(sx_chain: string) { // evaporation { - const xg_gas_wanted = 25000n; + const xg_gas_wanted = 250_000n; + const xg_gas_target = xg_gas_wanted - 35_000n; const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_a.exec('transfer', { amount: `${500000n}` as CwUint128, recipient: k_wallet_b.addr, - gas_target: `${xg_gas_wanted}`, + gas_target: `${xg_gas_target}`, }, xg_gas_wanted); if(xc_code) { throw Error(`Failed evaporation test: ${sx_res}`); } - const xg_gas_used = BigInt(g_meta.gas_used); - if(bigint_abs(xg_gas_wanted, xg_gas_used) > 20n) { + const xg_gas_used = BigInt(g_meta?.gas_used || '0'); + if(bigint_abs(xg_gas_wanted, xg_gas_used) > 35_000n) { throw Error(`Expected gas used to be ${xg_gas_wanted} but found ${xg_gas_used}`); } From 1f20825a351d87e2abbb41984a35507ab66cddd4 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 3 Aug 2024 07:45:25 +0000 Subject: [PATCH 60/87] fix: tweak gas evaporation params --- tests/dwb/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index 169d1019..0dcb0db2 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -104,7 +104,7 @@ async function transfer_chain(sx_chain: string) { // evaporation { const xg_gas_wanted = 250_000n; - const xg_gas_target = xg_gas_wanted - 35_000n; + const xg_gas_target = xg_gas_wanted - 36_000n; const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_a.exec('transfer', { amount: `${500000n}` as CwUint128, From 44af4fcf23a4e3d87af52c9a7788bdbc7ab21039 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 3 Aug 2024 19:33:49 +0000 Subject: [PATCH 61/87] style: code cleanup --- src/btbe.rs | 9 +++------ src/contract.rs | 23 ----------------------- src/dwb.rs | 7 +++---- tests/dwb/src/constants.ts | 1 + tests/dwb/src/main.ts | 8 ++++---- 5 files changed, 11 insertions(+), 37 deletions(-) diff --git a/src/btbe.rs b/src/btbe.rs index cf328a61..c2bfca59 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -14,10 +14,9 @@ use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; use crate::state::{safe_add_u64, INTERNAL_SECRET}; -use crate::{ - dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}, - gas_tracker::GasTracker, -}; +use crate::dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}; +#[cfg(feature = "gas_tracking")] +use crate::gas_tracker::GasTracker; pub const KEY_BTBE_ENTRY_HISTORY: &[u8] = b"btbe-entry-hist"; pub const KEY_BTBE_BUCKETS_COUNT: &[u8] = b"btbe-buckets-cnt"; @@ -27,9 +26,7 @@ pub const KEY_BTBE_TRIE_NODES_COUNT: &[u8] = b"btbe-trie-nodes-cnt"; const BUCKETING_SALT_BYTES: &[u8; 14] = b"bucketing-salt"; -const U16_BYTES: usize = 2; const U32_BYTES: usize = 4; -const U64_BYTES: usize = 8; const U128_BYTES: usize = 16; #[cfg(test)] diff --git a/src/contract.rs b/src/contract.rs index e4fbb8aa..273e9ec3 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -2539,24 +2539,6 @@ mod tests { "Init failed: {}", init_result.err().unwrap() ); - - /* - let (init_result, _deps) = init_helper(vec![ - InitialBalance { - address: "lebron".to_string(), - amount: Uint128::new(u128::max_value()), - }, - InitialBalance { - address: "giannis".to_string(), - amount: Uint128::new(1), - }, - ]); - let error = extract_error_msg(init_result); - assert_eq!( - error, - "The sum of all initial balances exceeds the maximum possible total supply" - ); - */ } // Handle tests @@ -2622,11 +2604,6 @@ mod tests { let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default(); assert_eq!(2, tx_count); - //let tx_node1 = TX_NODES.add_suffix(&1u64.to_be_bytes()).load(&deps.storage).unwrap(); - //println!("tx node 1: {tx_node1:?}"); - //let tx_node2 = TX_NODES.add_suffix(&2u64.to_be_bytes()).load(&deps.storage).unwrap(); - //println!("tx node 2: {tx_node2:?}"); - // now send 100 to charlie from bob let handle_msg = ExecuteMsg::Transfer { recipient: "charlie".to_string(), diff --git a/src/dwb.rs b/src/dwb.rs index 9703db14..a8cf7af5 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,17 +1,16 @@ use constant_time_eq::constant_time_eq; -use cosmwasm_std::{to_binary, Api, Binary, CanonicalAddr, StdError, StdResult, Storage}; +use cosmwasm_std::{Api, CanonicalAddr, StdError, StdResult, Storage}; use rand::RngCore; use secret_toolkit::storage::Item; use secret_toolkit_crypto::ContractPrng; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use std::env::var; use crate::btbe::{merge_dwb_entry, stored_balance}; -use crate::gas_tracker::GasTracker; -use crate::msg::QueryAnswer; use crate::state::{safe_add, safe_add_u64}; use crate::transaction_history::{Tx, TRANSACTIONS}; +#[cfg(feature = "gas_tracking")] +use crate::gas_tracker::GasTracker; include!(concat!(env!("OUT_DIR"), "/config.rs")); diff --git a/tests/dwb/src/constants.ts b/tests/dwb/src/constants.ts index 7591e3f4..3afd1cf4 100644 --- a/tests/dwb/src/constants.ts +++ b/tests/dwb/src/constants.ts @@ -5,6 +5,7 @@ import {Wallet} from '@solar-republic/neutrino'; export const P_LOCALSECRET_LCD = (process.env['SECRET_LCD'] || 'http://localhost:1317') as TrustedContextUrl; export const P_LOCALSECRET_RPC = (process.env['SECRET_RPC'] || 'http://localhost:26656') as TrustedContextUrl; +export const B_TEST_EVAPORATION = !!parseInt(process.env['ENABLE_EVAPORATION_TESTS'] || '0'); export const X_GAS_PRICE = 0.1; diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index 0dcb0db2..a0f691f2 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -12,7 +12,7 @@ import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protob import {SecretApp, SecretContract, Wallet, broadcast_result, create_and_sign_tx_direct, random_32, type TxMeta, type WeakSecretAccAddr} from '@solar-republic/neutrino'; import {BigNumber} from 'bignumber.js'; -import {N_DECIMALS, P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; +import {B_TEST_EVAPORATION, N_DECIMALS, P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; import {upload_code, instantiate_contract} from './contract'; import {DwbValidator} from './dwb'; import {GasChecker} from './gas-checker'; @@ -102,9 +102,9 @@ async function transfer_chain(sx_chain: string) { } // evaporation -{ +if(B_TEST_EVAPORATION) { const xg_gas_wanted = 250_000n; - const xg_gas_target = xg_gas_wanted - 36_000n; + const xg_gas_target = xg_gas_wanted - 100n; const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_a.exec('transfer', { amount: `${500000n}` as CwUint128, @@ -117,7 +117,7 @@ async function transfer_chain(sx_chain: string) { } const xg_gas_used = BigInt(g_meta?.gas_used || '0'); - if(bigint_abs(xg_gas_wanted, xg_gas_used) > 35_000n) { + if(bigint_abs(xg_gas_wanted, xg_gas_used) > 1000n) { throw Error(`Expected gas used to be ${xg_gas_wanted} but found ${xg_gas_used}`); } From cf0ef6505c2f69833fd8c7f5d73192e46a258a9c Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 3 Aug 2024 22:37:29 +0000 Subject: [PATCH 62/87] fix: inspectability --- build.rs | 2 +- src/contract.rs | 3 ++- src/dwb.rs | 4 ++++ src/msg.rs | 9 +++++---- tests/dwb/.env.example | 4 ++++ tests/dwb/README.md | 29 +++++++++++++++++++++++++++++ tests/dwb/src/constants.ts | 7 ++++--- tests/dwb/src/contract.ts | 12 +++++++----- tests/dwb/src/gas-checker.ts | 17 +++++++---------- tests/dwb/src/main.ts | 26 ++++++++++++++++---------- tests/dwb/src/snip.ts | 4 ++-- 11 files changed, 81 insertions(+), 36 deletions(-) create mode 100644 tests/dwb/.env.example create mode 100644 tests/dwb/README.md diff --git a/build.rs b/build.rs index eddad235..60de26dd 100644 --- a/build.rs +++ b/build.rs @@ -6,7 +6,7 @@ use std::path::Path; fn main() { // config parameters let dwb_capacity = env::var("DWB_CAPACITY").unwrap_or_else(|_| "64".to_string()); - let btbe_capacity = env::var("BTBE_CAPACITY").unwrap_or_else(|_| "128".to_string()); + let btbe_capacity = env::var("BTBE_CAPACITY").unwrap_or_else(|_| "64".to_string()); // path to destination config.rs file let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); diff --git a/src/contract.rs b/src/contract.rs index 273e9ec3..d7171e9a 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -313,7 +313,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S }; let padded_result = pad_handle_result(response, RESPONSE_BLOCK_SIZE); - msg.evaporate_to_target(api)?; + let evaporated = msg.evaporate_to_target(api)?; + padded_result } diff --git a/src/dwb.rs b/src/dwb.rs index a8cf7af5..8bb0462f 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -11,6 +11,10 @@ use crate::state::{safe_add, safe_add_u64}; use crate::transaction_history::{Tx, TRANSACTIONS}; #[cfg(feature = "gas_tracking")] use crate::gas_tracker::GasTracker; +#[cfg(feature = "gas_tracking")] +use cosmwasm_std::{Binary, to_binary}; +#[cfg(feature = "gas_tracking")] +use crate::msg::QueryAnswer; include!(concat!(env!("OUT_DIR"), "/config.rs")); diff --git a/src/msg.rs b/src/msg.rs index 4fb3caf5..d0f48b8c 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -368,11 +368,11 @@ pub enum ExecuteAnswer { } pub trait Evaporator { - fn evaporate_to_target(&self, api: &dyn Api) -> StdResult<()>; + fn evaporate_to_target(&self, api: &dyn Api) -> StdResult; } impl Evaporator for ExecuteMsg { - fn evaporate_to_target(&self, api: &dyn Api) -> StdResult<()> { + fn evaporate_to_target(&self, api: &dyn Api) -> StdResult { match self { ExecuteMsg::Redeem { gas_target, .. } | ExecuteMsg::Deposit { gas_target, .. } @@ -407,10 +407,11 @@ impl Evaporator for ExecuteMsg { if gas_used < gas_target.u64() { let evaporate_amount = gas_target.u64() - gas_used; api.gas_evaporate(evaporate_amount as u32)?; + return Ok(evaporate_amount) } - Ok(()) + Ok(0) } - None => Ok(()), + None => Ok(0), }, } } diff --git a/tests/dwb/.env.example b/tests/dwb/.env.example new file mode 100644 index 00000000..0a6b74d9 --- /dev/null +++ b/tests/dwb/.env.example @@ -0,0 +1,4 @@ +SECRET_LCD=http://localhost:1317 +SECRET_RPC=http://localhost:26657 +SECRET_CHAIN=secretdev-1 +ENABLE_EVAPORATION_TESTS=1 diff --git a/tests/dwb/README.md b/tests/dwb/README.md new file mode 100644 index 00000000..42d1eb57 --- /dev/null +++ b/tests/dwb/README.md @@ -0,0 +1,29 @@ +# DWB Integration Test Suite + +## Requirements +The test suite is run using [bun](https://bun.sh/). + +## Setup +From this directory: +```sh +bun install +cp .env.example .env +``` + +Edit the `.env` file (or leave as is) to configure the network to either your localsecret or pulsar-3. + +## Run +```sh +bun run test ## compiles the contract for integration tests and runs the main test suite +``` + + +## Debugging + +In case there is a silent failure, it may help to run the suite using node.js instead of bun. You can compile it and run it and debug it interactively with the following commands: +```sh +bun run build && node --env-file=.env --inspect-brk dist/main.js +``` + +The console output should look something like this: +![Integration test preview](https://github.com/user-attachments/assets/be2fedda-550c-45e6-aee4-5af45a84d5b8) diff --git a/tests/dwb/src/constants.ts b/tests/dwb/src/constants.ts index 3afd1cf4..120f6046 100644 --- a/tests/dwb/src/constants.ts +++ b/tests/dwb/src/constants.ts @@ -3,8 +3,9 @@ import type {TrustedContextUrl} from '@solar-republic/types'; import {base64_to_bytes} from '@blake.regalia/belt'; import {Wallet} from '@solar-republic/neutrino'; -export const P_LOCALSECRET_LCD = (process.env['SECRET_LCD'] || 'http://localhost:1317') as TrustedContextUrl; -export const P_LOCALSECRET_RPC = (process.env['SECRET_RPC'] || 'http://localhost:26656') as TrustedContextUrl; +export const P_SECRET_LCD = (process.env['SECRET_LCD'] || 'http://localhost:1317') as TrustedContextUrl; +export const P_SECRET_RPC = (process.env['SECRET_RPC'] || 'http://localhost:26656') as TrustedContextUrl; +export const SI_SECRET_CHAIN = (process.env['SECRET_CHAIN'] || 'secretdev-1') as TrustedContextUrl; export const B_TEST_EVAPORATION = !!parseInt(process.env['ENABLE_EVAPORATION_TESTS'] || '0'); export const X_GAS_PRICE = 0.1; @@ -15,7 +16,7 @@ export const [k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d] = await Promise.al 'buqil+tLeeW7VLuugvOdTmkP3+tUwlCoScPZxeteBPE=', 'UFrCdmofR9iChp6Eg7kE5O3wT+jsOXwJPWwB6kSeuhE=', 'MM/1ZSbT5RF1BnaY6ui/i7yEN0mukGzvXUv+jOyjD0E=', -].map(sb64_sk => Wallet(base64_to_bytes(sb64_sk), 'secretdev-1', P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, 'secret'))); +].map(sb64_sk => Wallet(base64_to_bytes(sb64_sk), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, 'secret'))); export const H_ADDRS = { [k_wallet_a.addr]: 'Alice', diff --git a/tests/dwb/src/contract.ts b/tests/dwb/src/contract.ts index de78ef56..a9aa3e74 100644 --- a/tests/dwb/src/contract.ts +++ b/tests/dwb/src/contract.ts @@ -15,9 +15,9 @@ import {destructSecretRegistrationKey} from '@solar-republic/cosmos-grpc/secret/ import {querySecretRegistrationTxKey} from '@solar-republic/cosmos-grpc/secret/registration/v1beta1/query'; import {SecretWasm, TendermintEventFilter, TendermintWs, broadcast_result, create_and_sign_tx_direct, exec_fees} from '@solar-republic/neutrino'; -import {X_GAS_PRICE, P_LOCALSECRET_LCD, P_LOCALSECRET_RPC} from './constants'; +import {X_GAS_PRICE, P_SECRET_LCD, P_SECRET_RPC} from './constants'; -const k_tef = await TendermintEventFilter(P_LOCALSECRET_RPC); +const k_tef = await TendermintEventFilter(P_SECRET_RPC); export async function exec(k_wallet: Wallet, atu8_msg: EncodedGoogleProtobufAny, xg_gas_limit: bigint): Promise { const [atu8_raw, atu8_signdoc, si_txn] = await create_and_sign_tx_direct( @@ -43,11 +43,13 @@ export async function upload_code(k_wallet: Wallet, atu8_wasm: Uint8Array): Prom const sb16_hash = cast(bytes_to_hex(atu8_hash)); // fetch all uploaded codes - const [,, g_codes] = await querySecretComputeCodes(P_LOCALSECRET_LCD); + const [,, g_codes] = await querySecretComputeCodes(P_SECRET_LCD); // already uploaded const g_existing = g_codes?.code_infos?.find(g => g.code_hash! === sb16_hash); if(g_existing) { + console.info(`Found code ID ${g_existing.code_id} already uploaded to network`); + return g_existing.code_id as WeakUintStr; } @@ -66,10 +68,10 @@ export async function upload_code(k_wallet: Wallet, atu8_wasm: Uint8Array): Prom } export async function instantiate_contract(k_wallet: Wallet, sg_code_id: WeakUintStr, h_init_msg: JsonObject): Promise { - const [,, g_reg] = await querySecretRegistrationTxKey(P_LOCALSECRET_LCD); + const [,, g_reg] = await querySecretRegistrationTxKey(P_SECRET_LCD); const [atu8_cons_pk] = destructSecretRegistrationKey(g_reg!); const k_wasm = SecretWasm(atu8_cons_pk!); - const [,, g_hash] = await querySecretComputeCodeHashByCodeId(P_LOCALSECRET_LCD, sg_code_id); + const [,, g_hash] = await querySecretComputeCodeHashByCodeId(P_SECRET_LCD, sg_code_id); // @ts-expect-error imported types versioning const atu8_body = await k_wasm.encodeMsg(g_hash!.code_hash, h_init_msg); diff --git a/tests/dwb/src/gas-checker.ts b/tests/dwb/src/gas-checker.ts index a406afde..12b4b898 100644 --- a/tests/dwb/src/gas-checker.ts +++ b/tests/dwb/src/gas-checker.ts @@ -23,9 +23,6 @@ export class GasChecker { // each group for(const [si_group, a_logs_local] of entries(h_local)) { - // logs emitted from this transfer group - let c_logs = 0; - // find group in baseline const a_logs_baseline = _h_baseline[si_group]; @@ -50,12 +47,13 @@ export class GasChecker { // comment only if('#' === si_group[0]) { - console.log([ - ' '.repeat(8)+si_group.slice(0, 20).padEnd(20, ' '), - ' '.repeat(3), - SX_ANSI_CYAN+s_comment_local+SX_ANSI_RESET, - ].join(' │ ')); - c_logs += 1; + if(s_comment_local.trim()) { + console.log([ + ' '.repeat(8)+si_group.slice(0, 20).padEnd(20, ' '), + ' '.repeat(3), + SX_ANSI_CYAN+s_comment_local+SX_ANSI_RESET, + ].join(' │ ')); + } } // non-zero delta else if(xg_delta || '@' === s_comment_local[0]) { @@ -64,7 +62,6 @@ export class GasChecker { delta_color(xg_delta, 3), ('@' === s_comment_local[0]? SX_ANSI_MAGENTA: '')+s_comment_local+SX_ANSI_RESET, ].join(' │ ')); - c_logs += 1; } } } diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index a0f691f2..2f9645da 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -12,7 +12,7 @@ import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protob import {SecretApp, SecretContract, Wallet, broadcast_result, create_and_sign_tx_direct, random_32, type TxMeta, type WeakSecretAccAddr} from '@solar-republic/neutrino'; import {BigNumber} from 'bignumber.js'; -import {B_TEST_EVAPORATION, N_DECIMALS, P_LOCALSECRET_LCD, P_LOCALSECRET_RPC, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; +import {B_TEST_EVAPORATION, N_DECIMALS, P_SECRET_LCD, P_SECRET_RPC, SI_SECRET_CHAIN, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; import {upload_code, instantiate_contract} from './contract'; import {DwbValidator} from './dwb'; import {GasChecker} from './gas-checker'; @@ -22,6 +22,8 @@ const S_CONTRACT_LABEL = 'snip2x-test_'+bytes_to_base64(crypto.getRandomValues(b const atu8_wasm = readFileSync('../../contract.wasm'); +console.log(k_wallet_a.addr); + console.debug(`Uploading code...`); const sg_code_id = await upload_code(k_wallet_a, atu8_wasm); @@ -58,7 +60,7 @@ const k_contract = await SecretContract>(P_LOCALSECRET_LCD, sa_snip); +}>>(P_SECRET_LCD, sa_snip); const k_app_a = SecretApp(k_wallet_a, k_contract, X_GAS_PRICE); const k_app_b = SecretApp(k_wallet_b, k_contract, X_GAS_PRICE); @@ -103,8 +105,9 @@ async function transfer_chain(sx_chain: string) { // evaporation if(B_TEST_EVAPORATION) { - const xg_gas_wanted = 250_000n; - const xg_gas_target = xg_gas_wanted - 100n; + const xg_post_evaporate_buffer = 50_000n; + const xg_gas_wanted = 100_000n; + const xg_gas_target = xg_gas_wanted - xg_post_evaporate_buffer; const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_a.exec('transfer', { amount: `${500000n}` as CwUint128, @@ -112,16 +115,19 @@ if(B_TEST_EVAPORATION) { gas_target: `${xg_gas_target}`, }, xg_gas_wanted); + console.log({g_meta}); + if(xc_code) { throw Error(`Failed evaporation test: ${sx_res}`); } const xg_gas_used = BigInt(g_meta?.gas_used || '0'); - if(bigint_abs(xg_gas_wanted, xg_gas_used) > 1000n) { + if(xg_gas_used < xg_gas_target) { + throw Error(`Expected gas used to be greater than ${xg_gas_target} but only used ${xg_gas_used}`); + } + else if(bigint_abs(xg_gas_wanted, xg_gas_used) > xg_post_evaporate_buffer) { throw Error(`Expected gas used to be ${xg_gas_wanted} but found ${xg_gas_used}`); } - - console.log(g_meta); } { @@ -161,7 +167,7 @@ if(B_TEST_EVAPORATION) { for(let i_sim=0; i_sim; export async function scrt_balance(sa_owner: WeakSecretAccAddr): Promise { - const [,, g_res] = await queryCosmosBankBalance(P_LOCALSECRET_LCD, sa_owner, 'uscrt'); + const [,, g_res] = await queryCosmosBankBalance(P_SECRET_LCD, sa_owner, 'uscrt'); return BigInt(g_res?.balance?.amount || '0'); } From cb85125a942c05f7a935a120f0b3ab7882649ef9 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Mon, 5 Aug 2024 01:37:11 +0000 Subject: [PATCH 63/87] fix: make evaporation optional --- .cargo/config.toml | 1 + Cargo.toml | 1 + src/contract.rs | 102 ++++++++++++++++++++++++++++++++++++++++++ src/msg.rs | 32 ++++++++++++- tests/dwb/src/main.ts | 12 ++--- 5 files changed, 141 insertions(+), 7 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 0e2770dc..0bf94f78 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,3 +9,4 @@ schema = "run --example schema" [features] gas_tracking = [] +gas_evaporation = [] diff --git a/Cargo.toml b/Cargo.toml index 8d8417e2..c3b0aebf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ overflow-checks = true #default = ["debug-print"] backtraces = ["cosmwasm-std/backtraces"] gas_tracking = [] +gas_evaporation = [] # debug-print = ["cosmwasm-std/debug-print"] [dependencies] diff --git a/src/contract.rs b/src/contract.rs index d7171e9a..de6e9173 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -20,6 +20,7 @@ use crate::btbe::{ find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count, }; use crate::gas_tracker::{GasTracker, LoggingExt}; +#[cfg(feature = "gas_evaporation")] use crate::msg::Evaporator; use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, @@ -313,6 +314,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S }; let padded_result = pad_handle_result(response, RESPONSE_BLOCK_SIZE); + + #[cfg(feature = "gas_evaporation")] let evaporated = msg.evaporate_to_target(api)?; padded_result @@ -2566,6 +2569,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1000), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2610,6 +2614,7 @@ mod tests { recipient: "charlie".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2653,6 +2658,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(500), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2731,6 +2737,7 @@ mod tests { recipient: "ernie".to_string(), amount: Uint128::new(200), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2778,6 +2785,7 @@ mod tests { recipient: "dora".to_string(), amount: Uint128::new(50), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2851,6 +2859,7 @@ mod tests { recipient, amount: Uint128::new(1), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2876,6 +2885,7 @@ mod tests { recipient, amount: Uint128::new(1), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2927,6 +2937,7 @@ mod tests { recipient: "dora".to_string(), amount: Uint128::new(1), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2965,6 +2976,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: "key".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3277,6 +3289,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(10000), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3302,6 +3315,7 @@ mod tests { let handle_msg = ExecuteMsg::RegisterReceive { code_hash: "this_is_a_hash_of_a_code".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3318,6 +3332,7 @@ mod tests { amount: Uint128::new(100), memo: Some("my memo".to_string()), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, msg: Some(to_binary("hey hey you you").unwrap()), }; @@ -3367,6 +3382,7 @@ mod tests { let handle_msg = ExecuteMsg::RegisterReceive { code_hash: "this_is_a_hash_of_a_code".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3398,6 +3414,7 @@ mod tests { let handle_msg = ExecuteMsg::CreateViewingKey { entropy: "".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3440,6 +3457,7 @@ mod tests { // Set VK let handle_msg = ExecuteMsg::SetViewingKey { key: "hi lol".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3461,6 +3479,7 @@ mod tests { let actual_vk = "x".to_string().repeat(VIEWING_KEY_SIZE); let handle_msg = ExecuteMsg::SetViewingKey { key: actual_vk.clone(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3486,6 +3505,7 @@ mod tests { ) -> Result { let handle_msg = ExecuteMsg::RevokePermit { permit_name: permit_name.to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3694,6 +3714,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2500), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3709,6 +3730,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: Some(1_571_797_420), }; @@ -3726,6 +3748,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2500), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3742,6 +3765,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2000), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3784,6 +3808,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(2000), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3818,6 +3843,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3849,6 +3875,7 @@ mod tests { amount: Uint128::new(2500), memo: None, msg: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3863,6 +3890,7 @@ mod tests { let handle_msg = ExecuteMsg::IncreaseAllowance { spender: "alice".to_string(), amount: Uint128::new(2000), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, expiration: None, @@ -3883,6 +3911,7 @@ mod tests { amount: Uint128::new(2500), memo: None, msg: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3896,6 +3925,7 @@ mod tests { // Sanity check let handle_msg = ExecuteMsg::RegisterReceive { code_hash: "lolz".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3923,6 +3953,7 @@ mod tests { amount: Uint128::new(2000), memo: Some("my memo".to_string()), msg: Some(send_msg), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3969,6 +4000,7 @@ mod tests { amount: Uint128::new(1), memo: None, msg: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4014,6 +4046,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4029,6 +4062,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4044,6 +4078,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -4060,6 +4095,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2500), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4075,6 +4111,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(2000), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4102,6 +4139,7 @@ mod tests { owner: "bob".to_string(), amount: Uint128::new(1), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4163,6 +4201,7 @@ mod tests { .collect(); let handle_msg = ExecuteMsg::BatchBurnFrom { actions, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4229,6 +4268,7 @@ mod tests { let handle_msg = ExecuteMsg::BatchBurnFrom { actions, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4264,6 +4304,7 @@ mod tests { let handle_msg = ExecuteMsg::BatchBurnFrom { actions, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4298,6 +4339,7 @@ mod tests { .collect(); let handle_msg = ExecuteMsg::BatchBurnFrom { actions, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4325,6 +4367,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -4354,6 +4397,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -4371,6 +4415,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(50), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -4410,6 +4455,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -4439,6 +4485,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(2000), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -4476,6 +4523,7 @@ mod tests { let handle_msg = ExecuteMsg::ChangeAdmin { address: "bob".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4507,6 +4555,7 @@ mod tests { let handle_msg = ExecuteMsg::SetContractStatus { level: ContractStatusLevel::StopAll, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4578,6 +4627,7 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4592,6 +4642,7 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4610,6 +4661,7 @@ mod tests { amount: Uint128::new(1000), denom: None, padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, }; let info = mock_info("butler", &[]); @@ -4626,6 +4678,7 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: Option::from("uscrt".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4677,6 +4730,7 @@ mod tests { ); // test when deposit disabled let handle_msg = ExecuteMsg::Deposit { + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4693,6 +4747,7 @@ mod tests { assert!(error.contains("Tried to deposit an unsupported coin uscrt")); let handle_msg = ExecuteMsg::Deposit { + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4722,6 +4777,7 @@ mod tests { let create_vk_msg = ExecuteMsg::CreateViewingKey { entropy: "34".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4778,6 +4834,7 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4793,6 +4850,7 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(burn_amount), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4844,6 +4902,7 @@ mod tests { recipient: "lebron".to_string(), amount: Uint128::new(mint_amount), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4860,6 +4919,7 @@ mod tests { recipient: "lebron".to_string(), amount: Uint128::new(mint_amount), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4900,6 +4960,7 @@ mod tests { let pause_msg = ExecuteMsg::SetContractStatus { level: ContractStatusLevel::StopAllButRedeems, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4912,6 +4973,7 @@ mod tests { let mint_msg = ExecuteMsg::AddMinters { minters: vec!["not_admin".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4924,6 +4986,7 @@ mod tests { let mint_msg = ExecuteMsg::RemoveMinters { minters: vec!["admin".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4936,6 +4999,7 @@ mod tests { let mint_msg = ExecuteMsg::SetMinters { minters: vec!["not_admin".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4948,6 +5012,7 @@ mod tests { let change_admin_msg = ExecuteMsg::ChangeAdmin { address: "not_admin".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4981,6 +5046,7 @@ mod tests { let pause_msg = ExecuteMsg::SetContractStatus { level: ContractStatusLevel::StopAllButRedeems, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4999,6 +5065,7 @@ mod tests { recipient: "account".to_string(), amount: Uint128::new(123), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5015,6 +5082,7 @@ mod tests { let withdraw_msg = ExecuteMsg::Redeem { amount: Uint128::new(5000), denom: Option::from("uscrt".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5043,6 +5111,7 @@ mod tests { let pause_msg = ExecuteMsg::SetContractStatus { level: ContractStatusLevel::StopAll, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5061,6 +5130,7 @@ mod tests { recipient: "account".to_string(), amount: Uint128::new(123), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5077,6 +5147,7 @@ mod tests { let withdraw_msg = ExecuteMsg::Redeem { amount: Uint128::new(5000), denom: Option::from("uscrt".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5122,6 +5193,7 @@ mod tests { // try when mint disabled let handle_msg = ExecuteMsg::SetMinters { minters: vec!["bob".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5134,6 +5206,7 @@ mod tests { let handle_msg = ExecuteMsg::SetMinters { minters: vec!["bob".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5146,6 +5219,7 @@ mod tests { let handle_msg = ExecuteMsg::SetMinters { minters: vec!["bob".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5159,6 +5233,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5172,6 +5247,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5214,6 +5290,7 @@ mod tests { // try when mint disabled let handle_msg = ExecuteMsg::AddMinters { minters: vec!["bob".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5226,6 +5303,7 @@ mod tests { let handle_msg = ExecuteMsg::AddMinters { minters: vec!["bob".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5238,6 +5316,7 @@ mod tests { let handle_msg = ExecuteMsg::AddMinters { minters: vec!["bob".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5251,6 +5330,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5264,6 +5344,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5305,6 +5386,7 @@ mod tests { // try when mint disabled let handle_msg = ExecuteMsg::RemoveMinters { minters: vec!["bob".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5317,6 +5399,7 @@ mod tests { let handle_msg = ExecuteMsg::RemoveMinters { minters: vec!["admin".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5329,6 +5412,7 @@ mod tests { let handle_msg = ExecuteMsg::RemoveMinters { minters: vec!["admin".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5342,6 +5426,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5356,6 +5441,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5369,6 +5455,7 @@ mod tests { // Removing another extra time to ensure nothing funky happens let handle_msg = ExecuteMsg::RemoveMinters { minters: vec!["admin".to_string()], + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5382,6 +5469,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5396,6 +5484,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5434,6 +5523,7 @@ mod tests { let create_vk_msg = ExecuteMsg::CreateViewingKey { entropy: "34".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5842,6 +5932,7 @@ mod tests { spender: "lebron".to_string(), amount: Uint128::new(2000), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -5874,6 +5965,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: vk1.clone(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -5893,6 +5985,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: vk2.clone(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6193,6 +6286,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: "key".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6252,6 +6346,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: "key".to_string(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6264,6 +6359,7 @@ mod tests { let handle_msg = ExecuteMsg::Burn { amount: Uint128::new(1), memo: Some("my burn message".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6280,6 +6376,7 @@ mod tests { let handle_msg = ExecuteMsg::Redeem { amount: Uint128::new(1000), denom: Option::from("uscrt".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6297,6 +6394,7 @@ mod tests { recipient: "bob".to_string(), amount: Uint128::new(100), memo: Some("my mint message".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6307,6 +6405,7 @@ mod tests { assert!(ensure_success(handle_result.unwrap())); let handle_msg = ExecuteMsg::Deposit { + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6329,6 +6428,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(1000), memo: Some("my transfer message #1".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6343,6 +6443,7 @@ mod tests { recipient: "banana".to_string(), amount: Uint128::new(500), memo: Some("my transfer message #2".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6357,6 +6458,7 @@ mod tests { recipient: "mango".to_string(), amount: Uint128::new(2500), memo: Some("my transfer message #3".to_string()), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; diff --git a/src/msg.rs b/src/msg.rs index d0f48b8c..74e1aa64 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -91,10 +91,12 @@ pub enum ExecuteMsg { Redeem { amount: Uint128, denom: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, Deposit { + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -104,6 +106,7 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -113,37 +116,44 @@ pub enum ExecuteMsg { amount: Uint128, msg: Option, memo: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, BatchTransfer { actions: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, BatchSend { actions: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, Burn { amount: Uint128, memo: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, RegisterReceive { code_hash: String, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, CreateViewingKey { entropy: String, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, SetViewingKey { key: String, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -153,6 +163,7 @@ pub enum ExecuteMsg { spender: String, amount: Uint128, expiration: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -160,6 +171,7 @@ pub enum ExecuteMsg { spender: String, amount: Uint128, expiration: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -168,6 +180,7 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -178,16 +191,19 @@ pub enum ExecuteMsg { amount: Uint128, msg: Option, memo: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, BatchTransferFrom { actions: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, BatchSendFrom { actions: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -195,11 +211,13 @@ pub enum ExecuteMsg { owner: String, amount: Uint128, memo: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, BatchBurnFrom { actions: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -209,26 +227,31 @@ pub enum ExecuteMsg { recipient: String, amount: Uint128, memo: Option, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, BatchMint { actions: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, AddMinters { minters: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, RemoveMinters { minters: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, SetMinters { minters: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -236,28 +259,33 @@ pub enum ExecuteMsg { // Admin ChangeAdmin { address: String, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, SetContractStatus { level: ContractStatusLevel, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, /// Add deposit/redeem support for these coin denoms AddSupportedDenoms { denoms: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, }, /// Remove deposit/redeem support for these coin denoms RemoveSupportedDenoms { denoms: Vec, + #[cfg(feature = "gas_evaporation")] gas_target: Option, }, // Permit RevokePermit { permit_name: String, + #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, }, @@ -367,10 +395,12 @@ pub enum ExecuteAnswer { }, } +#[cfg(feature = "gas_evaporation")] pub trait Evaporator { fn evaporate_to_target(&self, api: &dyn Api) -> StdResult; } +#[cfg(feature = "gas_evaporation")] impl Evaporator for ExecuteMsg { fn evaporate_to_target(&self, api: &dyn Api) -> StdResult { match self { @@ -406,7 +436,7 @@ impl Evaporator for ExecuteMsg { let gas_used = api.check_gas()?; if gas_used < gas_target.u64() { let evaporate_amount = gas_target.u64() - gas_used; - api.gas_evaporate(evaporate_amount as u32)?; + // api.gas_evaporate(evaporate_amount as u32)?; return Ok(evaporate_amount) } Ok(0) diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index 2f9645da..e3e8b446 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -77,11 +77,6 @@ const H_APPS = { // #ts-expect-error validator! const k_dwbv = new DwbValidator(k_app_a); -console.log('# Initialized'); -await k_dwbv.sync(); -k_dwbv.print(); -console.log('\n'); - async function transfer_chain(sx_chain: string) { const a_lines = sx_chain.split(/\s*\n+\s*/g).filter(s => s && /^\s*(\d+)/.test(s)); @@ -106,7 +101,7 @@ async function transfer_chain(sx_chain: string) { // evaporation if(B_TEST_EVAPORATION) { const xg_post_evaporate_buffer = 50_000n; - const xg_gas_wanted = 100_000n; + const xg_gas_wanted = 150_000n; const xg_gas_target = xg_gas_wanted - xg_post_evaporate_buffer; const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_a.exec('transfer', { @@ -131,6 +126,11 @@ if(B_TEST_EVAPORATION) { } { + console.log('# Initialized'); + await k_dwbv.sync(); + k_dwbv.print(); + console.log('\n'); + // basic transfers between principals await transfer_chain(` 1 TKN Alice => Bob From 4bdea0ca8ababf6bfec9b564d23803ceb35fb1a6 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 8 Aug 2024 16:41:25 +1200 Subject: [PATCH 64/87] remove prng store, update create viewing key to make entropy optional --- src/contract.rs | 50 +++++++++++++++++++++++++++++++++---------------- src/msg.rs | 6 ++++-- src/state.rs | 15 --------------- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index de6e9173..c92b5e0f 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,9 +1,11 @@ /// This contract implements SNIP-20 standard: /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md use cosmwasm_std::{ - entry_point, to_binary, Addr, Api, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, + entry_point, to_binary, Addr, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128, }; +#[cfg(feature = "gas_evaporation")] +use cosmwasm_std::Api; use secret_toolkit::notification::hkdf_sha_256; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; @@ -19,6 +21,7 @@ use crate::dwb::{DelayedWriteBuffer, DWB, TX_NODES}; use crate::btbe::{ find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count, }; +#[cfg(feature = "gas_tracking")] use crate::gas_tracker::{GasTracker, LoggingExt}; #[cfg(feature = "gas_evaporation")] use crate::msg::Evaporator; @@ -28,8 +31,8 @@ use crate::msg::{ }; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, AllowancesStore, Config, MintersStore, PrngStore, ReceiverHashStore, CONFIG, - CONTRACT_STATUS, INTERNAL_SECRET, PRNG, TOTAL_SUPPLY, + safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CONFIG, + CONTRACT_STATUS, INTERNAL_SECRET, TOTAL_SUPPLY, }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ @@ -72,9 +75,6 @@ pub fn instantiate( let mut total_supply: u128 = 0; - let prng_seed_hashed = sha_256(&msg.prng_seed.0); - PrngStore::save(deps.storage, prng_seed_hashed)?; - // initialize the bitwise-trie of bucketed entries initialize_btbe(deps.storage)?; @@ -84,7 +84,6 @@ pub fn instantiate( let initial_balances = msg.initial_balances.unwrap_or_default(); let raw_admin = deps.api.addr_canonicalize(admin.as_str())?; let rng_seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(rng_seed.as_slice(), &prng_seed_hashed); // use entropy and env.random to create an internal secret for the contract let entropy = msg.prng_seed.0.as_slice(); @@ -105,6 +104,7 @@ pub fn instantiate( )?; INTERNAL_SECRET.save(deps.storage, &internal_secret)?; + let mut rng = ContractPrng::new(rng_seed.as_slice(), &sha_256(&msg.prng_seed.0)); for balance in initial_balances { let amount = balance.amount.u128(); let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?; @@ -163,18 +163,24 @@ pub fn instantiate( }; MintersStore::save(deps.storage, minters)?; - ViewingKey::set_seed(deps.storage, &prng_seed_hashed); + let vk_seed = hkdf_sha_256( + &salt, + rng_seed.0.as_slice(), + "contract_viewing_key".as_bytes(), + 32, + )?; + ViewingKey::set_seed(deps.storage, &vk_seed); Ok(Response::default()) } #[entry_point] pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - let seed = env.block.random.as_ref().unwrap(); - let mut rng = ContractPrng::new(seed.as_slice(), &PRNG.load(deps.storage)?); + let mut rng = ContractPrng::from_env(&env); let contract_status = CONTRACT_STATUS.load(deps.storage)?; + #[cfg(feature = "gas_evaporation")] let api = deps.api; match contract_status { ContractStatusLevel::StopAll | ContractStatusLevel::StopAllButRedeems => { @@ -234,7 +240,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::RegisterReceive { code_hash, .. } => { try_register_receive(deps, info, code_hash) } - ExecuteMsg::CreateViewingKey { entropy, .. } => try_create_key(deps, env, info, entropy), + ExecuteMsg::CreateViewingKey { entropy, .. } => try_create_key(deps, env, info, entropy, &mut rng), ExecuteMsg::SetViewingKey { key, .. } => try_set_key(deps, info, key), // Allowance @@ -944,14 +950,17 @@ pub fn try_create_key( deps: DepsMut, env: Env, info: MessageInfo, - entropy: String, + entropy: Option, + rng: &mut ContractPrng, ) -> StdResult { + let entropy = [entropy.unwrap_or_default().as_bytes(), &rng.rand_bytes()].concat(); + let key = ViewingKey::create( deps.storage, &info, &env, info.sender.as_str(), - entropy.as_ref(), + &entropy, ); Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key })?)) @@ -1158,6 +1167,7 @@ fn try_redeem( // load delayed write buffer let mut dwb = DWB.load(deps.storage)?; + #[cfg(feature = "gas_tracking")] let mut tracker = GasTracker::new(deps.api); // settle the signer's account in buffer @@ -2831,6 +2841,7 @@ mod tests { recipient, amount: Uint128::new(1), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2912,6 +2923,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(i.into()), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -2958,6 +2970,7 @@ mod tests { recipient: "alice".to_string(), amount: Uint128::new(i.into()), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -3413,7 +3426,7 @@ mod tests { ); let handle_msg = ExecuteMsg::CreateViewingKey { - entropy: "".to_string(), + entropy: None, #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, @@ -4230,6 +4243,7 @@ mod tests { spender: "alice".to_string(), amount: Uint128::new(allowance_size), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -4245,6 +4259,7 @@ mod tests { owner: "name".to_string(), amount: Uint128::new(2500), memo: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -4776,7 +4791,7 @@ mod tests { assert_ne!(stored_balance(&deps.storage, &canonical).unwrap(), 6000); let create_vk_msg = ExecuteMsg::CreateViewingKey { - entropy: "34".to_string(), + entropy: Some("34".to_string()), #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, @@ -5522,7 +5537,7 @@ mod tests { ); let create_vk_msg = ExecuteMsg::CreateViewingKey { - entropy: "34".to_string(), + entropy: Some("34".to_string()), #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, @@ -6062,6 +6077,7 @@ mod tests { for i in 0..num_owners { let handle_msg = ExecuteMsg::SetViewingKey { key: vk.clone(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; @@ -6086,6 +6102,7 @@ mod tests { spender: format!("spender{}", j), amount: Uint128::new(50), padding: None, + #[cfg(feature = "gas_evaporation")] gas_target: None, expiration: None, }; @@ -6100,6 +6117,7 @@ mod tests { let handle_msg = ExecuteMsg::SetViewingKey { key: vk.clone(), + #[cfg(feature = "gas_evaporation")] gas_target: None, padding: None, }; diff --git a/src/msg.rs b/src/msg.rs index 74e1aa64..1a29ab98 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -4,7 +4,9 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{batch, transaction_history::Tx}; -use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint64}; +use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128,}; +#[cfg(feature = "gas_evaporation")] +use cosmwasm_std::Uint64; use secret_toolkit::permit::Permit; #[cfg_attr(test, derive(Eq, PartialEq))] @@ -146,7 +148,7 @@ pub enum ExecuteMsg { padding: Option, }, CreateViewingKey { - entropy: String, + entropy: Option, #[cfg(feature = "gas_evaporation")] gas_target: Option, padding: Option, diff --git a/src/state.rs b/src/state.rs index 7874dd9c..644f5a7b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,14 +4,12 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{Addr, StdError, StdResult, Storage}; use secret_toolkit::serialization::Json; use secret_toolkit::storage::{Item, Keymap, Keyset}; -use secret_toolkit_crypto::SHA256_HASH_SIZE; use crate::msg::ContractStatusLevel; pub const KEY_CONFIG: &[u8] = b"config"; pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply"; pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status"; -pub const KEY_PRNG: &[u8] = b"prng"; pub const KEY_MINTERS: &[u8] = b"minters"; pub const KEY_TX_COUNT: &[u8] = b"tx-count"; @@ -54,23 +52,10 @@ pub static TOTAL_SUPPLY: Item = Item::new(KEY_TOTAL_SUPPLY); pub static CONTRACT_STATUS: Item = Item::new(KEY_CONTRACT_STATUS); -pub static PRNG: Item<[u8; SHA256_HASH_SIZE]> = Item::new(KEY_PRNG); - pub static MINTERS: Item> = Item::new(KEY_MINTERS); pub static TX_COUNT: Item = Item::new(KEY_TX_COUNT); -pub struct PrngStore {} -impl PrngStore { - pub fn load(store: &dyn Storage) -> StdResult<[u8; SHA256_HASH_SIZE]> { - PRNG.load(store).map_err(|_err| StdError::generic_err("")) - } - - pub fn save(store: &mut dyn Storage, prng_seed: [u8; SHA256_HASH_SIZE]) -> StdResult<()> { - PRNG.save(store, &prng_seed) - } -} - pub struct MintersStore {} impl MintersStore { pub fn load(store: &dyn Storage) -> StdResult> { From 9e62c8ec00bf3b6153d29f18ac300625368a8d72 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 15 Oct 2024 22:30:26 +1300 Subject: [PATCH 65/87] dev: snip52+53 implementation for mint, transfer, batch transfer, send, batch send --- .vscode/settings.json | 2 +- Cargo.lock | 20 +- Cargo.toml | 7 +- src/btbe.rs | 4 +- src/contract.rs | 514 +++++++++++++++++++++++++++++++++++++++--- src/dwb.rs | 11 +- src/lib.rs | 1 + src/msg.rs | 47 +++- src/notifications.rs | 402 +++++++++++++++++++++++++++++++++ src/state.rs | 4 + 10 files changed, 959 insertions(+), 53 deletions(-) create mode 100644 src/notifications.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 83bf78f8..7dae7aa1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,7 @@ "**/node_modules": true }, "editor.insertSpaces": false, - "editor.tabSize": 3, + "editor.tabSize": 4, "typescript.tsdk": "node_modules/typescript/lib", "typescript.preferences.quoteStyle": "single", "javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false, diff --git a/Cargo.lock b/Cargo.lock index 9fe43e79..d66b8a9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -716,7 +716,7 @@ dependencies = [ [[package]] name = "secret-toolkit" version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" dependencies = [ "secret-toolkit-crypto", "secret-toolkit-notification", @@ -730,8 +730,9 @@ dependencies = [ [[package]] name = "secret-toolkit-crypto" version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" dependencies = [ + "hkdf", "rand_chacha", "rand_core 0.6.4", "secp256k1", @@ -742,7 +743,7 @@ dependencies = [ [[package]] name = "secret-toolkit-notification" version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" dependencies = [ "chacha20poly1305", "generic-array", @@ -760,7 +761,7 @@ dependencies = [ [[package]] name = "secret-toolkit-permit" version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" dependencies = [ "bech32", "remain", @@ -774,7 +775,7 @@ dependencies = [ [[package]] name = "secret-toolkit-serialization" version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" dependencies = [ "bincode2", "schemars", @@ -785,7 +786,7 @@ dependencies = [ [[package]] name = "secret-toolkit-storage" version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" dependencies = [ "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -796,7 +797,7 @@ dependencies = [ [[package]] name = "secret-toolkit-utils" version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" dependencies = [ "schemars", "secret-cosmwasm-std", @@ -807,7 +808,7 @@ dependencies = [ [[package]] name = "secret-toolkit-viewing-key" version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git#1bf8c5f3f08e75276fa812cb248c6eda5cd3beae" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" dependencies = [ "base64 0.21.7", "schemars", @@ -915,11 +916,12 @@ dependencies = [ [[package]] name = "snip20-reference-impl" -version = "1.0.0" +version = "2.0.0" dependencies = [ "base64 0.21.7", "constant_time_eq", "cosmwasm-schema", + "minicbor-ser", "primitive-types", "rand", "schemars", diff --git a/Cargo.toml b/Cargo.toml index c3b0aebf..b63d7f2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snip20-reference-impl" -version = "1.0.0" +version = "2.0.0" authors = ["@reuvenpo","@toml01","@assafmo","@liorbond","Itzik ","@darwinzer0","@supdoggie"] edition = "2021" exclude = [ @@ -38,9 +38,9 @@ cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } rand = { version = "0.8.5", default-features = false } # secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] } -secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"] } +secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"], rev = "8aed92d589dc119f69d20f8538d5a6eea8003d95" } # secret-toolkit-crypto = { version = "0.10.0", default-features = false, features = ["hash"] } -secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash"] } +secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash"], rev = "8aed92d589dc119f69d20f8538d5a6eea8003d95" } static_assertions = "1.1.0" schemars = "0.8.12" @@ -49,6 +49,7 @@ serde-big-array = "0.5.1" base64 = "0.21.0" constant_time_eq = "0.3.0" primitive-types = { version = "0.12.2", default-features = false } +minicbor-ser = "0.2.0" [dev-dependencies] cosmwasm-schema = { version = "1.1.8" } diff --git a/src/btbe.rs b/src/btbe.rs index c2bfca59..c213b44c 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -6,10 +6,10 @@ use constant_time_eq::constant_time_eq; use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; use primitive_types::U256; use secret_toolkit::{ - notification::hkdf_sha_256, serialization::{Bincode2, Serde}, storage::Item, }; +use secret_toolkit_crypto::hkdf_sha_256; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; @@ -511,7 +511,7 @@ pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> St } // merges a dwb entry into the current node's bucket -// `spent_amount` is any required subtraction due to being sender of tx +// `amount_spent` is any required subtraction due to being sender of tx pub fn merge_dwb_entry( storage: &mut dyn Storage, dwb_entry: &DelayedWriteBufferEntry, diff --git a/src/contract.rs b/src/contract.rs index c92b5e0f..0a9ecaeb 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -2,15 +2,15 @@ /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md use cosmwasm_std::{ entry_point, to_binary, Addr, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, - Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128, + Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128, Uint64, }; #[cfg(feature = "gas_evaporation")] use cosmwasm_std::Api; -use secret_toolkit::notification::hkdf_sha_256; +use secret_toolkit::notification::{get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, FlatDescriptor, Notification, NotificationData, StructDescriptor,}; use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; -use secret_toolkit_crypto::{sha_256, ContractPrng}; +use secret_toolkit_crypto::{hkdf_sha_256, sha_256, ContractPrng}; use crate::batch; @@ -29,10 +29,10 @@ use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, }; +use crate::notifications::{multi_received_data, AllowanceNotificationData, ReceivedNotificationData, SpentNotificationData, MULTI_RECEIVED_CHANNEL_BLOOM_K, MULTI_RECEIVED_CHANNEL_BLOOM_N, MULTI_RECEIVED_CHANNEL_ID, MULTI_RECEIVED_CHANNEL_PACKET_SIZE, MULTI_SPENT_CHANNEL_BLOOM_K, MULTI_SPENT_CHANNEL_BLOOM_N, MULTI_SPENT_CHANNEL_ID, MULTI_SPENT_CHANNEL_PACKET_SIZE}; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CONFIG, - CONTRACT_STATUS, INTERNAL_SECRET, TOTAL_SUPPLY, + safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, TOTAL_SUPPLY }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; use crate::transaction_history::{ @@ -42,6 +42,7 @@ use crate::transaction_history::{ /// We make sure that responses from `handle` are padded to a multiple of this size. pub const RESPONSE_BLOCK_SIZE: usize = 256; +pub const NOTIFICATION_BLOCK_SIZE: usize = 36; pub const PREFIX_REVOKED_PERMITS: &str = "revoked_permits"; #[entry_point] @@ -104,6 +105,19 @@ pub fn instantiate( )?; INTERNAL_SECRET.save(deps.storage, &internal_secret)?; + // Hard-coded channels + let channels: Vec = vec![ + ReceivedNotificationData::CHANNEL_ID.to_string(), + SpentNotificationData::CHANNEL_ID.to_string(), + AllowanceNotificationData::CHANNEL_ID.to_string(), + MULTI_RECEIVED_CHANNEL_ID.to_string(), + MULTI_SPENT_CHANNEL_ID.to_string(), + ]; + + for channel in channels { + CHANNELS.insert(deps.storage, &channel)?; + } + let mut rng = ContractPrng::new(rng_seed.as_slice(), &sha_256(&msg.prng_seed.0)); for balance in initial_balances { let amount = balance.amount.u128(); @@ -328,7 +342,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S } #[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { pad_query_result( match msg { QueryMsg::TokenInfo {} => query_token_info(deps.storage), @@ -336,18 +350,19 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::ContractStatus {} => query_contract_status(deps.storage), QueryMsg::ExchangeRate {} => query_exchange_rate(deps.storage), QueryMsg::Minters { .. } => query_minters(deps), - QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query), + QueryMsg::ListChannels {} => query_list_channels(deps), + QueryMsg::WithPermit { permit, query } => permit_queries(deps, env, permit, query), #[cfg(feature = "gas_tracking")] QueryMsg::Dwb {} => log_dwb(deps.storage), - _ => viewing_keys_queries(deps, msg), + _ => viewing_keys_queries(deps, env, msg), }, RESPONSE_BLOCK_SIZE, ) } -fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result { +fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) -> Result { // Validate permit content let token_address = CONFIG.load(deps.storage)?.contract_address; @@ -445,10 +460,17 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result< } query_allowances_received(deps, account, page.unwrap_or(0), page_size) } + QueryWithPermit::ChannelInfo { channels, txhash } => query_channel_info( + deps, + env, + channels, + txhash, + deps.api.addr_canonicalize(account.as_str())?, + ) } } -pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult { +pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { let (addresses, key) = msg.get_validation_params(deps.api)?; for address in addresses { @@ -479,6 +501,17 @@ pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult { page_size, .. } => query_allowances_received(deps, spender, page.unwrap_or(0), page_size), + QueryMsg::ChannelInfo { + channels, + txhash, + viewer, + } => query_channel_info( + deps, + env, + channels, + txhash, + deps.api.addr_canonicalize(viewer.address.as_str())?, + ), _ => panic!("This query type does not require authentication"), }; } @@ -489,6 +522,8 @@ pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult { }) } +// query functions + fn query_exchange_rate(storage: &dyn Storage) -> StdResult { let constants = CONFIG.load(storage)?; @@ -728,6 +763,203 @@ fn query_minters(deps: Deps) -> StdResult { to_binary(&response) } +// ***************** +// SNIP-52 query functions +// ***************** + +/// +/// ListChannels query +/// +/// Public query to list all notification channels. +/// +fn query_list_channels(deps: Deps) -> StdResult { + let channels: Vec = CHANNELS + .iter(deps.storage)? + .map(|channel| channel.unwrap()) + .collect(); + to_binary(&QueryAnswer::ListChannels { channels }) +} + +/// +/// ChannelInfo query +/// +/// Authenticated query allows clients to obtain the seed, +/// and Notification ID of an event for a specific tx_hash, for a specific channel. +/// +fn query_channel_info( + deps: Deps, + env: Env, + channels: Vec, + txhash: Option, + sender_raw: CanonicalAddr, +) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let seed = get_seed(&sender_raw, secret)?; + let mut channels_data = vec![]; + for channel in channels { + let answer_id; + if let Some(tx_hash) = &txhash { + answer_id = Some(notification_id(&seed, &channel, tx_hash)?); + } else { + answer_id = None; + } + match channel.as_str() { + ReceivedNotificationData::CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "txhash".to_string(), + channel, + answer_id, + parameters: None, + data: None, + next_id: None, + counter: None, + cddl: Some(ReceivedNotificationData::CDDL_SCHEMA.to_string()), + }; + channels_data.push(channel_info_data); + } + SpentNotificationData::CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "txhash".to_string(), + channel, + answer_id, + parameters: None, + data: None, + next_id: None, + counter: None, + cddl: Some(SpentNotificationData::CDDL_SCHEMA.to_string()), + }; + channels_data.push(channel_info_data); + } + AllowanceNotificationData::CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "txhash".to_string(), + channel, + answer_id, + parameters: None, + data: None, + next_id: None, + counter: None, + cddl: Some(AllowanceNotificationData::CDDL_SCHEMA.to_string()), + }; + channels_data.push(channel_info_data); + } + MULTI_RECEIVED_CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "bloom".to_string(), + channel, + answer_id, + parameters: Some(BloomParameters { + m: 512, + k: MULTI_RECEIVED_CHANNEL_BLOOM_K, + h: "sha256".to_string(), + }), + data: Some(Descriptor { + r#type: format!("packet[{}]", MULTI_RECEIVED_CHANNEL_BLOOM_N), + version: "1".to_string(), + packet_size: MULTI_RECEIVED_CHANNEL_PACKET_SIZE, + data: StructDescriptor { + r#type: "struct".to_string(), + label: "transfer".to_string(), + members: vec![ + FlatDescriptor { + r#type: "uint128".to_string(), + label: "amount".to_string(), + description: Some( + "The transfer amount in base denomination".to_string(), + ), + }, + FlatDescriptor { + r#type: "bytes8".to_string(), + label: "spender".to_string(), + description: Some( + "The last 8 bytes of the sender's canonical address" + .to_string(), + ), + }, + ], + }, + }), + counter: None, + next_id: None, + cddl: None, + }; + channels_data.push(channel_info_data); + } + MULTI_SPENT_CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "bloom".to_string(), + channel, + answer_id, + parameters: Some(BloomParameters { + m: 512, + k: MULTI_SPENT_CHANNEL_BLOOM_K, + h: "sha256".to_string(), + }), + data: Some(Descriptor { + r#type: format!("packet[{}]", MULTI_SPENT_CHANNEL_BLOOM_N), + version: "1".to_string(), + packet_size: MULTI_SPENT_CHANNEL_PACKET_SIZE, + data: StructDescriptor { + r#type: "struct".to_string(), + label: "transfer".to_string(), + members: vec![ + FlatDescriptor { + r#type: "uint128".to_string(), + label: "amount".to_string(), + description: Some( + "The transfer amount in base denomination".to_string(), + ), + }, + FlatDescriptor { + r#type: "uint128".to_string(), + label: "balance".to_string(), + description: Some( + "Spender's new balance after the transfer".to_string(), + ), + }, + FlatDescriptor { + r#type: "bytes8".to_string(), + label: "recipient".to_string(), + description: Some( + "The last 8 bytes of the recipient's canonical address" + .to_string(), + ), + }, + ], + }, + }), + counter: None, + next_id: None, + cddl: None, + }; + channels_data.push(channel_info_data); + } + _ => { + return Err(StdError::generic_err(format!( + "`{}` channel is undefined", + channel + ))); + } + } + } + + //Ok(Binary(vec![])) + //let schema = CHANNEL_SCHEMATA.get(deps.storage, &channel); + + to_binary(&QueryAnswer::ChannelInfo { + as_of_block: Uint64::from(env.block.height), + channels: channels_data, + seed, + }) +} + +// ***************** +// End SNIP-52 query functions +// ***************** + +// execute functions + fn change_admin(deps: DepsMut, info: MessageInfo, address: String) -> StdResult { let address = deps.api.addr_validate(address.as_str())?; @@ -838,6 +1070,9 @@ fn try_mint( amount: Uint128, memo: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let recipient = deps.api.addr_validate(recipient.as_str())?; let constants = CONFIG.load(deps.storage)?; @@ -867,7 +1102,7 @@ fn try_mint( &mut deps, rng, info.sender, - recipient, + recipient.clone(), Uint128::new(minted_amount), constants.symbol, memo, @@ -876,7 +1111,21 @@ fn try_mint( &mut tracker, )?; - let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); + let received_notification = Notification::new( + recipient, + ReceivedNotificationData { + amount: minted_amount, + sender: None, + }, + ) + .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + + let resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?) + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ); #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); @@ -892,6 +1141,9 @@ fn try_batch_mint( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let constants = CONFIG.load(deps.storage)?; if !constants.mint_is_enabled { @@ -909,6 +1161,7 @@ fn try_batch_mint( let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + let mut notifications = vec![]; // Quick loop to check that the total of amounts is valid for action in actions { let actual_amount = safe_add(&mut total_supply, action.amount.u128()); @@ -922,7 +1175,7 @@ fn try_batch_mint( &mut deps, rng, info.sender.clone(), - recipient, + recipient.clone(), Uint128::new(actual_amount), constants.symbol.clone(), action.memo, @@ -930,11 +1183,37 @@ fn try_batch_mint( #[cfg(feature = "gas_tracking")] &mut tracker, )?; + notifications.push(Notification::new ( + recipient, + ReceivedNotificationData { + amount: actual_amount, + sender: None, + }, + )); } + let tx_hash = env + .transaction + .clone() + .ok_or(StdError::generic_err("no tx hash found"))? + .hash; + let received_data = multi_received_data( + deps.api, + notifications, + &tx_hash, + env.block.random.unwrap(), + secret, + )?; + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?)) + Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?) + .add_attribute_plaintext( + format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), + Binary::from(received_data).to_base64(), + ) + ) } pub fn try_set_key(deps: DepsMut, info: MessageInfo, key: String) -> StdResult { @@ -1227,11 +1506,11 @@ fn try_transfer_impl( memo: Option, block: &cosmwasm_std::BlockInfo, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<()> { +) -> StdResult<(Notification, Notification)> { let raw_sender = deps.api.addr_canonicalize(sender.as_str())?; let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; - perform_transfer( + let sender_balance = perform_transfer( deps.storage, rng, &raw_sender, @@ -1244,8 +1523,25 @@ fn try_transfer_impl( #[cfg(feature = "gas_tracking")] tracker, )?; + let received_notification = Notification::new( + recipient.clone(), + ReceivedNotificationData { + amount: amount.u128(), + sender: Some(sender.clone()), + } + ); - Ok(()) + let spent_notification = Notification::new ( + sender.clone(), + SpentNotificationData { + amount: amount.u128(), + actions: 1, + recipient: Some(recipient.clone()), + balance: sender_balance, + } + ); + + Ok((received_notification, spent_notification)) } #[allow(clippy::too_many_arguments)] @@ -1258,6 +1554,9 @@ fn try_transfer( amount: Uint128, memo: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let recipient: Addr = deps.api.addr_validate(recipient.as_str())?; let symbol = CONFIG.load(deps.storage)?.symbol; @@ -1265,7 +1564,7 @@ fn try_transfer( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); - try_transfer_impl( + let (received_notification, spent_notification) = try_transfer_impl( &mut deps, rng, &info.sender, @@ -1281,7 +1580,30 @@ fn try_transfer( #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("try_transfer.rest"); - let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + let received_notification = received_notification.to_txhash_notification( + deps.api, + &env, + secret, + Some(NOTIFICATION_BLOCK_SIZE), + )?; + + let spent_notification = spent_notification.to_txhash_notification( + deps.api, + &env, + secret, + Some(NOTIFICATION_BLOCK_SIZE) + )?; + + let resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?) + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); #[cfg(feature = "gas_tracking")] group1.log("rest"); @@ -1300,14 +1622,25 @@ fn try_batch_transfer( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { + let num_actions = actions.len(); + if num_actions == 0 { + return Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?) + ); + } + + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let symbol = CONFIG.load(deps.storage)?.symbol; #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); + let mut notifications = vec![]; for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; - try_transfer_impl( + let (received_notification, spent_notification) = try_transfer_impl( &mut deps, rng, &info.sender, @@ -1319,9 +1652,51 @@ fn try_batch_transfer( #[cfg(feature = "gas_tracking")] &mut tracker, )?; + notifications.push((received_notification, spent_notification)); } - let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + let tx_hash = env + .transaction + .clone() + .ok_or(StdError::generic_err("no tx hash found"))? + .hash; + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + let received_data = multi_received_data( + deps.api, + received_notifications, + &tx_hash, + env.block.random.clone().unwrap(), + secret, + )?; + + let total_amount_spent = spent_notifications + .iter() + .fold(0u128, |acc, notification| acc.saturating_add(notification.data.amount)); + + let spent_notification = Notification::new ( + info.sender, + SpentNotificationData { + amount: total_amount_spent, + actions: num_actions as u32, + recipient: spent_notifications[0].data.recipient.clone(), + balance: spent_notifications.last().unwrap().data.balance, + } + ) + .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + + let resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?) + .add_attribute_plaintext( + format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), + Binary::from(received_data).to_base64(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); @@ -1374,8 +1749,8 @@ fn try_send_impl( msg: Option, block: &cosmwasm_std::BlockInfo, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<()> { - try_transfer_impl( +) -> StdResult<(Notification, Notification)> { + let (received_notification, spent_notification) = try_transfer_impl( deps, rng, &sender, @@ -1400,7 +1775,7 @@ fn try_send_impl( memo, )?; - Ok(()) + Ok((received_notification, spent_notification)) } #[allow(clippy::too_many_arguments)] @@ -1415,6 +1790,9 @@ fn try_send( memo: Option, msg: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let recipient = deps.api.addr_validate(recipient.as_str())?; let mut messages = vec![]; @@ -1423,7 +1801,7 @@ fn try_send( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); - try_send_impl( + let (received_notification, spent_notification) = try_send_impl( &mut deps, rng, &mut messages, @@ -1439,9 +1817,26 @@ fn try_send( &mut tracker, )?; + let received_notification = received_notification.to_txhash_notification( + deps.api, + &env, + secret, + Some(NOTIFICATION_BLOCK_SIZE) + )?; + let spent_notification = + spent_notification.to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + let resp = Response::new() .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); + .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?) + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); @@ -1457,7 +1852,21 @@ fn try_batch_send( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { + let num_actions = actions.len(); + if num_actions == 0 { + return Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) + ); + } + + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let mut messages = vec![]; + + let mut notifications = vec![]; + let num_actions: usize = actions.len(); + let symbol = CONFIG.load(deps.storage)?.symbol; #[cfg(feature = "gas_tracking")] @@ -1465,7 +1874,7 @@ fn try_batch_send( for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; - try_send_impl( + let (received_notification, spent_notification) = try_send_impl( &mut deps, rng, &mut messages, @@ -1480,11 +1889,54 @@ fn try_batch_send( #[cfg(feature = "gas_tracking")] &mut tracker, )?; + notifications.push((received_notification, spent_notification)); } + let tx_hash = env + .transaction + .clone() + .ok_or(StdError::generic_err("no tx hash found"))? + .hash; + + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + let received_data = multi_received_data( + deps.api, + received_notifications, + &tx_hash, + env.block.random.clone().unwrap(), + secret, + )?; + + let total_amount_spent = spent_notifications + .iter() + .fold(0u128, |acc, notification| acc + notification.data.amount); + + let spent_notification = Notification::new ( + info.sender, + SpentNotificationData { + amount: total_amount_spent, + actions: num_actions as u32, + recipient: spent_notifications[0].data.recipient.clone(), + balance: spent_notifications.last().unwrap().data.balance, + } + ) + .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + Ok(Response::new() .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?)) + .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) + .add_attribute_plaintext( + format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), + Binary::from(received_data).to_base64(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ) + ) } fn try_register_receive( @@ -2119,7 +2571,7 @@ fn perform_transfer( memo: Option, block: &BlockInfo, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<()> { +) -> StdResult { #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("perform_transfer.1"); @@ -2138,7 +2590,7 @@ fn perform_transfer( let transfer_str = "transfer"; // settle the owner's account - dwb.settle_sender_or_owner_account( + let owner_balance = dwb.settle_sender_or_owner_account( store, from, tx_id, @@ -2180,7 +2632,7 @@ fn perform_transfer( #[cfg(feature = "gas_tracking")] group2.log("DWB.save"); - Ok(()) + Ok(owner_balance) } fn perform_mint( diff --git a/src/dwb.rs b/src/dwb.rs index 8bb0462f..8b4c7adf 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -88,7 +88,7 @@ impl DelayedWriteBuffer { amount_spent: u128, op_name: &str, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, - ) -> StdResult<()> { + ) -> StdResult { #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("settle_sender_or_owner_account.1"); @@ -98,7 +98,8 @@ impl DelayedWriteBuffer { #[cfg(feature = "gas_tracking")] group1.log("release_dwb_recipient"); - if balance.checked_sub(amount_spent).is_none() { + let checked_balance = balance.checked_sub(amount_spent); + if checked_balance.is_none() { return Err(StdError::generic_err(format!( "insufficient funds to {op_name}: balance={balance}, required={amount_spent}", ))); @@ -119,15 +120,15 @@ impl DelayedWriteBuffer { entry.amount()? )); - let result = merge_dwb_entry( + merge_dwb_entry( store, &entry, Some(amount_spent), #[cfg(feature = "gas_tracking")] tracker, - ); + )?; - result + Ok(checked_balance.unwrap()) } /// "releases" a given recipient from the buffer, removing their entry if one exists diff --git a/src/lib.rs b/src/lib.rs index 0713f569..b9928515 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,3 +11,4 @@ pub mod receiver; pub mod state; mod strings; mod transaction_history; +mod notifications; \ No newline at end of file diff --git a/src/msg.rs b/src/msg.rs index 1a29ab98..09d0be73 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -4,10 +4,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{batch, transaction_history::Tx}; -use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128,}; +use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint64,}; #[cfg(feature = "gas_evaporation")] use cosmwasm_std::Uint64; -use secret_toolkit::permit::Permit; +use secret_toolkit::{notification::ChannelInfoData, permit::Permit}; #[cfg_attr(test, derive(Eq, PartialEq))] #[derive(Serialize, Deserialize, Clone, JsonSchema)] @@ -491,15 +491,37 @@ pub enum QueryMsg { page_size: u32, }, Minters {}, + + // SNIP-52 Private Push Notifications + /// Public query to list all notification channels + ListChannels {}, + /// Authenticated query allows clients to obtain the seed + /// and schema for a specific channel. + ChannelInfo { + channels: Vec, + txhash: Option, + viewer: ViewerInfo, + }, + WithPermit { permit: Permit, query: QueryWithPermit, }, + // for debug purposes only #[cfg(feature = "gas_tracking")] Dwb {}, } +/// the address and viewing key making an authenticated query request +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct ViewerInfo { + /// querying address + pub address: String, + /// authentication key string + pub viewing_key: String, +} + impl QueryMsg { pub fn get_validation_params(&self, api: &dyn Api) -> StdResult<(Vec, String)> { match self { @@ -534,6 +556,10 @@ impl QueryMsg { let spender = api.addr_validate(spender.as_str())?; Ok((vec![spender], key.clone())) } + Self::ChannelInfo { viewer, .. } => { + let address = api.addr_validate(viewer.address.as_str())?; + Ok((vec![address], viewer.viewing_key.clone())) + } _ => panic!("This query type does not require authentication"), } } @@ -566,6 +592,11 @@ pub enum QueryWithPermit { page: Option, page_size: u32, }, + // SNIP-52 Private Push Notifications + ChannelInfo { + channels: Vec, + txhash: Option, + }, } #[derive(Serialize, Deserialize, JsonSchema, Debug)] @@ -621,6 +652,18 @@ pub enum QueryAnswer { Minters { minters: Vec, }, + + // SNIP-52 Private Push Notifications + ListChannels { + channels: Vec, + }, + ChannelInfo { + /// scopes validity of this response + as_of_block: Uint64, + /// shared secret in base64 + seed: Binary, + channels: Vec, + }, #[cfg(feature = "gas_tracking")] Dwb { diff --git a/src/notifications.rs b/src/notifications.rs new file mode 100644 index 00000000..38e76f74 --- /dev/null +++ b/src/notifications.rs @@ -0,0 +1,402 @@ +use std::collections::HashMap; + +use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, StdError, StdResult}; +use primitive_types::{U256, U512}; +use secret_toolkit::notification::{get_seed, notification_id, xor_bytes, Notification, NotificationData}; +use minicbor_ser as cbor; +use secret_toolkit_crypto::{hkdf_sha_512, sha_256}; +use serde::{Deserialize, Serialize}; + +const ZERO_ADDR: [u8; 20] = [0u8; 20]; + +// recvd = [ +// amount: biguint, ; transfer amount in base denomination +// sender: bstr, ; byte sequence of sender's canonical address +// balance: biguint ; recipient's new balance after the transfer +// ] + +#[derive(Serialize, Debug, Deserialize, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct ReceivedNotificationData { + pub amount: u128, + pub sender: Option, +} + +impl NotificationData for ReceivedNotificationData { + const CHANNEL_ID: &'static str = "recvd"; + const CDDL_SCHEMA: &'static str = "recvd=[amount:biguint,sender:bstr]"; + + fn to_cbor(&self, api: &dyn Api) -> StdResult> { + let received_data; + if let Some(sender) = &self.sender { + let sender_raw = api.addr_canonicalize(sender.as_str())?; + received_data = cbor::to_vec(&(self.amount.to_be_bytes(), sender_raw.as_slice())) + .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + } else { + received_data = cbor::to_vec(&(self.amount.to_be_bytes(), ZERO_ADDR)) + .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + } + Ok(received_data) + } +} + +// spent = [ +// amount: biguint, ; transfer amount in base denomination +// actions: uint ; number of actions the execution performed +// recipient: bstr, ; byte sequence of first recipient's canonical address +// balance: biguint ; sender's new balance aactions: uint ; number of actions the execution performedfter the transfer +// ] + +#[derive(Serialize, Debug, Deserialize, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct SpentNotificationData { + pub amount: u128, + pub actions: u32, + pub recipient: Option, + pub balance: u128, +} + +impl NotificationData for SpentNotificationData { + const CHANNEL_ID: &'static str = "spent"; + const CDDL_SCHEMA: &'static str = "spent=[amount:biguint,actions:uint,recipient:bstr,balance:biguint]"; + fn to_cbor(&self, api: &dyn Api) -> StdResult> { + let spent_data; + if let Some(recipient) = &self.recipient { + let recipient_raw = api.addr_canonicalize(recipient.as_str())?; + spent_data = cbor::to_vec(&( + self.amount.to_be_bytes(), + self.actions.to_be_bytes(), + recipient_raw.as_slice(), + self.balance.to_be_bytes(), + )) + .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + } else { + spent_data = cbor::to_vec(&( + self.amount.to_be_bytes(), + self.actions.to_be_bytes(), + ZERO_ADDR, + self.balance.to_be_bytes(), + )) + .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + } + Ok(spent_data) + } +} + +//allowance = [ +// amount: biguint, ; allowance amount in base denomination +// allower: bstr, ; byte sequence of allower's canonical address +// expiration: uint, ; epoch seconds of allowance expiration +//] + +#[derive(Serialize, Debug, Deserialize, Clone)] +#[cfg_attr(test, derive(Eq, PartialEq))] +pub struct AllowanceNotificationData { + pub amount: u128, + pub allower: Addr, + pub expiration: Option, +} + +impl NotificationData for AllowanceNotificationData { + const CHANNEL_ID: &'static str = "allowance"; + const CDDL_SCHEMA: &'static str = "allowance=[amount:biguint,allower:bstr,expiration:uint]"; + fn to_cbor(&self, api: &dyn Api) -> StdResult> { + let allower_raw = api.addr_canonicalize(self.allower.as_str())?; + + // use CBOR to encode data + let updated_allowance_data = cbor::to_vec(&( + self.amount.to_be_bytes(), + allower_raw.as_slice(), + self.expiration.unwrap_or(0u64), // expiration == 0 means no expiration + )) + .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + Ok(updated_allowance_data) + } +} + +// multi recipient push notifications + +// id for the `multirecvd` channel +pub const MULTI_RECEIVED_CHANNEL_ID: &str = "multirecvd"; +pub const MULTI_RECEIVED_CHANNEL_BLOOM_K: u32 = 15; +pub const MULTI_RECEIVED_CHANNEL_BLOOM_N: u32 = 16; +pub const MULTI_RECEIVED_CHANNEL_PACKET_SIZE: u32 = 24; + +// id for the `multispent` channel +pub const MULTI_SPENT_CHANNEL_ID: &str = "multispent"; +pub const MULTI_SPENT_CHANNEL_BLOOM_K: u32 = 5; +pub const MULTI_SPENT_CHANNEL_BLOOM_N: u32 = 4; +pub const MULTI_SPENT_CHANNEL_PACKET_SIZE: u32 = 40; + +pub fn multi_received_data( + api: &dyn Api, + notifications: Vec>, + tx_hash: &String, + env_random: Binary, + secret: &[u8], +) -> StdResult> { + let mut received_bloom_filter: U512 = U512::from(0); + let mut received_packets: Vec<(Addr, Vec)> = vec![]; + + // keep track of how often addresses might show up in packet data. + // we need to remove any address that might show up more than once. + let mut recipient_counts: HashMap = HashMap::new(); + + for notification in ¬ifications { + recipient_counts.insert( + notification.notification_for.clone(), + recipient_counts + .get(¬ification.notification_for) + .unwrap_or(&0u16) + + 1, + ); + + // we can short circuit this if recipient count > 1, since we will throw out this packet + // anyway, and address has already been added to bloom filter + if *recipient_counts + .get(¬ification.notification_for) + .unwrap() + > 1 + { + continue; + } + + // contribute to received bloom filter + let recipient_addr_raw = api.addr_canonicalize(notification.notification_for.as_str())?; + let seed = get_seed(&recipient_addr_raw, secret)?; + let id = notification_id(&seed, &MULTI_RECEIVED_CHANNEL_ID.to_string(), &tx_hash)?; + let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); + for _ in 0..MULTI_RECEIVED_CHANNEL_BLOOM_K { + let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize(); + received_bloom_filter = received_bloom_filter | (U512::from(1) << bit_index); + hash_bytes = hash_bytes >> 9; + } + + // make the received packet + let mut received_packet_plaintext: Vec = vec![]; + // amount bytes (u128 == 16 bytes) + received_packet_plaintext.extend_from_slice(¬ification.data.amount.to_be_bytes()); + // sender account last 8 bytes + let sender_bytes: &[u8]; + let sender_raw; + if let Some(sender) = ¬ification.data.sender { + sender_raw = api.addr_canonicalize(sender.as_str())?; + sender_bytes = &sender_raw.as_slice()[sender_raw.0.len() - 8..]; + } else { + sender_bytes = &ZERO_ADDR[ZERO_ADDR.len() - 8..]; + } + // 24 bytes total + received_packet_plaintext.extend_from_slice(sender_bytes); + + let received_packet_id = &id.0.as_slice()[0..8]; + let received_packet_ikm = &id.0.as_slice()[8..32]; + let received_packet_ciphertext = + xor_bytes(received_packet_plaintext.as_slice(), received_packet_ikm); + let received_packet_bytes: Vec = + [received_packet_id.to_vec(), received_packet_ciphertext].concat(); + + received_packets.push((notification.notification_for.clone(), received_packet_bytes)); + } + + // filter out any notifications for recipients showing up more than once + let mut received_packets: Vec> = received_packets + .into_iter() + .filter(|(addr, _)| *recipient_counts.get(addr).unwrap_or(&0u16) <= 1) + .map(|(_, packet)| packet) + .collect(); + if received_packets.len() > MULTI_RECEIVED_CHANNEL_BLOOM_N as usize { + // still too many packets + received_packets = received_packets[0..MULTI_RECEIVED_CHANNEL_BLOOM_N as usize].to_vec(); + } + + // now add extra packets, if needed, to hide number of packets + let padding_size = + MULTI_RECEIVED_CHANNEL_BLOOM_N.saturating_sub(received_packets.len() as u32) as usize; + if padding_size > 0 { + let padding_addresses = hkdf_sha_512( + &Some(vec![0u8; 64]), + &env_random, + format!("{}:decoys", MULTI_RECEIVED_CHANNEL_ID).as_bytes(), + padding_size * 20, // 20 bytes per random addr + )?; + + // handle each padding package + for i in 0..padding_size { + let padding_address = &padding_addresses[i * 20..(i + 1) * 20]; + + // contribute padding packet to bloom filter + let seed = get_seed(&CanonicalAddr::from(padding_address), secret)?; + let id = notification_id(&seed, &MULTI_RECEIVED_CHANNEL_ID.to_string(), &tx_hash)?; + let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); + for _ in 0..MULTI_RECEIVED_CHANNEL_BLOOM_K { + let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize(); + received_bloom_filter = received_bloom_filter | (U512::from(1) << bit_index); + hash_bytes = hash_bytes >> 9; + } + + // padding packet plaintext + let padding_packet_plaintext = [0u8; MULTI_RECEIVED_CHANNEL_PACKET_SIZE as usize]; + let padding_packet_id = &id.0.as_slice()[0..8]; + let padding_packet_ikm = &id.0.as_slice()[8..32]; + let padding_packet_ciphertext = + xor_bytes(padding_packet_plaintext.as_slice(), padding_packet_ikm); + let padding_packet_bytes: Vec = + [padding_packet_id.to_vec(), padding_packet_ciphertext].concat(); + received_packets.push(padding_packet_bytes); + } + } + + let mut received_bloom_filter_bytes: Vec = vec![]; + for biguint in received_bloom_filter.0 { + received_bloom_filter_bytes.extend_from_slice(&biguint.to_be_bytes()); + } + for packet in received_packets { + received_bloom_filter_bytes.extend(packet.iter()); + } + + Ok(received_bloom_filter_bytes) +} + +pub fn multi_spent_data( + api: &dyn Api, + notifications: Vec>, + tx_hash: &String, + env_random: Binary, + secret: &[u8], +) -> StdResult> { + let mut spent_bloom_filter: U512 = U512::from(0); + let mut spent_packets: Vec<(Addr, Vec)> = vec![]; + + // keep track of how often addresses might show up in packet data. + // we need to remove any address that might show up more than once. + let mut spent_counts: HashMap = HashMap::new(); + + for notification in ¬ifications { + spent_counts.insert( + notification.notification_for.clone(), + spent_counts + .get(¬ification.notification_for) + .unwrap_or(&0u16) + + 1, + ); + + // we can short circuit this if recipient count > 1, since we will throw out this packet + // anyway, and address has already been added to bloom filter + if *spent_counts.get(¬ification.notification_for).unwrap() > 1 { + continue; + } + + let spender_addr_raw = api.addr_canonicalize(notification.notification_for.as_str())?; + let seed = get_seed(&spender_addr_raw, secret)?; + let id = notification_id(&seed, &MULTI_SPENT_CHANNEL_ID.to_string(), &tx_hash)?; + let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); + for _ in 0..MULTI_SPENT_CHANNEL_BLOOM_K { + let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize(); + spent_bloom_filter = spent_bloom_filter | (U512::from(1) << bit_index); + hash_bytes = hash_bytes >> 9; + } + + // make the spent packet + let mut spent_packet_plaintext: Vec = vec![]; + // amount bytes (u128 == 16 bytes) + spent_packet_plaintext.extend_from_slice(¬ification.data.amount.to_be_bytes()); + // balance bytes (u128 == 16 bytes) + spent_packet_plaintext.extend_from_slice(¬ification.data.balance.to_be_bytes()); + // recipient account last 8 bytes + let recipient_bytes: &[u8]; + let recipient_raw; + if let Some(recipient) = ¬ification.data.recipient { + recipient_raw = api.addr_canonicalize(recipient.as_str())?; + recipient_bytes = &recipient_raw.as_slice()[recipient_raw.0.len() - 8..]; + } else { + recipient_bytes = &ZERO_ADDR[ZERO_ADDR.len() - 8..]; + } + // 40 bytes total + spent_packet_plaintext.extend_from_slice(recipient_bytes); + + let spent_packet_size = spent_packet_plaintext.len(); + let spent_packet_id = &id.0.as_slice()[0..8]; + let spent_packet_ikm = &id.0.as_slice()[8..32]; + let spent_packet_key = hkdf_sha_512( + &Some(vec![0u8; 64]), + spent_packet_ikm, + "".as_bytes(), + spent_packet_size, + )?; + let spent_packet_ciphertext = xor_bytes( + spent_packet_plaintext.as_slice(), + spent_packet_key.as_slice(), + ); + let spent_packet_bytes: Vec = + [spent_packet_id.to_vec(), spent_packet_ciphertext].concat(); + + spent_packets.push((notification.notification_for.clone(), spent_packet_bytes)); + } + + // filter out any notifications for senders showing up more than once + let mut spent_packets: Vec> = spent_packets + .into_iter() + .filter(|(addr, _)| *spent_counts.get(addr).unwrap_or(&0u16) <= 1) + .map(|(_, packet)| packet) + .collect(); + if spent_packets.len() > MULTI_SPENT_CHANNEL_BLOOM_N as usize { + // still too many packets + spent_packets = spent_packets[0..MULTI_SPENT_CHANNEL_BLOOM_N as usize].to_vec(); + } + + // now add extra packets, if needed, to hide number of packets + let padding_size = + MULTI_SPENT_CHANNEL_BLOOM_N.saturating_sub(spent_packets.len() as u32) as usize; + if padding_size > 0 { + let padding_addresses = hkdf_sha_512( + &Some(vec![0u8; 64]), + &env_random, + format!("{}:decoys", MULTI_SPENT_CHANNEL_ID).as_bytes(), + padding_size * 20, // 20 bytes per random addr + )?; + + // handle each padding package + for i in 0..padding_size { + let padding_address = &padding_addresses[i * 20..(i + 1) * 20]; + + // contribute padding packet to bloom filter + let seed = get_seed(&CanonicalAddr::from(padding_address), secret)?; + let id = notification_id(&seed, &MULTI_SPENT_CHANNEL_ID.to_string(), &tx_hash)?; + let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); + for _ in 0..MULTI_SPENT_CHANNEL_BLOOM_K { + let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize(); + spent_bloom_filter = spent_bloom_filter | (U512::from(1) << bit_index); + hash_bytes = hash_bytes >> 9; + } + + // padding packet plaintext + let padding_packet_plaintext = [0u8; MULTI_SPENT_CHANNEL_PACKET_SIZE as usize]; + let padding_plaintext_size = MULTI_SPENT_CHANNEL_PACKET_SIZE as usize; + let padding_packet_id = &id.0.as_slice()[0..8]; + let padding_packet_ikm = &id.0.as_slice()[8..32]; + let padding_packet_key = hkdf_sha_512( + &Some(vec![0u8; 64]), + padding_packet_ikm, + "".as_bytes(), + padding_plaintext_size, + )?; + let padding_packet_ciphertext = xor_bytes( + padding_packet_plaintext.as_slice(), + padding_packet_key.as_slice(), + ); + let padding_packet_bytes: Vec = + [padding_packet_id.to_vec(), padding_packet_ciphertext].concat(); + spent_packets.push(padding_packet_bytes); + } + } + + let mut spent_bloom_filter_bytes: Vec = vec![]; + for biguint in spent_bloom_filter.0 { + spent_bloom_filter_bytes.extend_from_slice(&biguint.to_be_bytes()); + } + for packet in spent_packets { + spent_bloom_filter_bytes.extend(packet.iter()); + } + + Ok(spent_bloom_filter_bytes) +} \ No newline at end of file diff --git a/src/state.rs b/src/state.rs index 644f5a7b..3c44b4fb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -223,3 +223,7 @@ impl ReceiverHashStore { } pub static INTERNAL_SECRET: Item> = Item::new(b"internal-secret"); + +// SNIP-52 channels +pub static CHANNELS: Keyset = Keyset::new(b"channel-ids"); + From 055805c3a3262645cc7d3622827b8b123e1e67b4 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 16 Oct 2024 11:13:57 +1300 Subject: [PATCH 66/87] dev: add remainder of notifications --- src/contract.rs | 323 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 287 insertions(+), 36 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index 0a9ecaeb..29773f15 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -29,7 +29,7 @@ use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, }; -use crate::notifications::{multi_received_data, AllowanceNotificationData, ReceivedNotificationData, SpentNotificationData, MULTI_RECEIVED_CHANNEL_BLOOM_K, MULTI_RECEIVED_CHANNEL_BLOOM_N, MULTI_RECEIVED_CHANNEL_ID, MULTI_RECEIVED_CHANNEL_PACKET_SIZE, MULTI_SPENT_CHANNEL_BLOOM_K, MULTI_SPENT_CHANNEL_BLOOM_N, MULTI_SPENT_CHANNEL_ID, MULTI_SPENT_CHANNEL_PACKET_SIZE}; +use crate::notifications::{multi_received_data, multi_spent_data, AllowanceNotificationData, ReceivedNotificationData, SpentNotificationData, MULTI_RECEIVED_CHANNEL_BLOOM_K, MULTI_RECEIVED_CHANNEL_BLOOM_N, MULTI_RECEIVED_CHANNEL_ID, MULTI_RECEIVED_CHANNEL_PACKET_SIZE, MULTI_SPENT_CHANNEL_BLOOM_K, MULTI_SPENT_CHANNEL_BLOOM_N, MULTI_SPENT_CHANNEL_ID, MULTI_SPENT_CHANNEL_PACKET_SIZE}; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, TOTAL_SUPPLY @@ -1992,7 +1992,7 @@ fn try_transfer_from_impl( amount: Uint128, denom: String, memo: Option, -) -> StdResult<()> { +) -> StdResult<(Notification, Notification)> { let raw_amount = amount.u128(); let raw_spender = deps.api.addr_canonicalize(spender.as_str())?; let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; @@ -2003,7 +2003,7 @@ fn try_transfer_from_impl( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); - perform_transfer( + let owner_balance = perform_transfer( deps.storage, rng, &raw_owner, @@ -2017,7 +2017,25 @@ fn try_transfer_from_impl( &mut tracker, )?; - Ok(()) + let received_notification = Notification::new( + recipient.clone(), + ReceivedNotificationData { + amount: amount.u128(), + sender: Some(owner.clone()), + } + ); + + let spent_notification = Notification::new ( + owner.clone(), + SpentNotificationData { + amount: amount.u128(), + actions: 1, + recipient: Some(recipient.clone()), + balance: owner_balance, + } + ); + + Ok((received_notification, spent_notification)) } #[allow(clippy::too_many_arguments)] @@ -2031,10 +2049,13 @@ fn try_transfer_from( amount: Uint128, memo: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; let symbol = CONFIG.load(deps.storage)?.symbol; - try_transfer_from_impl( + let (received_notification, spent_notification) = try_transfer_from_impl( &mut deps, rng, env, @@ -2045,8 +2066,32 @@ fn try_transfer_from( symbol, memo, )?; + let received_notification = received_notification.to_txhash_notification( + deps.api, + &env, + secret, + Some(NOTIFICATION_BLOCK_SIZE), + )?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?)) + let spent_notification = spent_notification.to_txhash_notification( + deps.api, + &env, + secret, + Some(NOTIFICATION_BLOCK_SIZE) + )?; + + Ok( + Response::new() + .set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?) + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ) + ) } fn try_batch_transfer_from( @@ -2056,11 +2101,16 @@ fn try_batch_transfer_from( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + + let mut notifications = vec![]; + let symbol = CONFIG.load(deps.storage)?.symbol; for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; - try_transfer_from_impl( + let (received_notification, spent_notification) = try_transfer_from_impl( &mut deps, rng, env, @@ -2071,12 +2121,45 @@ fn try_batch_transfer_from( symbol.clone(), action.memo, )?; + notifications.push((received_notification, spent_notification)); } + let tx_hash = env + .transaction + .clone() + .ok_or(StdError::generic_err("no tx hash found"))? + .hash; + + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + let received_data = multi_received_data( + deps.api, + received_notifications, + &tx_hash, + env.block.random.clone().unwrap(), + secret, + )?; + let spent_data = multi_spent_data( + deps.api, + spent_notifications, + &tx_hash, + env.block.random.clone().unwrap(), + secret, + )?; + Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransferFrom { - status: Success, - })?), + Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchTransferFrom {status: Success})?) + .add_attribute_plaintext( + format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), + Binary::from(received_data).to_base64(), + ) + .add_attribute_plaintext( + format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID), + Binary::from(spent_data).to_base64(), + ) ) } @@ -2093,10 +2176,10 @@ fn try_send_from_impl( amount: Uint128, memo: Option, msg: Option, -) -> StdResult<()> { +) -> StdResult<(Notification, Notification)> { let spender = info.sender.clone(); let symbol = CONFIG.load(deps.storage)?.symbol; - try_transfer_from_impl( + let (received_notification, spent_notification) = try_transfer_from_impl( deps, rng, &env, @@ -2120,7 +2203,7 @@ fn try_send_from_impl( memo, )?; - Ok(()) + Ok((received_notification, spent_notification)) } #[allow(clippy::too_many_arguments)] @@ -2136,12 +2219,15 @@ fn try_send_from( memo: Option, msg: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; let mut messages = vec![]; - try_send_from_impl( + let (received_notification, spent_notification) = try_send_from_impl( &mut deps, - env, + env.clone(), info, rng, &mut messages, @@ -2153,9 +2239,27 @@ fn try_send_from( msg, )?; + let received_notification = received_notification.to_txhash_notification( + deps.api, + &env, + secret, + Some(NOTIFICATION_BLOCK_SIZE), + )?; + let spent_notification = + spent_notification.to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + Ok(Response::new() .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?)) + .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?) + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ) + ) } fn try_batch_send_from( @@ -2165,12 +2269,16 @@ fn try_batch_send_from( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let mut messages = vec![]; + let mut notifications = vec![]; for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; - try_send_from_impl( + let (received_notification, spent_notification) = try_send_from_impl( &mut deps, env.clone(), info, @@ -2183,13 +2291,48 @@ fn try_batch_send_from( action.memo, action.msg, )?; + notifications.push((received_notification, spent_notification)); } + let tx_hash = env + .transaction + .clone() + .ok_or(StdError::generic_err("no tx hash found"))? + .hash; + + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + let received_data = multi_received_data( + deps.api, + received_notifications, + &tx_hash, + env.block.random.clone().unwrap(), + secret, + )?; + let spent_data = multi_spent_data( + deps.api, + spent_notifications, + &tx_hash, + env.block.random.clone().unwrap(), + secret, + )?; + Ok(Response::new() .add_messages(messages) .set_data(to_binary(&ExecuteAnswer::BatchSendFrom { status: Success, - })?)) + })?) + .add_attribute_plaintext( + format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), + Binary::from(received_data).to_base64(), + ) + .add_attribute_plaintext( + format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID), + Binary::from(spent_data).to_base64(), + ) + ) } #[allow(clippy::too_many_arguments)] @@ -2201,6 +2344,9 @@ fn try_burn_from( amount: Uint128, memo: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let owner = deps.api.addr_validate(owner.as_str())?; let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; let constants = CONFIG.load(deps.storage)?; @@ -2231,7 +2377,7 @@ fn try_burn_from( let mut tracker = GasTracker::new(deps.api); // settle the owner's account in buffer - dwb.settle_sender_or_owner_account( + let owner_balance = dwb.settle_sender_or_owner_account( deps.storage, &raw_owner, tx_id, @@ -2267,7 +2413,25 @@ fn try_burn_from( } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?)) + let spent_notification = Notification::new ( + owner, + SpentNotificationData { + amount: raw_amount, + actions: 1, + recipient: None, + balance: owner_balance, + } + ) + .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + + Ok( + Response::new() + .set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ) + ) } fn try_batch_burn_from( @@ -2276,6 +2440,9 @@ fn try_batch_burn_from( info: MessageInfo, actions: Vec, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let constants = CONFIG.load(deps.storage)?; if !constants.burn_is_enabled { return Err(StdError::generic_err( @@ -2285,6 +2452,7 @@ fn try_batch_burn_from( let raw_spender = deps.api.addr_canonicalize(info.sender.as_str())?; let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + let mut spent_notifications = vec![]; for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; @@ -2309,7 +2477,7 @@ fn try_batch_burn_from( let mut tracker = GasTracker::new(deps.api); // settle the owner's account in buffer - dwb.settle_sender_or_owner_account( + let owner_balance = dwb.settle_sender_or_owner_account( deps.storage, &raw_owner, tx_id, @@ -2340,14 +2508,40 @@ fn try_batch_burn_from( "You're trying to burn more than is available in the total supply: {action:?}", ))); } + + spent_notifications.push(Notification::new ( + info.sender.clone(), + SpentNotificationData { + amount, + actions: 1, + recipient: None, + balance: owner_balance, + } + )); } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + let tx_hash = env + .transaction + .clone() + .ok_or(StdError::generic_err("no tx hash found"))? + .hash; + let spent_data = multi_spent_data( + deps.api, + spent_notifications, + &tx_hash, + env.block.random.clone().unwrap(), + secret, + )?; + Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::BatchBurnFrom { - status: Success, - })?), + Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchBurnFrom {status: Success,})?) + .add_attribute_plaintext( + format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID), + Binary::from(spent_data).to_base64(), + ) ) } @@ -2359,6 +2553,9 @@ fn try_increase_allowance( amount: Uint128, expiration: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let spender = deps.api.addr_validate(spender.as_str())?; let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender); @@ -2378,12 +2575,27 @@ fn try_increase_allowance( let new_amount = allowance.amount; AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; + let notification = Notification::new ( + spender.clone(), + AllowanceNotificationData { + amount: new_amount, + allower: info.sender.clone(), + expiration, + } + ) + .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { - owner: info.sender, - spender, - allowance: Uint128::from(new_amount), - })?), + Response::new() + .set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { + owner: info.sender, + spender, + allowance: Uint128::from(new_amount), + })?) + .add_attribute_plaintext( + notification.id_plaintext(), + notification.data_plaintext() + ) ) } @@ -2395,6 +2607,9 @@ fn try_decrease_allowance( amount: Uint128, expiration: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let spender = deps.api.addr_validate(spender.as_str())?; let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender); @@ -2414,12 +2629,27 @@ fn try_decrease_allowance( let new_amount = allowance.amount; AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; + let notification = Notification::new ( + spender.clone(), + AllowanceNotificationData { + amount: new_amount, + allower: info.sender.clone(), + expiration, + } + ) + .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { - owner: info.sender, - spender, - allowance: Uint128::from(new_amount), - })?), + Response::new() + .set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { + owner: info.sender, + spender, + allowance: Uint128::from(new_amount), + })?) + .add_attribute_plaintext( + notification.id_plaintext(), + notification.data_plaintext() + ) ) } @@ -2508,6 +2738,9 @@ fn try_burn( amount: Uint128, memo: Option, ) -> StdResult { + let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = secret.as_slice(); + let constants = CONFIG.load(deps.storage)?; if !constants.burn_is_enabled { return Err(StdError::generic_err( @@ -2535,7 +2768,7 @@ fn try_burn( let mut tracker = GasTracker::new(deps.api); // settle the signer's account in buffer - dwb.settle_sender_or_owner_account( + let owner_balance = dwb.settle_sender_or_owner_account( deps.storage, &raw_burn_address, tx_id, @@ -2557,7 +2790,25 @@ fn try_burn( } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?)) + let spent_notification = Notification::new ( + info.sender, + SpentNotificationData { + amount: raw_amount, + actions: 1, + recipient: None, + balance: owner_balance, + } + ) + .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + + Ok( + Response::new() + .set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ) + ) } fn perform_transfer( From 508c33ba8153c28258204bb9820ac2a9b7328e9c Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 18 Dec 2024 21:26:42 +1300 Subject: [PATCH 67/87] update dependencies --- Cargo.lock | 316 +++++++++++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 15 ++- 2 files changed, 281 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d66b8a9f..540923ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aead" @@ -23,6 +23,27 @@ dependencies = [ "version_check", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "base16ct" version = "0.1.1" @@ -81,6 +102,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -89,9 +116,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.0.99" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" [[package]] name = "cfg-if" @@ -123,6 +150,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "cipher" version = "0.4.4" @@ -146,6 +187,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cosmwasm-derive" version = "1.5.5" @@ -414,6 +461,29 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "inout" version = "0.1.3" @@ -429,6 +499,16 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "k256" version = "0.11.6" @@ -448,33 +528,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] -name = "minicbor" -version = "0.18.0" +name = "log" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a20020e8e2d1881d8736f64011bb5ff99f1db9947ce3089706945c8915695cb" -dependencies = [ - "minicbor-derive", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "minicbor-derive" -version = "0.12.0" +name = "minicbor" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8608fb1c805b5b6b3d5ab7bd95c40c396df622b64d77b2d621a5eae1eed050ee" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "c0452a60c1863c1f50b5f77cd295e8d2786849f35883f0b9e18e7e6e1b5691b0" [[package]] -name = "minicbor-ser" -version = "0.2.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0834b86a9c56311671913d56f640d7f0b6da803df61121661cc890f0edc0eb1" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "minicbor", - "serde", + "autocfg", ] [[package]] @@ -523,7 +594,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "uint 0.10.0", ] [[package]] @@ -700,7 +781,7 @@ dependencies = [ "serde", "serde-json-wasm", "thiserror", - "uint", + "uint 0.9.5", ] [[package]] @@ -715,8 +796,8 @@ dependencies = [ [[package]] name = "secret-toolkit" -version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" +version = "0.10.3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" dependencies = [ "secret-toolkit-crypto", "secret-toolkit-notification", @@ -729,9 +810,10 @@ dependencies = [ [[package]] name = "secret-toolkit-crypto" -version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" +version = "0.10.3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" dependencies = [ + "cc", "hkdf", "rand_chacha", "rand_core 0.6.4", @@ -742,14 +824,15 @@ dependencies = [ [[package]] name = "secret-toolkit-notification" -version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" +version = "0.10.3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" dependencies = [ "chacha20poly1305", "generic-array", + "hex", "hkdf", - "minicbor-ser", - "primitive-types", + "minicbor", + "primitive-types 0.12.2", "ripemd", "schemars", "secret-cosmwasm-std", @@ -760,8 +843,8 @@ dependencies = [ [[package]] name = "secret-toolkit-permit" -version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" +version = "0.10.3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" dependencies = [ "bech32", "remain", @@ -769,13 +852,15 @@ dependencies = [ "schemars", "secret-cosmwasm-std", "secret-toolkit-crypto", + "secret-toolkit-storage", + "secret-toolkit-utils", "serde", ] [[package]] name = "secret-toolkit-serialization" -version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" +version = "0.10.3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" dependencies = [ "bincode2", "schemars", @@ -785,8 +870,8 @@ dependencies = [ [[package]] name = "secret-toolkit-storage" -version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" +version = "0.10.3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" dependencies = [ "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -796,9 +881,10 @@ dependencies = [ [[package]] name = "secret-toolkit-utils" -version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" +version = "0.10.3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" dependencies = [ + "chrono", "schemars", "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -807,8 +893,8 @@ dependencies = [ [[package]] name = "secret-toolkit-viewing-key" -version = "0.10.0" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95" +version = "0.10.3" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" dependencies = [ "base64 0.21.7", "schemars", @@ -921,9 +1007,12 @@ dependencies = [ "base64 0.21.7", "constant_time_eq", "cosmwasm-schema", - "minicbor-ser", - "primitive-types", + "hex", + "minicbor", + "primitive-types 0.13.1", "rand", + "rand_chacha", + "rand_core 0.6.4", "schemars", "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -1016,6 +1105,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -1044,6 +1145,133 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index b63d7f2b..987a7fbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,19 +37,22 @@ gas_evaporation = [] cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } rand = { version = "0.8.5", default-features = false } -# secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] } -secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"], rev = "8aed92d589dc119f69d20f8538d5a6eea8003d95" } -# secret-toolkit-crypto = { version = "0.10.0", default-features = false, features = ["hash"] } -secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash"], rev = "8aed92d589dc119f69d20f8538d5a6eea8003d95" } +# secret-toolkit = { version = "0.10.2", default-features = false, features = ["permit", "storage", "viewing-key", "notification"] } +secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"], rev = "be621610d342416b9e3591cc4a61a05853673c13" } +# secret-toolkit-crypto = { version = "0.10.2", default-features = false, features = ["hash", "hkdf", "rand"] } +secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash", "hkdf", "rand"], rev = "be621610d342416b9e3591cc4a61a05853673c13" } static_assertions = "1.1.0" +rand_core = { version = "0.6.4", default-features = false } +rand_chacha = { version = "0.3.1", default-features = false } schemars = "0.8.12" serde = { version = "1.0.158", default-features = false, features = ["derive"] } serde-big-array = "0.5.1" base64 = "0.21.0" constant_time_eq = "0.3.0" -primitive-types = { version = "0.12.2", default-features = false } -minicbor-ser = "0.2.0" +primitive-types = { version = "0.13.1", default-features = false } +minicbor = "0.25.1" +hex = "0.4.3" [dev-dependencies] cosmwasm-schema = { version = "1.1.8" } From 495fddc306808f67d918f6f85abd2055aae2099c Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 19 Dec 2024 11:34:13 +1300 Subject: [PATCH 68/87] dev: integration of updates/fixes from migration contract --- Cargo.lock | 16 +- Cargo.toml | 4 +- src/btbe.rs | 220 +++++--- src/constants.rs | 18 + src/contract.rs | 1245 +++++++++++++++++++++++++----------------- src/dwb.rs | 34 +- src/lib.rs | 1 + src/msg.rs | 75 ++- src/notifications.rs | 618 +++++++++++---------- src/state.rs | 10 +- src/strings.rs | 2 + 11 files changed, 1340 insertions(+), 903 deletions(-) create mode 100644 src/constants.rs diff --git a/Cargo.lock b/Cargo.lock index 540923ca..0cdbaa77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -797,7 +797,7 @@ dependencies = [ [[package]] name = "secret-toolkit" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" dependencies = [ "secret-toolkit-crypto", "secret-toolkit-notification", @@ -811,7 +811,7 @@ dependencies = [ [[package]] name = "secret-toolkit-crypto" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" dependencies = [ "cc", "hkdf", @@ -825,7 +825,7 @@ dependencies = [ [[package]] name = "secret-toolkit-notification" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" dependencies = [ "chacha20poly1305", "generic-array", @@ -844,7 +844,7 @@ dependencies = [ [[package]] name = "secret-toolkit-permit" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" dependencies = [ "bech32", "remain", @@ -860,7 +860,7 @@ dependencies = [ [[package]] name = "secret-toolkit-serialization" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" dependencies = [ "bincode2", "schemars", @@ -871,7 +871,7 @@ dependencies = [ [[package]] name = "secret-toolkit-storage" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" dependencies = [ "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -882,7 +882,7 @@ dependencies = [ [[package]] name = "secret-toolkit-utils" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" dependencies = [ "chrono", "schemars", @@ -894,7 +894,7 @@ dependencies = [ [[package]] name = "secret-toolkit-viewing-key" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=be621610d342416b9e3591cc4a61a05853673c13#be621610d342416b9e3591cc4a61a05853673c13" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" dependencies = [ "base64 0.21.7", "schemars", diff --git a/Cargo.toml b/Cargo.toml index 987a7fbe..d9395396 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,9 +38,9 @@ cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } rand = { version = "0.8.5", default-features = false } # secret-toolkit = { version = "0.10.2", default-features = false, features = ["permit", "storage", "viewing-key", "notification"] } -secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"], rev = "be621610d342416b9e3591cc4a61a05853673c13" } +secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"], rev = "4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" } # secret-toolkit-crypto = { version = "0.10.2", default-features = false, features = ["hash", "hkdf", "rand"] } -secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash", "hkdf", "rand"], rev = "be621610d342416b9e3591cc4a61a05853673c13" } +secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash", "hkdf", "rand"], rev = "4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" } static_assertions = "1.1.0" rand_core = { version = "0.6.4", default-features = false } diff --git a/src/btbe.rs b/src/btbe.rs index c213b44c..999fee20 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -4,7 +4,6 @@ include!(concat!(env!("OUT_DIR"), "/config.rs")); use constant_time_eq::constant_time_eq; use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage}; -use primitive_types::U256; use secret_toolkit::{ serialization::{Bincode2, Serde}, storage::Item, @@ -13,7 +12,7 @@ use secret_toolkit_crypto::hkdf_sha_256; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use crate::state::{safe_add_u64, INTERNAL_SECRET}; +use crate::{constants::{ADDRESS_BYTES_LEN, IMPOSSIBLE_ADDR}, dwb::constant_time_if_else_u32, state::{safe_add, safe_add_u64, INTERNAL_SECRET_SENSITIVE}}; use crate::dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}; #[cfg(feature = "gas_tracking")] use crate::gas_tracker::GasTracker; @@ -29,32 +28,18 @@ const BUCKETING_SALT_BYTES: &[u8; 14] = b"bucketing-salt"; const U32_BYTES: usize = 4; const U128_BYTES: usize = 16; -#[cfg(test)] -const BTBE_BUCKET_ADDRESS_BYTES: usize = 54; -#[cfg(not(test))] -const BTBE_BUCKET_ADDRESS_BYTES: usize = 20; -const BTBE_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u128) +const BTBE_BUCKET_ADDRESS_BYTES: usize = ADDRESS_BYTES_LEN; +const BTBE_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u64) const BTBE_BUCKET_HISTORY_BYTES: usize = 4; // Max 4 (u32) +const BTBE_BUCKET_CACHE_BYTES: usize = 0; const_assert!(BTBE_BUCKET_BALANCE_BYTES <= U128_BYTES); const_assert!(BTBE_BUCKET_HISTORY_BYTES <= U32_BYTES); const BTBE_BUCKET_ENTRY_BYTES: usize = - BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES; + BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES + + BTBE_BUCKET_CACHE_BYTES; -/// canonical address bytes corresponding to the 33-byte null public key, in hexadecimal -#[cfg(test)] -const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [ - 0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC, - 0xFF, 0xA2, 0xFE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -]; -#[cfg(not(test))] -const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [ - 0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC, - 0xFF, 0xA2, 0xFE, 0xEF, -]; /// A `StoredEntry` consists of the address, balance, and tx bundle history length in a byte array representation. /// The methods of the struct implementation also handle pushing and getting the tx bundle history in a simplified @@ -156,22 +141,59 @@ impl StoredEntry { Ok(()) } + pub fn save_hash_cache(&mut self, storage: &dyn Storage) -> StdResult<()> { + let hash_bytes = hkdf_sha_256( + &Some(BUCKETING_SALT_BYTES.to_vec()), + INTERNAL_SECRET_SENSITIVE.load(storage)?.as_slice(), + self.address_slice(), + 32, + )?; + + let start = BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES; + let end = start + BTBE_BUCKET_CACHE_BYTES; + self.0[start..end].copy_from_slice(&hash_bytes.as_slice()[0..BTBE_BUCKET_CACHE_BYTES]); + Ok(()) + } + + pub fn routes_to_right_node(&self, bit_pos: usize, secret: &[u8]) -> StdResult { + // target byte value + let byte; + + // bit pos is cached + if bit_pos < (BTBE_BUCKET_CACHE_BYTES << 3) { + // select the byte from cache corresponding to this bit position + byte = self.0[BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES + (bit_pos >> 3)]; + } + // not cached; calculate on the fly + else { + // create key bytes + let key_bytes = hkdf_sha_256( + &Some(BUCKETING_SALT_BYTES.to_vec()), + secret, + self.address_slice(), + 32, + )?; + + // select the byte containing the target bit + byte = key_bytes[bit_pos >> 3]; + } + + // extract value at bit position and turn into bool + return Ok(((byte >> (7 - (bit_pos % 8))) & 1) != 0); + + } + pub fn merge_dwb_entry( &mut self, storage: &mut dyn Storage, dwb_entry: &DelayedWriteBufferEntry, amount_spent: Option, ) -> StdResult<()> { - let history_len = self.history_len()?; - if history_len == 0 { - return Err(StdError::generic_err( - "use `from` to create new entry from dwb_entry", - )); - } - + // increase account's stored balance let mut balance = self.balance()?; safe_add_u64(&mut balance, dwb_entry.amount()?); + // safety check amount spent before spending from balance let amount_spent = amount_u64(amount_spent)?; // error should never happen because already checked in `settle_sender_or_owner_account` @@ -184,15 +206,48 @@ impl StoredEntry { ))); }; + // set new balance to stored entry self.set_balance(balance)?; - // peek at the last tx bundle added - let last_tx_bundle = self.get_tx_bundle_at(storage, history_len - 1)?; + // retrieve currenty history length + let history_len = self.history_len()?; + + // flag if history is empty + let empty_history = (history_len == 0) as u32; + + // position of last tx bundle to read + let bundle_pos = constant_time_if_else_u32( + empty_history, + 0u32, + history_len.wrapping_sub(1) // constant-time subtraction with underflow + ); + + // peek at the last tx bundle added (read the dummy one if its void) + let last_tx_bundle_result = self.get_tx_bundle_at_unchecked(storage, bundle_pos); + if last_tx_bundle_result.is_err() { + return Err(StdError::generic_err(format!( + "missing tx bundle while merging dwb entry!", + ))); + } + + // unwrap + let last_tx_bundle = last_tx_bundle_result?; + + // calculate the appropriate bundle offset to use + let bundle_offset = constant_time_if_else_u32( + empty_history, + 0u32, + last_tx_bundle.offset + (last_tx_bundle.list_len as u32) + ); + + // create new tx bundle let tx_bundle = TxBundle { head_node: dwb_entry.head_node()?, list_len: dwb_entry.list_len()?, - offset: last_tx_bundle.offset + u32::from(last_tx_bundle.list_len), + offset: bundle_offset, }; + + // add to list self.push_tx_bundle(storage, &tx_bundle)?; Ok(()) @@ -249,7 +304,10 @@ impl StoredEntry { fn push_tx_bundle(&mut self, storage: &mut dyn Storage, bundle: &TxBundle) -> StdResult<()> { let len = self.history_len()?; self.set_tx_bundle_at_unchecked(storage, len, bundle)?; - self.set_history_len(len.saturating_add(1))?; + // if the head node is null, then add this as a ghost bundle that does not contribute to len of list, + // and will be overwritten next time + let len_add = constant_time_if_else_u32((bundle.head_node == 0) as u32, 0, 1); + self.set_history_len(len.saturating_add(len_add))?; Ok(()) } } @@ -378,30 +436,13 @@ impl BitwiseTrieNode { } } -/// Determines whether a given entry belongs in the left node (true) or right node (false) -fn entry_belongs_in_left_node(secret: &[u8], entry: StoredEntry, bit_pos: u8) -> StdResult { - // create key bytes - let key_bytes = hkdf_sha_256( - &Some(BUCKETING_SALT_BYTES.to_vec()), - secret, - entry.address_slice(), - 32, - )?; - - // convert to u258 - let key_u256 = U256::from_big_endian(&key_bytes); - - // extract the bit value at the target bit position - return Ok(U256::from(0) == (key_u256 >> (255 - bit_pos)) & U256::from(1)); -} - /// Locates a btbe node given an address; returns tuple of (node, node_id, bit position) pub fn locate_btbe_node( storage: &dyn Storage, address: &CanonicalAddr, -) -> StdResult<(BitwiseTrieNode, u64, u8)> { +) -> StdResult<(BitwiseTrieNode, u64, usize)> { // load internal contract secret - let secret = INTERNAL_SECRET.load(storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(storage)?; let secret = secret.as_slice(); // create key bytes @@ -417,7 +458,9 @@ pub fn locate_btbe_node( let mut node = BTBE_TRIE_NODES .add_suffix(&node_id.to_be_bytes()) .load(storage)?; - let mut bit_pos: u8 = 0; + + // bit position + let mut bit_pos: usize = 0; // while the node has children while node.bucket == 0 { @@ -510,9 +553,9 @@ pub fn stored_tx_count(storage: &dyn Storage, entry: &Option) -> St Ok(0) } -// merges a dwb entry into the current node's bucket +// settles a dwb entry into its appropriate bucket // `amount_spent` is any required subtraction due to being sender of tx -pub fn merge_dwb_entry( +pub fn settle_dwb_entry( storage: &mut dyn Storage, dwb_entry: &DelayedWriteBufferEntry, amount_spent: Option, @@ -521,8 +564,11 @@ pub fn merge_dwb_entry( #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("#merge_dwb_entry.1"); + // ref the entry's recipient address + let address = &dwb_entry.recipient()?; + // locate the node that the given entry belongs in - let (mut node, mut node_id, mut bit_pos) = locate_btbe_node(storage, &dwb_entry.recipient()?)?; + let (mut node, mut node_id, mut bit_pos) = locate_btbe_node(storage, address)?; // load that node's current bucket let mut bucket = node.bucket(storage)?; @@ -531,7 +577,7 @@ pub fn merge_dwb_entry( let mut bucket_id = node.bucket; // search for an existing entry - if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(&dwb_entry.recipient()?) + if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(address) { // found existing entry // merge amount and history from dwb entry @@ -541,7 +587,7 @@ pub fn merge_dwb_entry( #[cfg(feature = "gas_tracking")] group1.logf(format!( "merged {} into node #{}, bucket #{} at position {} ", - dwb_entry.recipient()?, + address, node_id, bucket_id, idx @@ -549,13 +595,18 @@ pub fn merge_dwb_entry( // save updated bucket to storage node.set_and_save_bucket(storage, bucket)?; - } else { + } + // nothing was stored yet + else { // need to insert new entry // create new stored balance entry - let btbe_entry = StoredEntry::from(storage, &dwb_entry, amount_spent)?; + let mut btbe_entry = StoredEntry::from(storage, &dwb_entry, amount_spent)?; + + // cache the address + btbe_entry.save_hash_cache(storage)?; // load contract's internal secret - let secret = INTERNAL_SECRET.load(storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(storage)?; let secret = secret.as_slice(); loop { @@ -584,12 +635,11 @@ pub fn merge_dwb_entry( // each entry for entry in bucket.entries { - // left_bucket.add_entry(&entry); // route entry - if entry_belongs_in_left_node(secret, entry, bit_pos)? { - left_bucket.add_entry(&entry); - } else { + if entry.routes_to_right_node(bit_pos, secret)? { right_bucket.add_entry(&entry); + } else { + left_bucket.add_entry(&entry); } } @@ -663,16 +713,16 @@ pub fn merge_dwb_entry( )); // route entry - if entry_belongs_in_left_node(secret, btbe_entry, bit_pos)? { - node = left; - node_id = left_id; - bucket = left_bucket; - bucket_id = left_bucket_id; - } else { + if btbe_entry.routes_to_right_node(bit_pos, secret)? { node = right; node_id = right_id; bucket = right_bucket; bucket_id = right_bucket_id; + } else { + node = left; + node_id = left_id; + bucket = left_bucket; + bucket_id = left_bucket_id; } // increment bit position for next iteration of the loop @@ -760,6 +810,7 @@ mod tests { address: "bob".to_string(), amount: Uint128::new(5000), }]); + assert!( init_result.is_ok(), "Init failed: {}", @@ -767,11 +818,13 @@ mod tests { ); let _env = mock_env(); let _info = mock_info("bob", &[]); + let storage = &mut deps.storage; let canonical = deps .api .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str()) .unwrap(); + let entry = StoredEntry::new(&canonical).unwrap(); assert_eq!(entry.address().unwrap(), canonical); assert_eq!(entry.balance().unwrap(), 0_u64); @@ -799,12 +852,13 @@ mod tests { "Init failed: {}", init_result.err().unwrap() ); - let _env = mock_env(); + let env = mock_env(); let _info = mock_info("bob", &[]); + let storage = &mut deps.storage; - let _ = initialize_btbe(&mut deps.storage).unwrap(); + let _ = initialize_btbe(storage).unwrap(); - let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap(); + let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(storage).unwrap(); assert_eq!(btbe_node_count, 1); for i in 1..=128 { @@ -812,18 +866,21 @@ mod tests { .api .addr_canonicalize(Addr::unchecked(format!("{i}zzzzzz")).as_str()) .unwrap(); - let entry = StoredEntry::new(&canonical).unwrap(); + + let mut entry = StoredEntry::new(&canonical).unwrap(); + entry.save_hash_cache(storage).unwrap(); + assert_eq!(entry.address().unwrap(), canonical); assert_eq!(entry.balance().unwrap(), 0_u64); - let dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap(); + let mut dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap(); - let _result = merge_dwb_entry(&mut deps.storage, &dwb_entry, None); + let _result = settle_dwb_entry(storage, &mut dwb_entry, None); - let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap(); + let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(storage).unwrap(); assert_eq!(btbe_node_count, 1); - let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &canonical).unwrap(); + let (node, node_id, bit_pos) = locate_btbe_node(storage, &canonical).unwrap(); assert_eq!( node, BitwiseTrieNode { @@ -841,13 +898,14 @@ mod tests { .api .addr_canonicalize(Addr::unchecked(format!("bob")).as_str()) .unwrap(); - let entry = StoredEntry::new(&canonical).unwrap(); + let mut entry = StoredEntry::new(&canonical).unwrap(); + entry.save_hash_cache(storage); assert_eq!(entry.address().unwrap(), canonical); assert_eq!(entry.balance().unwrap(), 0_u64); - let dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap(); + let mut dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap(); - let _result = merge_dwb_entry(&mut deps.storage, &dwb_entry, None); + let _result = settle_dwb_entry(&mut deps.storage, &mut dwb_entry, None); let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap(); assert_eq!(btbe_node_count, 3); diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 00000000..0f1bde69 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,18 @@ +#[cfg(test)] +pub const ADDRESS_BYTES_LEN: usize = 54; +#[cfg(not(test))] +pub const ADDRESS_BYTES_LEN: usize = 20; + +/// canonical address bytes corresponding to the 33-byte null public key, in hexadecimal +#[cfg(test)] +pub const IMPOSSIBLE_ADDR: [u8; ADDRESS_BYTES_LEN] = [ + 0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC, + 0xFF, 0xA2, 0xFE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; +#[cfg(not(test))] +pub const IMPOSSIBLE_ADDR: [u8; ADDRESS_BYTES_LEN] = [ + 0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC, + 0xFF, 0xA2, 0xFE, 0xEF, +]; \ No newline at end of file diff --git a/src/contract.rs b/src/contract.rs index 29773f15..ac7f80be 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -6,8 +6,10 @@ use cosmwasm_std::{ }; #[cfg(feature = "gas_evaporation")] use cosmwasm_std::Api; -use secret_toolkit::notification::{get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, FlatDescriptor, Notification, NotificationData, StructDescriptor,}; -use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions}; +use rand_chacha::ChaChaRng; +use rand_core::{RngCore, SeedableRng}; +use secret_toolkit::notification::{get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, FlatDescriptor, GroupChannel, Notification, DirectChannel, StructDescriptor}; +use secret_toolkit::permit::{AllRevokedInterval, Permit, RevokedPermits, RevokedPermitsStore, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{hkdf_sha_256, sha_256, ContractPrng}; @@ -19,7 +21,7 @@ use crate::dwb::log_dwb; use crate::dwb::{DelayedWriteBuffer, DWB, TX_NODES}; use crate::btbe::{ - find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count, + find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count }; #[cfg(feature = "gas_tracking")] use crate::gas_tracker::{GasTracker, LoggingExt}; @@ -29,21 +31,21 @@ use crate::msg::{ AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, }; -use crate::notifications::{multi_received_data, multi_spent_data, AllowanceNotificationData, ReceivedNotificationData, SpentNotificationData, MULTI_RECEIVED_CHANNEL_BLOOM_K, MULTI_RECEIVED_CHANNEL_BLOOM_N, MULTI_RECEIVED_CHANNEL_ID, MULTI_RECEIVED_CHANNEL_PACKET_SIZE, MULTI_SPENT_CHANNEL_BLOOM_K, MULTI_SPENT_CHANNEL_BLOOM_N, MULTI_SPENT_CHANNEL_ID, MULTI_SPENT_CHANNEL_PACKET_SIZE}; +use crate::notifications::{ + render_group_notification, AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification +}; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, TOTAL_SUPPLY + safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY }; -use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; +use crate::strings::{SEND_TO_SSCRT_CONTRACT_MSG, TRANSFER_HISTORY_UNSUPPORTED_MSG}; use crate::transaction_history::{ - store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, - store_transfer_action, Tx, + store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx }; /// We make sure that responses from `handle` are padded to a multiple of this size. pub const RESPONSE_BLOCK_SIZE: usize = 256; -pub const NOTIFICATION_BLOCK_SIZE: usize = 36; -pub const PREFIX_REVOKED_PERMITS: &str = "revoked_permits"; +pub const NOTIFICATION_BLOCK_SIZE: usize = 1; #[entry_point] pub fn instantiate( @@ -95,23 +97,31 @@ pub fn instantiate( rng_entropy.extend_from_slice(info.sender.as_bytes()); rng_entropy.extend_from_slice(entropy); - // Create INTERNAL_SECRET + // create internal secrets let salt = Some(sha_256(&rng_entropy).to_vec()); - let internal_secret = hkdf_sha_256( + let internal_secret_sensitive = hkdf_sha_256( &salt, rng_seed.0.as_slice(), - "contract_internal_secret".as_bytes(), + "contract_internal_secret_sensitive".as_bytes(), 32, )?; - INTERNAL_SECRET.save(deps.storage, &internal_secret)?; + INTERNAL_SECRET_SENSITIVE.save(deps.storage, &internal_secret_sensitive)?; + + let internal_secret_relaxed = hkdf_sha_256( + &salt, + rng_seed.0.as_slice(), + "contract_internal_secret_relaxed".as_bytes(), + 32, + )?; + INTERNAL_SECRET_RELAXED.save(deps.storage, &internal_secret_relaxed)?; // Hard-coded channels let channels: Vec = vec![ - ReceivedNotificationData::CHANNEL_ID.to_string(), - SpentNotificationData::CHANNEL_ID.to_string(), - AllowanceNotificationData::CHANNEL_ID.to_string(), - MULTI_RECEIVED_CHANNEL_ID.to_string(), - MULTI_SPENT_CHANNEL_ID.to_string(), + RecvdNotification::CHANNEL_ID.to_string(), + SpentNotification::CHANNEL_ID.to_string(), + AllowanceNotification::CHANNEL_ID.to_string(), + MultiRecvdNotification::CHANNEL_ID.to_string(), + MultiSpentNotification::CHANNEL_ID.to_string(), ]; for channel in channels { @@ -326,11 +336,23 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::AddMinters { minters, .. } => add_minters(deps, info, minters), ExecuteMsg::RemoveMinters { minters, .. } => remove_minters(deps, info, minters), ExecuteMsg::SetMinters { minters, .. } => set_minters(deps, info, minters), + + // SNIP-24 ExecuteMsg::RevokePermit { permit_name, .. } => revoke_permit(deps, info, permit_name), + + // SNIP-24.1 + ExecuteMsg::RevokeAllPermits { interval, .. } => revoke_all_permits(deps, info, interval), + ExecuteMsg::DeletePermitRevocation { revocation_id, .. } => delete_permit_revocation(deps, info, revocation_id), + ExecuteMsg::AddSupportedDenoms { denoms, .. } => add_supported_denoms(deps, info, denoms), ExecuteMsg::RemoveSupportedDenoms { denoms, .. } => { remove_supported_denoms(deps, info, denoms) - } + }, + + // SNIP-52 + ExecuteMsg::SetNotificationStatus { enabled, .. } => { + set_notification_status(deps, info, enabled) + }, }; let padded_result = pad_handle_result(response, RESPONSE_BLOCK_SIZE); @@ -368,7 +390,7 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) let account = secret_toolkit::permit::validate( deps, - PREFIX_REVOKED_PERMITS, + &env, &permit, token_address.into_string(), None, @@ -466,7 +488,16 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) channels, txhash, deps.api.addr_canonicalize(account.as_str())?, - ) + ), + QueryWithPermit::ListPermitRevocations { .. } => { + if !permit.check_permission(&TokenPermissions::Owner) { + return Err(StdError::generic_err(format!( + "No permission to query list permit revocations, got permissions {:?}", + permit.params.permissions + ))); + } + query_list_permit_revocations(deps, account.as_str()) + }, } } @@ -512,6 +543,7 @@ pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult query_list_permit_revocations(deps, viewer.address.as_str()), _ => panic!("This query type does not require authentication"), }; } @@ -613,6 +645,8 @@ pub fn query_transactions( if dwb_index > 0 && txs_in_dwb_count > 0 && start < txs_in_dwb_count as u32 { // skip if start is after buffer entries let head_node_index = dwb.entries[dwb_index].head_node()?; + + // only look if head node is not null if head_node_index > 0 { let head_node = TX_NODES .add_suffix(&head_node_index.to_be_bytes()) @@ -648,18 +682,23 @@ pub fn query_transactions( let mut bundle_idx = tx_bundles_idx_len - 1; loop { let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; - let head_node = TX_NODES - .add_suffix(&tx_bundle.head_node.to_be_bytes()) - .load(deps.storage)?; - let list_len = tx_bundle.list_len as u32; - if txs_left <= list_len { - txs.extend_from_slice( - &head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize], - ); - break; + + // only look if head node is not null + if tx_bundle.head_node > 0 { + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; + + let list_len = tx_bundle.list_len as u32; + if txs_left <= list_len { + txs.extend_from_slice( + &head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize], + ); + break; + } + txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs_left = txs_left.saturating_sub(list_len); } - txs.extend(head_node.to_vec(deps.storage, deps.api)?); - txs_left = txs_left.saturating_sub(list_len); if bundle_idx > 0 { bundle_idx -= 1; } else { @@ -683,20 +722,28 @@ pub fn query_transactions( find_start_bundle(deps.storage, &account_raw, settled_start)? { let mut txs_left = end - start; - - let head_node = TX_NODES - .add_suffix(&tx_bundle.head_node.to_be_bytes()) - .load(deps.storage)?; let list_len = tx_bundle.list_len as u32; if start_at + txs_left <= list_len { - // this first bundle has all the txs we need - txs = head_node.to_vec(deps.storage, deps.api)? - [start_at as usize..(start_at + txs_left) as usize] - .to_vec(); + // only look if head node is not null + if tx_bundle.head_node > 0 { + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; + // this first bundle has all the txs we need + txs = head_node.to_vec(deps.storage, deps.api)? + [start_at as usize..(start_at + txs_left) as usize] + .to_vec(); + } } else { - // get the rest of the txs in this bundle and then go back through history - txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); - txs_left = txs_left.saturating_sub(list_len - start_at); + // only look if head node is not null + if tx_bundle.head_node > 0 { + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; + // get the rest of the txs in this bundle and then go back through history + txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); + txs_left = txs_left.saturating_sub(list_len - start_at); + } if bundle_idx > 0 && txs_left > 0 { // get the next earlier bundle @@ -705,19 +752,22 @@ pub fn query_transactions( loop { let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; - let head_node = TX_NODES - .add_suffix(&tx_bundle.head_node.to_be_bytes()) - .load(deps.storage)?; - let list_len = tx_bundle.list_len as u32; - if txs_left <= list_len { - txs.extend_from_slice( - &head_node.to_vec(deps.storage, deps.api)? - [0..txs_left as usize], - ); - break; + // only look if head node is not null + if tx_bundle.head_node > 0 { + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; + let list_len = tx_bundle.list_len as u32; + if txs_left <= list_len { + txs.extend_from_slice( + &head_node.to_vec(deps.storage, deps.api)? + [0..txs_left as usize], + ); + break; + } + txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs_left = txs_left.saturating_sub(list_len); } - txs.extend(head_node.to_vec(deps.storage, deps.api)?); - txs_left = txs_left.saturating_sub(list_len); if bundle_idx > 0 { bundle_idx -= 1; } else { @@ -730,6 +780,29 @@ pub fn query_transactions( } } + // deterministically obfuscate ids so they are not serial to prevent metadata leak + let internal_secret = INTERNAL_SECRET_RELAXED.load(deps.storage)?; + let internal_secret_u64: u64 = u64::from_be_bytes(internal_secret[..8].try_into().unwrap()); + let txs = txs + .iter() + .map(|tx| { + // PRNG(PRNG(serial_id) ^ secret) + let mut rng = ChaChaRng::seed_from_u64(tx.id); + let serial_id_rand = rng.next_u64(); + let new_seed = serial_id_rand ^ internal_secret_u64; + let mut rng = ChaChaRng::seed_from_u64(new_seed); + let new_id = rng.next_u64() >> (64 - 53); + Tx { + id: new_id, + action: tx.action.clone(), + coins: tx.coins.clone(), + memo: tx.memo.clone(), + block_height: tx.block_height, + block_time: tx.block_time, + } + }) + .collect(); + let result = QueryAnswer::TransactionHistory { txs, total: Some(total as u64), @@ -793,7 +866,7 @@ fn query_channel_info( txhash: Option, sender_raw: CanonicalAddr, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let seed = get_seed(&sender_raw, secret)?; let mut channels_data = vec![]; @@ -805,7 +878,7 @@ fn query_channel_info( answer_id = None; } match channel.as_str() { - ReceivedNotificationData::CHANNEL_ID => { + RecvdNotification::CHANNEL_ID => { let channel_info_data = ChannelInfoData { mode: "txhash".to_string(), channel, @@ -814,11 +887,11 @@ fn query_channel_info( data: None, next_id: None, counter: None, - cddl: Some(ReceivedNotificationData::CDDL_SCHEMA.to_string()), + cddl: Some(RecvdNotification::CDDL_SCHEMA.to_string()), }; channels_data.push(channel_info_data); } - SpentNotificationData::CHANNEL_ID => { + SpentNotification::CHANNEL_ID => { let channel_info_data = ChannelInfoData { mode: "txhash".to_string(), channel, @@ -827,11 +900,11 @@ fn query_channel_info( data: None, next_id: None, counter: None, - cddl: Some(SpentNotificationData::CDDL_SCHEMA.to_string()), + cddl: Some(SpentNotification::CDDL_SCHEMA.to_string()), }; channels_data.push(channel_info_data); } - AllowanceNotificationData::CHANNEL_ID => { + AllowanceNotification::CHANNEL_ID => { let channel_info_data = ChannelInfoData { mode: "txhash".to_string(), channel, @@ -840,41 +913,40 @@ fn query_channel_info( data: None, next_id: None, counter: None, - cddl: Some(AllowanceNotificationData::CDDL_SCHEMA.to_string()), + cddl: Some(AllowanceNotification::CDDL_SCHEMA.to_string()), }; channels_data.push(channel_info_data); } - MULTI_RECEIVED_CHANNEL_ID => { + MultiRecvdNotification::CHANNEL_ID => { let channel_info_data = ChannelInfoData { mode: "bloom".to_string(), channel, answer_id, parameters: Some(BloomParameters { - m: 512, - k: MULTI_RECEIVED_CHANNEL_BLOOM_K, + m: MultiRecvdNotification::BLOOM_M, + k: MultiRecvdNotification::BLOOM_K, h: "sha256".to_string(), }), data: Some(Descriptor { - r#type: format!("packet[{}]", MULTI_RECEIVED_CHANNEL_BLOOM_N), + r#type: format!("packet[{}]", MultiRecvdNotification::BLOOM_N), version: "1".to_string(), - packet_size: MULTI_RECEIVED_CHANNEL_PACKET_SIZE, + packet_size: MultiRecvdNotification::PACKET_SIZE as u32, data: StructDescriptor { r#type: "struct".to_string(), label: "transfer".to_string(), members: vec![ FlatDescriptor { - r#type: "uint128".to_string(), - label: "amount".to_string(), + r#type: "uint64".to_string(), + label: "flagsAndAmount".to_string(), description: Some( - "The transfer amount in base denomination".to_string(), + "Bit field of [0]: non-empty memo; [2]: sender is owner; [2..]: uint62 transfer amount in base denomination".to_string(), ), }, FlatDescriptor { r#type: "bytes8".to_string(), - label: "spender".to_string(), + label: "ownerId".to_string(), description: Some( - "The last 8 bytes of the sender's canonical address" - .to_string(), + "The last 8 bytes of the owner's canonical address".to_string(), ), }, ], @@ -886,44 +958,43 @@ fn query_channel_info( }; channels_data.push(channel_info_data); } - MULTI_SPENT_CHANNEL_ID => { + MultiSpentNotification::CHANNEL_ID => { let channel_info_data = ChannelInfoData { mode: "bloom".to_string(), channel, answer_id, parameters: Some(BloomParameters { - m: 512, - k: MULTI_SPENT_CHANNEL_BLOOM_K, + m: MultiSpentNotification::BLOOM_M, + k: MultiSpentNotification::BLOOM_K, h: "sha256".to_string(), }), data: Some(Descriptor { - r#type: format!("packet[{}]", MULTI_SPENT_CHANNEL_BLOOM_N), + r#type: format!("packet[{}]", MultiSpentNotification::BLOOM_N), version: "1".to_string(), - packet_size: MULTI_SPENT_CHANNEL_PACKET_SIZE, + packet_size: MultiSpentNotification::PACKET_SIZE as u32, data: StructDescriptor { r#type: "struct".to_string(), label: "transfer".to_string(), members: vec![ FlatDescriptor { - r#type: "uint128".to_string(), - label: "amount".to_string(), + r#type: "uint64".to_string(), + label: "flagsAndAmount".to_string(), description: Some( - "The transfer amount in base denomination".to_string(), + "Bit field of [0]: non-empty memo; [1]: reserved; [2..] uint62 transfer amount in base denomination".to_string(), ), }, FlatDescriptor { - r#type: "uint128".to_string(), - label: "balance".to_string(), + r#type: "bytes8".to_string(), + label: "recipientId".to_string(), description: Some( - "Spender's new balance after the transfer".to_string(), + "The last 8 bytes of the recipient's canonical address".to_string(), ), }, FlatDescriptor { - r#type: "bytes8".to_string(), - label: "recipient".to_string(), + r#type: "uint64".to_string(), + label: "balance".to_string(), description: Some( - "The last 8 bytes of the recipient's canonical address" - .to_string(), + "Spender's new balance after the transfer".to_string(), ), }, ], @@ -944,9 +1015,6 @@ fn query_channel_info( } } - //Ok(Binary(vec![])) - //let schema = CHANNEL_SCHEMATA.get(deps.storage, &channel); - to_binary(&QueryAnswer::ChannelInfo { as_of_block: Uint64::from(env.block.height), channels: channels_data, @@ -1028,6 +1096,28 @@ fn remove_supported_denoms( ) } +// SNIP-52 functions + +fn set_notification_status( + deps: DepsMut, + info: MessageInfo, + enabled: bool, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + check_if_admin(&config.admin, &info.sender)?; + + NOTIFICATIONS_ENABLED.save(deps.storage, &enabled)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetNotificationStatus { + status: Success, + })?), + ) +} + +// end SNIP-52 functions + #[allow(clippy::too_many_arguments)] fn try_mint_impl( deps: &mut DepsMut, @@ -1070,7 +1160,7 @@ fn try_mint( amount: Uint128, memo: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let recipient = deps.api.addr_validate(recipient.as_str())?; @@ -1097,6 +1187,8 @@ fn try_mint( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency try_mint_impl( &mut deps, @@ -1111,21 +1203,26 @@ fn try_mint( &mut tracker, )?; - let received_notification = Notification::new( - recipient, - ReceivedNotificationData { - amount: minted_amount, - sender: None, - }, - ) - .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); - let resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?) - .add_attribute_plaintext( + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let received_notification = Notification::new( + recipient, + RecvdNotification { + amount: minted_amount, + sender: None, + memo_len, + sender_is_owner: true, + }, + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( received_notification.id_plaintext(), received_notification.data_plaintext(), ); + } #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); @@ -1141,7 +1238,7 @@ fn try_batch_mint( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let constants = CONFIG.load(deps.storage)?; @@ -1171,11 +1268,21 @@ fn try_batch_mint( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); + notifications.push(Notification::new ( + recipient.clone(), + RecvdNotification { + amount: actual_amount, + sender: None, + memo_len: action.memo.as_ref().map(|s| s.len()).unwrap_or_default(), + sender_is_owner: true, + }, + )); + try_mint_impl( &mut deps, rng, info.sender.clone(), - recipient.clone(), + recipient, Uint128::new(actual_amount), constants.symbol.clone(), action.memo, @@ -1183,37 +1290,25 @@ fn try_batch_mint( #[cfg(feature = "gas_tracking")] &mut tracker, )?; - notifications.push(Notification::new ( - recipient, - ReceivedNotificationData { - amount: actual_amount, - sender: None, - }, - )); } - let tx_hash = env - .transaction - .clone() - .ok_or(StdError::generic_err("no tx hash found"))? - .hash; - let received_data = multi_received_data( - deps.api, - notifications, - &tx_hash, - env.block.random.unwrap(), - secret, - )?; - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?) - .add_attribute_plaintext( - format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), - Binary::from(received_data).to_base64(), - ) - ) + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + resp = render_group_notification( + deps.api, + MultiRecvdNotification(notifications), + &env.transaction.unwrap().hash, + env.block.random.unwrap(), + secret, + resp, + )?; + } + + Ok(resp) } pub fn try_set_key(deps: DepsMut, info: MessageInfo, key: String) -> StdResult { @@ -1456,6 +1551,7 @@ fn try_redeem( tx_id, amount_raw, "redeem", + false, #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -1499,45 +1595,57 @@ fn try_redeem( fn try_transfer_impl( deps: &mut DepsMut, rng: &mut ContractPrng, - sender: &Addr, + owner: &Addr, recipient: &Addr, amount: Uint128, denom: String, memo: Option, block: &cosmwasm_std::BlockInfo, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<(Notification, Notification)> { - let raw_sender = deps.api.addr_canonicalize(sender.as_str())?; +) -> StdResult<(Notification, Notification)> { + // canonicalize owner and recipient addresses + let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; - let sender_balance = perform_transfer( + // memo length + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + // create the tokens received notification for recipient + let received_notification = Notification::new( + recipient.clone(), + RecvdNotification { + amount: amount.u128(), + sender: Some(owner.clone()), + memo_len, + sender_is_owner: true, + } + ); + + // perform the transfer from owner to recipient + let owner_balance = perform_transfer( deps.storage, rng, - &raw_sender, + &raw_owner, &raw_recipient, - &raw_sender, + &raw_owner, amount.u128(), denom, - memo, + memo.clone(), block, + false, #[cfg(feature = "gas_tracking")] tracker, )?; - let received_notification = Notification::new( - recipient.clone(), - ReceivedNotificationData { - amount: amount.u128(), - sender: Some(sender.clone()), - } - ); + // create the tokens spent notification for owner let spent_notification = Notification::new ( - sender.clone(), - SpentNotificationData { + owner.clone(), + SpentNotification { amount: amount.u128(), actions: 1, recipient: Some(recipient.clone()), - balance: sender_balance, + balance: owner_balance, + memo_len, } ); @@ -1554,17 +1662,26 @@ fn try_transfer( amount: Uint128, memo: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let recipient: Addr = deps.api.addr_validate(recipient.as_str())?; let symbol = CONFIG.load(deps.storage)?.symbol; + // make sure the sender is not accidentally sending tokens to the sscrt contract address + if recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + } + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); - let (received_notification, spent_notification) = try_transfer_impl( + // perform the transfer + let ( + received_notification, + spent_notification + ) = try_transfer_impl( &mut deps, rng, &info.sender, @@ -1580,23 +1697,27 @@ fn try_transfer( #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("try_transfer.rest"); - let received_notification = received_notification.to_txhash_notification( - deps.api, - &env, - secret, - Some(NOTIFICATION_BLOCK_SIZE), - )?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); - let spent_notification = spent_notification.to_txhash_notification( - deps.api, - &env, - secret, - Some(NOTIFICATION_BLOCK_SIZE) - )?; + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + // render the tokens received notification + let received_notification = received_notification.to_txhash_notification( + deps.api, + &env, + secret, + None, + )?; - let resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?) - .add_attribute_plaintext( + // render the tokens spent notification + let spent_notification = spent_notification.to_txhash_notification( + deps.api, + &env, + secret, + None, + )?; + + resp = resp.add_attribute_plaintext( received_notification.id_plaintext(), received_notification.data_plaintext(), ) @@ -1604,6 +1725,7 @@ fn try_transfer( spent_notification.id_plaintext(), spent_notification.data_plaintext(), ); + } #[cfg(feature = "gas_tracking")] group1.log("rest"); @@ -1629,18 +1751,31 @@ fn try_batch_transfer( ); } - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let symbol = CONFIG.load(deps.storage)?.symbol; + let mut total_memo_len = 0; + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); let mut notifications = vec![]; for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; - let (received_notification, spent_notification) = try_transfer_impl( + + // make sure the sender is not accidentally sending tokens to the sscrt contract address + if recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + } + + total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + let ( + received_notification, + spent_notification + ) = try_transfer_impl( &mut deps, rng, &info.sender, @@ -1652,51 +1787,52 @@ fn try_batch_transfer( #[cfg(feature = "gas_tracking")] &mut tracker, )?; + notifications.push((received_notification, spent_notification)); } - let tx_hash = env - .transaction - .clone() - .ok_or(StdError::generic_err("no tx hash found"))? - .hash; - let (received_notifications, spent_notifications): ( - Vec>, - Vec>, - ) = notifications.into_iter().unzip(); - let received_data = multi_received_data( - deps.api, + let ( received_notifications, - &tx_hash, - env.block.random.clone().unwrap(), - secret, - )?; - - let total_amount_spent = spent_notifications - .iter() - .fold(0u128, |acc, notification| acc.saturating_add(notification.data.amount)); + spent_notifications + ): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); - let spent_notification = Notification::new ( - info.sender, - SpentNotificationData { - amount: total_amount_spent, - actions: num_actions as u32, - recipient: spent_notifications[0].data.recipient.clone(), - balance: spent_notifications.last().unwrap().data.balance, - } - ) - .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + resp = render_group_notification( + deps.api, + MultiRecvdNotification(received_notifications), + &env.transaction.clone().unwrap().hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; - let resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?) - .add_attribute_plaintext( - format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), - Binary::from(received_data).to_base64(), + let total_amount_spent = spent_notifications + .iter() + .fold(0u128, |acc, notification| acc.saturating_add(notification.data.amount)); + + let spent_notification = Notification::new ( + info.sender, + SpentNotification { + amount: total_amount_spent, + actions: num_actions as u32, + recipient: spent_notifications[0].data.recipient.clone(), + balance: spent_notifications.last().unwrap().data.balance, + memo_len: total_memo_len, + } ) - .add_attribute_plaintext( + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( spent_notification.id_plaintext(), spent_notification.data_plaintext(), ); + } #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); @@ -1749,8 +1885,11 @@ fn try_send_impl( msg: Option, block: &cosmwasm_std::BlockInfo, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<(Notification, Notification)> { - let (received_notification, spent_notification) = try_transfer_impl( +) -> StdResult<(Notification, Notification)> { + let ( + received_notification, + spent_notification + ) = try_transfer_impl( deps, rng, &sender, @@ -1790,7 +1929,7 @@ fn try_send( memo: Option, msg: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let recipient = deps.api.addr_validate(recipient.as_str())?; @@ -1798,10 +1937,18 @@ fn try_send( let mut messages = vec![]; let symbol = CONFIG.load(deps.storage)?.symbol; + // make sure the sender is not accidentally sending tokens to the sscrt contract address + if recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + } + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); - let (received_notification, spent_notification) = try_send_impl( + let ( + received_notification, + spent_notification + ) = try_send_impl( &mut deps, rng, &mut messages, @@ -1817,19 +1964,15 @@ fn try_send( &mut tracker, )?; - let received_notification = received_notification.to_txhash_notification( - deps.api, - &env, - secret, - Some(NOTIFICATION_BLOCK_SIZE) - )?; - let spent_notification = - spent_notification.to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; - - let resp = Response::new() + let mut resp = Response::new() .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?) - .add_attribute_plaintext( + .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let received_notification = received_notification.to_txhash_notification(deps.api, &env, secret, None)?; + let spent_notification = spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( received_notification.id_plaintext(), received_notification.data_plaintext(), ) @@ -1837,6 +1980,7 @@ fn try_send( spent_notification.id_plaintext(), spent_notification.data_plaintext(), ); + } #[cfg(feature = "gas_tracking")] return Ok(resp.add_gas_tracker(tracker)); @@ -1859,7 +2003,7 @@ fn try_batch_send( ); } - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let mut messages = vec![]; @@ -1869,12 +2013,25 @@ fn try_batch_send( let symbol = CONFIG.load(deps.storage)?.symbol; + let mut total_memo_len = 0; + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; - let (received_notification, spent_notification) = try_send_impl( + + // make sure the sender is not accidentally sending tokens to the sscrt contract address + if recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + } + + total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + let ( + received_notification, + spent_notification + ) = try_send_impl( &mut deps, rng, &mut messages, @@ -1889,54 +2046,52 @@ fn try_batch_send( #[cfg(feature = "gas_tracking")] &mut tracker, )?; + notifications.push((received_notification, spent_notification)); } - let tx_hash = env - .transaction - .clone() - .ok_or(StdError::generic_err("no tx hash found"))? - .hash; - - let (received_notifications, spent_notifications): ( - Vec>, - Vec>, - ) = notifications.into_iter().unzip(); - let received_data = multi_received_data( - deps.api, - received_notifications, - &tx_hash, - env.block.random.clone().unwrap(), - secret, - )?; - - let total_amount_spent = spent_notifications - .iter() - .fold(0u128, |acc, notification| acc + notification.data.amount); - - let spent_notification = Notification::new ( - info.sender, - SpentNotificationData { - amount: total_amount_spent, - actions: num_actions as u32, - recipient: spent_notifications[0].data.recipient.clone(), - balance: spent_notifications.last().unwrap().data.balance, - } - ) - .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; - - Ok(Response::new() + let mut resp = Response::new() .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) - .add_attribute_plaintext( - format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), - Binary::from(received_data).to_base64(), + .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + + resp = render_group_notification( + deps.api, + MultiRecvdNotification(received_notifications), + &env.transaction.clone().unwrap().hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + + let total_amount_spent = spent_notifications + .iter() + .fold(0u128, |acc, notification| acc + notification.data.amount); + + let spent_notification = Notification::new ( + info.sender, + SpentNotification { + amount: total_amount_spent, + actions: num_actions as u32, + recipient: spent_notifications[0].data.recipient.clone(), + balance: spent_notifications.last().unwrap().data.balance, + memo_len: total_memo_len, + } ) - .add_attribute_plaintext( + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( spent_notification.id_plaintext(), spent_notification.data_plaintext(), - ) - ) + ); + } + + Ok(resp) } fn try_register_receive( @@ -1967,7 +2122,7 @@ fn use_allowance( ) -> StdResult<()> { let mut allowance = AllowancesStore::load(storage, owner, spender); - if allowance.is_expired_at(&env.block) { + if allowance.is_expired_at(&env.block) || allowance.amount == 0 { return Err(insufficient_allowance(0, amount)); } if let Some(new_allowance) = allowance.amount.checked_sub(amount) { @@ -1992,7 +2147,7 @@ fn try_transfer_from_impl( amount: Uint128, denom: String, memo: Option, -) -> StdResult<(Notification, Notification)> { +) -> StdResult<(Notification, Notification)> { let raw_amount = amount.u128(); let raw_spender = deps.api.addr_canonicalize(spender.as_str())?; let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; @@ -2000,9 +2155,28 @@ fn try_transfer_from_impl( use_allowance(deps.storage, env, owner, spender, raw_amount)?; + // make sure the sender is not accidentally sending tokens to the sscrt contract address + if *recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + } + #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + // create tokens received notification for recipient + let received_notification = Notification::new( + recipient.clone(), + RecvdNotification { + amount: amount.u128(), + sender: Some(owner.clone()), + memo_len, + sender_is_owner: spender == owner, + } + ); + + // perform the transfer from owner to recipient let owner_balance = perform_transfer( deps.storage, rng, @@ -2013,25 +2187,20 @@ fn try_transfer_from_impl( denom, memo, &env.block, + true, #[cfg(feature = "gas_tracking")] &mut tracker, )?; - let received_notification = Notification::new( - recipient.clone(), - ReceivedNotificationData { - amount: amount.u128(), - sender: Some(owner.clone()), - } - ); - + // create tokens spent notification for owner let spent_notification = Notification::new ( owner.clone(), - SpentNotificationData { + SpentNotification { amount: amount.u128(), actions: 1, recipient: Some(recipient.clone()), balance: owner_balance, + memo_len, } ); @@ -2049,13 +2218,16 @@ fn try_transfer_from( amount: Uint128, memo: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; let symbol = CONFIG.load(deps.storage)?.symbol; - let (received_notification, spent_notification) = try_transfer_from_impl( + let ( + received_notification, + spent_notification + ) = try_transfer_from_impl( &mut deps, rng, env, @@ -2066,32 +2238,36 @@ fn try_transfer_from( symbol, memo, )?; - let received_notification = received_notification.to_txhash_notification( - deps.api, - &env, - secret, - Some(NOTIFICATION_BLOCK_SIZE), - )?; - let spent_notification = spent_notification.to_txhash_notification( - deps.api, - &env, - secret, - Some(NOTIFICATION_BLOCK_SIZE) - )?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?); - Ok( - Response::new() - .set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?) - .add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ) - ) + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let received_notification = received_notification.to_txhash_notification( + deps.api, + &env, + secret, + None, + )?; + + let spent_notification = spent_notification.to_txhash_notification( + deps.api, + &env, + secret, + None + )?; + + resp = resp.add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + Ok(resp) } fn try_batch_transfer_from( @@ -2101,7 +2277,7 @@ fn try_batch_transfer_from( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let mut notifications = vec![]; @@ -2110,7 +2286,11 @@ fn try_batch_transfer_from( for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; - let (received_notification, spent_notification) = try_transfer_from_impl( + + let ( + received_notification, + spent_notification + ) = try_transfer_from_impl( &mut deps, rng, env, @@ -2121,46 +2301,41 @@ fn try_batch_transfer_from( symbol.clone(), action.memo, )?; + notifications.push((received_notification, spent_notification)); } - let tx_hash = env - .transaction - .clone() - .ok_or(StdError::generic_err("no tx hash found"))? - .hash; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchTransferFrom {status: Success})?); - let (received_notifications, spent_notifications): ( - Vec>, - Vec>, - ) = notifications.into_iter().unzip(); - let received_data = multi_received_data( - deps.api, - received_notifications, - &tx_hash, - env.block.random.clone().unwrap(), - secret, - )?; - let spent_data = multi_spent_data( - deps.api, - spent_notifications, - &tx_hash, - env.block.random.clone().unwrap(), - secret, - )?; + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); - Ok( - Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchTransferFrom {status: Success})?) - .add_attribute_plaintext( - format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), - Binary::from(received_data).to_base64(), - ) - .add_attribute_plaintext( - format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID), - Binary::from(spent_data).to_base64(), - ) - ) + let tx_hash = env.transaction.clone().unwrap().hash; + + resp = render_group_notification( + deps.api, + MultiRecvdNotification(received_notifications), + &tx_hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + + resp = render_group_notification( + deps.api, + MultiSpentNotification(spent_notifications), + &tx_hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + } + + Ok(resp) } #[allow(clippy::too_many_arguments)] @@ -2176,10 +2351,13 @@ fn try_send_from_impl( amount: Uint128, memo: Option, msg: Option, -) -> StdResult<(Notification, Notification)> { +) -> StdResult<(Notification, Notification)> { let spender = info.sender.clone(); let symbol = CONFIG.load(deps.storage)?.symbol; - let (received_notification, spent_notification) = try_transfer_from_impl( + let ( + received_notification, + spent_notification + ) = try_transfer_from_impl( deps, rng, &env, @@ -2219,13 +2397,16 @@ fn try_send_from( memo: Option, msg: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; let mut messages = vec![]; - let (received_notification, spent_notification) = try_send_from_impl( + let ( + received_notification, + spent_notification + ) = try_send_from_impl( &mut deps, env.clone(), info, @@ -2239,19 +2420,15 @@ fn try_send_from( msg, )?; - let received_notification = received_notification.to_txhash_notification( - deps.api, - &env, - secret, - Some(NOTIFICATION_BLOCK_SIZE), - )?; - let spent_notification = - spent_notification.to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; - - Ok(Response::new() + let mut resp = Response::new() .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?) - .add_attribute_plaintext( + .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let received_notification = received_notification.to_txhash_notification(deps.api, &env, secret, None,)?; + let spent_notification = spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( received_notification.id_plaintext(), received_notification.data_plaintext(), ) @@ -2259,7 +2436,9 @@ fn try_send_from( spent_notification.id_plaintext(), spent_notification.data_plaintext(), ) - ) + } + + Ok(resp) } fn try_batch_send_from( @@ -2269,7 +2448,7 @@ fn try_batch_send_from( rng: &mut ContractPrng, actions: Vec, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let mut messages = vec![]; @@ -2278,7 +2457,10 @@ fn try_batch_send_from( for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; - let (received_notification, spent_notification) = try_send_from_impl( + let ( + received_notification, + spent_notification + ) = try_send_from_impl( &mut deps, env.clone(), info, @@ -2294,45 +2476,40 @@ fn try_batch_send_from( notifications.push((received_notification, spent_notification)); } - let tx_hash = env - .transaction - .clone() - .ok_or(StdError::generic_err("no tx hash found"))? - .hash; - - let (received_notifications, spent_notifications): ( - Vec>, - Vec>, - ) = notifications.into_iter().unzip(); - let received_data = multi_received_data( - deps.api, - received_notifications, - &tx_hash, - env.block.random.clone().unwrap(), - secret, - )?; - let spent_data = multi_spent_data( - deps.api, - spent_notifications, - &tx_hash, - env.block.random.clone().unwrap(), - secret, - )?; - - Ok(Response::new() + let mut resp = Response::new() .add_messages(messages) .set_data(to_binary(&ExecuteAnswer::BatchSendFrom { status: Success, - })?) - .add_attribute_plaintext( - format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID), - Binary::from(received_data).to_base64(), - ) - .add_attribute_plaintext( - format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID), - Binary::from(spent_data).to_base64(), - ) - ) + })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + + let tx_hash = env.transaction.clone().unwrap().hash; + + resp = render_group_notification( + deps.api, + MultiRecvdNotification(received_notifications), + &tx_hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + + resp = render_group_notification( + deps.api, + MultiSpentNotification(spent_notifications), + &tx_hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + } + + Ok(resp) } #[allow(clippy::too_many_arguments)] @@ -2344,7 +2521,7 @@ fn try_burn_from( amount: Uint128, memo: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let owner = deps.api.addr_validate(owner.as_str())?; @@ -2360,6 +2537,9 @@ fn try_burn_from( use_allowance(deps.storage, env, &owner, &info.sender, raw_amount)?; let raw_burner = deps.api.addr_canonicalize(info.sender.as_str())?; + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + // store the event let tx_id = store_burn_action( deps.storage, raw_owner.clone(), @@ -2383,9 +2563,12 @@ fn try_burn_from( tx_id, raw_amount, "burn", + raw_burner == raw_owner, #[cfg(feature = "gas_tracking")] &mut tracker, )?; + + // sender and owner are different if raw_burner != raw_owner { // also settle sender's account dwb.settle_sender_or_owner_account( @@ -2394,6 +2577,7 @@ fn try_burn_from( tx_id, 0, "burn", + false, #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -2411,27 +2595,32 @@ fn try_burn_from( "You're trying to burn more than is available in the total supply", )); } + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - let spent_notification = Notification::new ( - owner, - SpentNotificationData { - amount: raw_amount, - actions: 1, - recipient: None, - balance: owner_balance, - } - ) - .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?); - Ok( - Response::new() - .set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ) - ) + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let spent_notification = Notification::new ( + owner, + SpentNotification { + amount: raw_amount, + actions: 1, + recipient: None, + balance: owner_balance, + memo_len, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + Ok(resp) } fn try_batch_burn_from( @@ -2440,7 +2629,7 @@ fn try_batch_burn_from( info: MessageInfo, actions: Vec, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let constants = CONFIG.load(deps.storage)?; @@ -2483,16 +2672,21 @@ fn try_batch_burn_from( tx_id, amount, "burn", + raw_spender == raw_owner, #[cfg(feature = "gas_tracking")] &mut tracker, )?; + + // sender and owner are different if raw_spender != raw_owner { + // also settle the sender's account dwb.settle_sender_or_owner_account( deps.storage, &raw_spender, tx_id, 0, "burn", + false, #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -2511,38 +2705,33 @@ fn try_batch_burn_from( spent_notifications.push(Notification::new ( info.sender.clone(), - SpentNotificationData { + SpentNotification { amount, actions: 1, recipient: None, balance: owner_balance, + memo_len: action.memo.as_ref().map(|s| s.len()).unwrap_or_default() } )); } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - let tx_hash = env - .transaction - .clone() - .ok_or(StdError::generic_err("no tx hash found"))? - .hash; - let spent_data = multi_spent_data( - deps.api, - spent_notifications, - &tx_hash, - env.block.random.clone().unwrap(), - secret, - )?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchBurnFrom {status: Success,})?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + resp = render_group_notification( + deps.api, + MultiSpentNotification(spent_notifications), + &env.transaction.clone().unwrap().hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + } - Ok( - Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchBurnFrom {status: Success,})?) - .add_attribute_plaintext( - format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID), - Binary::from(spent_data).to_base64(), - ) - ) + Ok(resp) } fn try_increase_allowance( @@ -2553,7 +2742,7 @@ fn try_increase_allowance( amount: Uint128, expiration: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let spender = deps.api.addr_validate(spender.as_str())?; @@ -2575,28 +2764,31 @@ fn try_increase_allowance( let new_amount = allowance.amount; AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; - let notification = Notification::new ( - spender.clone(), - AllowanceNotificationData { - amount: new_amount, - allower: info.sender.clone(), - expiration, - } - ) - .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { + owner: info.sender.clone(), + spender: spender.clone(), + allowance: Uint128::from(new_amount), + })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let notification = Notification::new ( + spender, + AllowanceNotification { + amount: new_amount, + allower: info.sender, + expiration, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; - Ok( - Response::new() - .set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { - owner: info.sender, - spender, - allowance: Uint128::from(new_amount), - })?) - .add_attribute_plaintext( - notification.id_plaintext(), - notification.data_plaintext() - ) - ) + resp = resp.add_attribute_plaintext( + notification.id_plaintext(), + notification.data_plaintext() + ); + } + + Ok(resp) } fn try_decrease_allowance( @@ -2607,7 +2799,7 @@ fn try_decrease_allowance( amount: Uint128, expiration: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let spender = deps.api.addr_validate(spender.as_str())?; @@ -2629,28 +2821,31 @@ fn try_decrease_allowance( let new_amount = allowance.amount; AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; - let notification = Notification::new ( - spender.clone(), - AllowanceNotificationData { - amount: new_amount, - allower: info.sender.clone(), - expiration, - } - ) - .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { + owner: info.sender.clone(), + spender: spender.clone(), + allowance: Uint128::from(new_amount), + })?); - Ok( - Response::new() - .set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { - owner: info.sender, - spender, - allowance: Uint128::from(new_amount), - })?) - .add_attribute_plaintext( - notification.id_plaintext(), - notification.data_plaintext() - ) - ) + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let notification = Notification::new ( + spender, + AllowanceNotification { + amount: new_amount, + allower: info.sender, + expiration, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + notification.id_plaintext(), + notification.data_plaintext() + ); + } + + Ok(resp) } fn add_minters( @@ -2738,7 +2933,7 @@ fn try_burn( amount: Uint128, memo: Option, ) -> StdResult { - let secret = INTERNAL_SECRET.load(deps.storage)?; + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); let constants = CONFIG.load(deps.storage)?; @@ -2751,6 +2946,8 @@ fn try_burn( let raw_amount = amount.u128(); let raw_burn_address = deps.api.addr_canonicalize(info.sender.as_str())?; + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + let tx_id = store_burn_action( deps.storage, raw_burn_address.clone(), @@ -2774,6 +2971,7 @@ fn try_burn( tx_id, raw_amount, "burn", + false, #[cfg(feature = "gas_tracking")] &mut tracker, )?; @@ -2790,25 +2988,29 @@ fn try_burn( } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - let spent_notification = Notification::new ( - info.sender, - SpentNotificationData { - amount: raw_amount, - actions: 1, - recipient: None, - balance: owner_balance, - } - ) - .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?; + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?); - Ok( - Response::new() - .set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ) - ) + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let spent_notification = Notification::new ( + info.sender, + SpentNotification { + amount: raw_amount, + actions: 1, + recipient: None, + balance: owner_balance, + memo_len, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + Ok(resp) } fn perform_transfer( @@ -2821,6 +3023,7 @@ fn perform_transfer( denom: String, memo: Option, block: &BlockInfo, + is_from_action: bool, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult { #[cfg(feature = "gas_tracking")] @@ -2847,18 +3050,21 @@ fn perform_transfer( tx_id, amount, transfer_str, + is_from_action && sender == from, #[cfg(feature = "gas_tracking")] tracker, )?; - // if this is a *_from action, settle the sender's account, too + // sender and owner are different if sender != from { + // settle the sender's account too dwb.settle_sender_or_owner_account( store, sender, tx_id, 0, transfer_str, + false, #[cfg(feature = "gas_tracking")] tracker, )?; @@ -2903,14 +3109,16 @@ fn perform_mint( // load delayed write buffer let mut dwb = DWB.load(store)?; - // if minter is not recipient, settle them + // sender and owner are different if minter != to { + // settle the sender's account too dwb.settle_sender_or_owner_account( store, minter, tx_id, 0, "mint", + false, #[cfg(feature = "gas_tracking")] tracker, )?; @@ -2966,7 +3174,6 @@ fn perform_deposit( fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdResult { RevokedPermits::revoke_permit( deps.storage, - PREFIX_REVOKED_PERMITS, info.sender.as_str(), &permit_name, ); @@ -2974,6 +3181,44 @@ fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdRe Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?)) } +// SNIP 24.1 + +fn revoke_all_permits(deps: DepsMut, info: MessageInfo, interval: AllRevokedInterval) -> StdResult { + let revocation_id = RevokedPermits::revoke_all_permits( + deps.storage, + info.sender.as_str(), + &interval, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokeAllPermits { + status: Success, + revocation_id: Some(revocation_id.to_string()), + })?)) +} + +fn delete_permit_revocation(deps: DepsMut, info: MessageInfo, revocation_id: String) -> StdResult { + RevokedPermits::delete_revocation( + deps.storage, + info.sender.as_str(), + revocation_id.as_str(), + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::DeletePermitRevocation { + status: Success, + })?)) +} + +fn query_list_permit_revocations(deps: Deps, account: &str) -> StdResult { + let revocations = RevokedPermits::list_revocations( + deps.storage, + account + )?; + + to_binary(&QueryAnswer::ListPermitRevocations { revocations }) +} + +// end SNIP 24.1 + fn check_if_admin(config_admin: &Addr, account: &Addr) -> StdResult<()> { if config_admin != account { return Err(StdError::generic_err( @@ -4263,6 +4508,8 @@ mod tests { permit_name: permit_name.to_string(), chain_id: chain_id.to_string(), permissions: vec![permit_type], + created: None, + expires: None, }, signature: PermitSignature { pub_key: PubKey { diff --git a/src/dwb.rs b/src/dwb.rs index 8b4c7adf..2a7dd255 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -6,7 +6,7 @@ use secret_toolkit_crypto::ContractPrng; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use crate::btbe::{merge_dwb_entry, stored_balance}; +use crate::btbe::{settle_dwb_entry, stored_balance}; use crate::state::{safe_add, safe_add_u64}; use crate::transaction_history::{Tx, TRANSACTIONS}; #[cfg(feature = "gas_tracking")] @@ -87,6 +87,7 @@ impl DelayedWriteBuffer { tx_id: u64, amount_spent: u128, op_name: &str, + is_from_self: bool, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, ) -> StdResult { #[cfg(feature = "gas_tracking")] @@ -98,6 +99,7 @@ impl DelayedWriteBuffer { #[cfg(feature = "gas_tracking")] group1.log("release_dwb_recipient"); + // check that the owner has sufficient funds to perform the transfer let checked_balance = balance.checked_sub(amount_spent); if checked_balance.is_none() { return Err(StdError::generic_err(format!( @@ -105,24 +107,29 @@ impl DelayedWriteBuffer { ))); }; + // record the event in the dwb entry dwb_entry.add_tx_node(store, tx_id)?; + // *_from action where sender is the owner, repeat the event in history + if is_from_self { + dwb_entry.add_tx_node(store, tx_id)?; + } + #[cfg(feature = "gas_tracking")] group1.log("add_tx_node"); - let mut entry = dwb_entry.clone(); - entry.set_recipient(address)?; + dwb_entry.set_recipient(address)?; #[cfg(feature = "gas_tracking")] group1.logf(format!( "@entry=address:{}, amount:{}", - entry.recipient()?, - entry.amount()? + dwb_entry.recipient()?, + dwb_entry.amount()? )); - merge_dwb_entry( + settle_dwb_entry( store, - &entry, + &mut dwb_entry, Some(amount_spent), #[cfg(feature = "gas_tracking")] tracker, @@ -266,10 +273,10 @@ impl DelayedWriteBuffer { group1.logf(format!("@write_index: {}", write_index)); // settle the entry - let dwb_entry = self.entries[actual_settle_index]; - merge_dwb_entry( + let mut dwb_entry = self.entries[actual_settle_index]; + settle_dwb_entry( store, - &dwb_entry, + &mut dwb_entry, None, #[cfg(feature = "gas_tracking")] tracker, @@ -516,10 +523,15 @@ fn constant_time_is_not_zero(value: i32) -> u32 { } #[inline] -fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { +pub fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize { (then * condition as usize) | (els * (1 - condition as usize)) } +#[inline] +pub fn constant_time_if_else_u32(condition: u32, then: u32, els: u32) -> u32 { + (then * condition) | (els * (1 - condition)) +} + #[cfg(feature = "gas_tracking")] pub fn log_dwb(storage: &dyn Storage) -> StdResult { let dwb = DWB.load(storage)?; diff --git a/src/lib.rs b/src/lib.rs index b9928515..9c7642bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ extern crate static_assertions as sa; mod batch; mod btbe; +mod constants; pub mod contract; mod dwb; mod gas_tracker; diff --git a/src/msg.rs b/src/msg.rs index 09d0be73..fc6e1f73 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -7,7 +7,7 @@ use crate::{batch, transaction_history::Tx}; use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint64,}; #[cfg(feature = "gas_evaporation")] use cosmwasm_std::Uint64; -use secret_toolkit::{notification::ChannelInfoData, permit::Permit}; +use secret_toolkit::{notification::ChannelInfoData, permit::{AllRevocation, AllRevokedInterval, Permit}}; #[cfg_attr(test, derive(Eq, PartialEq))] #[derive(Serialize, Deserialize, Clone, JsonSchema)] @@ -283,6 +283,12 @@ pub enum ExecuteMsg { #[cfg(feature = "gas_evaporation")] gas_target: Option, }, + /// Enable or disable SNIP-52 notifications + SetNotificationStatus { + enabled: bool, + #[cfg(feature = "gas_evaporation")] + gas_target: Option, + }, // Permit RevokePermit { @@ -291,6 +297,27 @@ pub enum ExecuteMsg { gas_target: Option, padding: Option, }, + + // SNIP 24.1 Blanket Permits + + /// Revokes all permits. Client can supply a datetime for created_after, created_before, both, or neither. + /// * created_before – makes it so any permits using a created value less than this datetime will be rejected + /// * created_after – makes it so any permits using a created value greater than this datetime will be rejected + /// * both created_before and created_after – makes it so any permits using a created value between these two datetimes will be rejected + /// * neither – makes it so ANY permit will be rejected. + /// in this case, the contract MUST return a revocation ID of "REVOKED_ALL". this action is idempotent + RevokeAllPermits { + interval: AllRevokedInterval, + #[cfg(feature = "gas_evaporation")] + gas_target: Option, + }, + + /// Deletes a previously issued permit revocation. + DeletePermitRevocation { + revocation_id: String, + #[cfg(feature = "gas_evaporation")] + gas_target: Option, + }, } #[derive(Serialize, Deserialize, JsonSchema, Debug)] @@ -390,11 +417,25 @@ pub enum ExecuteAnswer { RemoveSupportedDenoms { status: ResponseStatus, }, + SetNotificationStatus { + status: ResponseStatus, + }, // Permit RevokePermit { status: ResponseStatus, }, + + // SNIP 24.1 - Blanket Permits + RevokeAllPermits { + status: ResponseStatus, + revocation_id: Option, + }, + + DeletePermitRevocation { + status: ResponseStatus, + }, + } #[cfg(feature = "gas_evaporation")] @@ -433,12 +474,15 @@ impl Evaporator for ExecuteMsg { | ExecuteMsg::SetContractStatus { gas_target, .. } | ExecuteMsg::AddSupportedDenoms { gas_target, .. } | ExecuteMsg::RemoveSupportedDenoms { gas_target, .. } - | ExecuteMsg::RevokePermit { gas_target, .. } => match gas_target { + | ExecuteMsg::SetNotificationStatus { gas_targe, .. } + | ExecuteMsg::RevokePermit { gas_target, .. } + | ExecuteMsg::RevokeAllPermits { gas_target, .. } + | ExecuteMsg::DeletePermitRevocation { gas_target, .. } => match gas_target { Some(gas_target) => { let gas_used = api.check_gas()?; if gas_used < gas_target.u64() { let evaporate_amount = gas_target.u64() - gas_used; - // api.gas_evaporate(evaporate_amount as u32)?; + api.gas_evaporate(evaporate_amount as u32)?; return Ok(evaporate_amount) } Ok(0) @@ -503,6 +547,15 @@ pub enum QueryMsg { viewer: ViewerInfo, }, + // SNIP 24.1 + ListPermitRevocations { + // `page` and `page_size` do nothing here because max revocations is only 10 but included + // to satisfy the SNIP24.1 spec + page: Option, + page_size: Option, + viewer: ViewerInfo, + }, + WithPermit { permit: Permit, query: QueryWithPermit, @@ -560,6 +613,10 @@ impl QueryMsg { let address = api.addr_validate(viewer.address.as_str())?; Ok((vec![address], viewer.viewing_key.clone())) } + Self::ListPermitRevocations { viewer, .. } => { + let address = api.addr_validate(viewer.address.as_str())?; + Ok((vec![address], viewer.viewing_key.clone())) + } _ => panic!("This query type does not require authentication"), } } @@ -597,6 +654,13 @@ pub enum QueryWithPermit { channels: Vec, txhash: Option, }, + // SNIP 24.1 + ListPermitRevocations { + // `page` and `page_size` do nothing here because max revocations is only 10 but included + // to satisfy the SNIP24.1 spec + page: Option, + page_size: Option, + }, } #[derive(Serialize, Deserialize, JsonSchema, Debug)] @@ -665,6 +729,11 @@ pub enum QueryAnswer { channels: Vec, }, + // SNIP-24.1 + ListPermitRevocations { + revocations: Vec, + }, + #[cfg(feature = "gas_tracking")] Dwb { dwb: String, diff --git a/src/notifications.rs b/src/notifications.rs index 38e76f74..2ceafa59 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -1,402 +1,426 @@ use std::collections::HashMap; -use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, StdError, StdResult}; +use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, Response, StdResult}; use primitive_types::{U256, U512}; -use secret_toolkit::notification::{get_seed, notification_id, xor_bytes, Notification, NotificationData}; -use minicbor_ser as cbor; +use secret_toolkit::notification::{get_seed, notification_id, xor_bytes, EncoderExt, CBL_ADDRESS, CBL_ARRAY_SHORT, CBL_BIGNUM_U64, CBL_TIMESTAMP, CBL_U8, Notification, DirectChannel, GroupChannel}; +use minicbor::Encoder; use secret_toolkit_crypto::{hkdf_sha_512, sha_256}; use serde::{Deserialize, Serialize}; + const ZERO_ADDR: [u8; 20] = [0u8; 20]; -// recvd = [ -// amount: biguint, ; transfer amount in base denomination -// sender: bstr, ; byte sequence of sender's canonical address -// balance: biguint ; recipient's new balance after the transfer -// ] +// maximum value that can be stored in 62 bits +const U62_MAX: u128 = (1 << 62) - 1; + +// maximum value that can be stored in 63 bits +const U63_MAX: u128 = (1 << 63) - 1; + #[derive(Serialize, Debug, Deserialize, Clone)] #[cfg_attr(test, derive(Eq, PartialEq))] -pub struct ReceivedNotificationData { +pub struct RecvdNotification { pub amount: u128, pub sender: Option, + pub memo_len: usize, + pub sender_is_owner: bool, } -impl NotificationData for ReceivedNotificationData { - const CHANNEL_ID: &'static str = "recvd"; - const CDDL_SCHEMA: &'static str = "recvd=[amount:biguint,sender:bstr]"; - - fn to_cbor(&self, api: &dyn Api) -> StdResult> { - let received_data; +/// ```cddl +/// recvd = [ +/// amount: biguint .size 8, ; transfer amount in base denomination +/// sender: bstr .size 20, ; number of actions the execution performed +/// memo_len: uint .size 1, ; byte sequence of first recipient's canonical address +/// ] +/// ``` +impl DirectChannel for RecvdNotification { + const CHANNEL_ID: &'static str = "recvd"; + const CDDL_SCHEMA: &'static str = "recvd=[amount:biguint .size 8,sender:bstr .size 20,memo_len:uint .size 1]"; + const ELEMENTS: u64 = 3; + const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_ADDRESS + CBL_U8; + + fn encode_cbor(&self, api: &dyn Api, encoder: &mut Encoder<&mut [u8]>) -> StdResult<()> { + // amount:biguint (8-byte uint) + encoder.ext_u64_from_u128(self.amount)?; + + // sender:bstr (20-byte address) if let Some(sender) = &self.sender { let sender_raw = api.addr_canonicalize(sender.as_str())?; - received_data = cbor::to_vec(&(self.amount.to_be_bytes(), sender_raw.as_slice())) - .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + encoder.ext_address(sender_raw)?; } else { - received_data = cbor::to_vec(&(self.amount.to_be_bytes(), ZERO_ADDR)) - .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + encoder.ext_bytes(&ZERO_ADDR)?; } - Ok(received_data) + + // memo_len:uint (1-byte uint) + encoder.ext_u8(self.memo_len.clamp(0, u8::MAX.into()) as u8)?; + + Ok(()) } } -// spent = [ -// amount: biguint, ; transfer amount in base denomination -// actions: uint ; number of actions the execution performed -// recipient: bstr, ; byte sequence of first recipient's canonical address -// balance: biguint ; sender's new balance aactions: uint ; number of actions the execution performedfter the transfer -// ] - +/// ```cddl +/// spent = [ +/// amount: biguint .size 8, ; transfer amount in base denomination +/// actions: uint .size 1, ; number of actions the execution performed +/// recipient: bstr .size 20, ; byte sequence of first recipient's canonical address +/// balance: biguint .size 8, ; sender's new balance aactions +/// ] +/// ``` #[derive(Serialize, Debug, Deserialize, Clone)] #[cfg_attr(test, derive(Eq, PartialEq))] -pub struct SpentNotificationData { +pub struct SpentNotification { pub amount: u128, pub actions: u32, pub recipient: Option, pub balance: u128, + pub memo_len: usize, } -impl NotificationData for SpentNotificationData { + +impl DirectChannel for SpentNotification { const CHANNEL_ID: &'static str = "spent"; - const CDDL_SCHEMA: &'static str = "spent=[amount:biguint,actions:uint,recipient:bstr,balance:biguint]"; - fn to_cbor(&self, api: &dyn Api) -> StdResult> { - let spent_data; + const CDDL_SCHEMA: &'static str = "spent=[amount:biguint .size 8,actions:uint .size 1,recipient:bstr .size 20,balance:biguint .size 8]"; + const ELEMENTS: u64 = 4; + const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_U8 + CBL_ADDRESS + CBL_BIGNUM_U64; + + fn encode_cbor(&self, api: &dyn Api, encoder: &mut Encoder<&mut [u8]>) -> StdResult<()> { + // amount:biguint (8-byte uint), actions:uint (1-byte uint) + let mut spent_data = encoder + .ext_u64_from_u128(self.amount)? + .ext_u8(self.actions.clamp(0, u8::MAX.into()) as u8)?; + + // recipient:bstr (20-byte address) if let Some(recipient) = &self.recipient { let recipient_raw = api.addr_canonicalize(recipient.as_str())?; - spent_data = cbor::to_vec(&( - self.amount.to_be_bytes(), - self.actions.to_be_bytes(), - recipient_raw.as_slice(), - self.balance.to_be_bytes(), - )) - .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + spent_data = spent_data.ext_address(recipient_raw)?; } else { - spent_data = cbor::to_vec(&( - self.amount.to_be_bytes(), - self.actions.to_be_bytes(), - ZERO_ADDR, - self.balance.to_be_bytes(), - )) - .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; + spent_data = spent_data.ext_bytes(&ZERO_ADDR)? } - Ok(spent_data) + + // balance:biguint (8-byte uint) + spent_data.ext_u64_from_u128(self.balance)?; + + Ok(()) } } -//allowance = [ -// amount: biguint, ; allowance amount in base denomination -// allower: bstr, ; byte sequence of allower's canonical address -// expiration: uint, ; epoch seconds of allowance expiration -//] +///```cddl +/// allowance = [ +/// amount: biguint .size 8, ; allowance amount in base denomination +/// allower: bstr .size 20, ; byte sequence of allower's canonical address +/// expiration: uint .size 8, ; epoch seconds of allowance expiration +///] +/// ``` #[derive(Serialize, Debug, Deserialize, Clone)] #[cfg_attr(test, derive(Eq, PartialEq))] -pub struct AllowanceNotificationData { +pub struct AllowanceNotification { pub amount: u128, pub allower: Addr, pub expiration: Option, } -impl NotificationData for AllowanceNotificationData { +impl DirectChannel for AllowanceNotification { const CHANNEL_ID: &'static str = "allowance"; - const CDDL_SCHEMA: &'static str = "allowance=[amount:biguint,allower:bstr,expiration:uint]"; - fn to_cbor(&self, api: &dyn Api) -> StdResult> { + const CDDL_SCHEMA: &'static str = "allowance=[amount:biguint .size 8,allower:bstr .size 20,expiration:uint .size 8]"; + const ELEMENTS: u64 = 3; + const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_ADDRESS + CBL_TIMESTAMP; + + fn encode_cbor(&self, api: &dyn Api, encoder: &mut Encoder<&mut [u8]>) -> StdResult<()> { let allower_raw = api.addr_canonicalize(self.allower.as_str())?; - // use CBOR to encode data - let updated_allowance_data = cbor::to_vec(&( - self.amount.to_be_bytes(), - allower_raw.as_slice(), - self.expiration.unwrap_or(0u64), // expiration == 0 means no expiration - )) - .map_err(|e| StdError::generic_err(format!("{:?}", e)))?; - Ok(updated_allowance_data) + // amount:biguint (8-byte uint), allower:bstr (20-byte address), expiration:uint (8-byte timestamp) + encoder + .ext_u64_from_u128(self.amount)? + .ext_bytes(allower_raw.as_slice())? + .ext_timestamp(self.expiration.unwrap_or_default())?; + + Ok(()) } } -// multi recipient push notifications +pub struct MultiRecvdNotification(pub Vec>); -// id for the `multirecvd` channel -pub const MULTI_RECEIVED_CHANNEL_ID: &str = "multirecvd"; -pub const MULTI_RECEIVED_CHANNEL_BLOOM_K: u32 = 15; -pub const MULTI_RECEIVED_CHANNEL_BLOOM_N: u32 = 16; -pub const MULTI_RECEIVED_CHANNEL_PACKET_SIZE: u32 = 24; +impl GroupChannel for MultiRecvdNotification { + const CHANNEL_ID: &'static str = "multirecvd"; -// id for the `multispent` channel -pub const MULTI_SPENT_CHANNEL_ID: &str = "multispent"; -pub const MULTI_SPENT_CHANNEL_BLOOM_K: u32 = 5; -pub const MULTI_SPENT_CHANNEL_BLOOM_N: u32 = 4; -pub const MULTI_SPENT_CHANNEL_PACKET_SIZE: u32 = 40; + // bloom parameters for the `multirecvd` channel: + const BLOOM_N: usize = 16; + const BLOOM_M: u32 = 512; + const BLOOM_K: u32 = 22; -pub fn multi_received_data( - api: &dyn Api, - notifications: Vec>, - tx_hash: &String, - env_random: Binary, - secret: &[u8], -) -> StdResult> { - let mut received_bloom_filter: U512 = U512::from(0); - let mut received_packets: Vec<(Addr, Vec)> = vec![]; - - // keep track of how often addresses might show up in packet data. - // we need to remove any address that might show up more than once. - let mut recipient_counts: HashMap = HashMap::new(); - - for notification in ¬ifications { - recipient_counts.insert( - notification.notification_for.clone(), - recipient_counts - .get(¬ification.notification_for) - .unwrap_or(&0u16) - + 1, - ); - - // we can short circuit this if recipient count > 1, since we will throw out this packet - // anyway, and address has already been added to bloom filter - if *recipient_counts - .get(¬ification.notification_for) - .unwrap() - > 1 - { - continue; - } + // flagsAndAmount:8 + ownerId:8 == 16 bytes + const PACKET_SIZE: usize = 16; - // contribute to received bloom filter - let recipient_addr_raw = api.addr_canonicalize(notification.notification_for.as_str())?; - let seed = get_seed(&recipient_addr_raw, secret)?; - let id = notification_id(&seed, &MULTI_RECEIVED_CHANNEL_ID.to_string(), &tx_hash)?; - let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); - for _ in 0..MULTI_RECEIVED_CHANNEL_BLOOM_K { - let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize(); - received_bloom_filter = received_bloom_filter | (U512::from(1) << bit_index); - hash_bytes = hash_bytes >> 9; - } + fn notifications(&self) -> &Vec> { + &self.0 + } + fn build_packet(&self, api: &dyn Api, data: &RecvdNotification) -> StdResult> { // make the received packet - let mut received_packet_plaintext: Vec = vec![]; - // amount bytes (u128 == 16 bytes) - received_packet_plaintext.extend_from_slice(¬ification.data.amount.to_be_bytes()); - // sender account last 8 bytes - let sender_bytes: &[u8]; - let sender_raw; - if let Some(sender) = ¬ification.data.sender { - sender_raw = api.addr_canonicalize(sender.as_str())?; - sender_bytes = &sender_raw.as_slice()[sender_raw.0.len() - 8..]; + let mut packet_plaintext = [0u8; Self::PACKET_SIZE]; + + // encode flags and amount into 8 bytes (leftmost 2 bits reserved) + let amount_bytes = &(data.amount.clamp(0, U62_MAX) + | (((data.memo_len != 0) as u128) << 63) + | ((data.sender_is_owner as u128) << 62) + ).to_be_bytes()[8..]; + + // packet flag bits and amount bytes (u64 == 8 bytes) + packet_plaintext[0..8].copy_from_slice(amount_bytes); + + // determine owner address + let owner_addr: CanonicalAddr; + let owner_bytes: &[u8]; + if let Some(owner) = &data.sender { + owner_addr = api.addr_canonicalize(owner.as_str())?; + owner_bytes = &owner_addr.as_slice() } else { - sender_bytes = &ZERO_ADDR[ZERO_ADDR.len() - 8..]; + owner_bytes = &ZERO_ADDR; } - // 24 bytes total - received_packet_plaintext.extend_from_slice(sender_bytes); - let received_packet_id = &id.0.as_slice()[0..8]; - let received_packet_ikm = &id.0.as_slice()[8..32]; - let received_packet_ciphertext = - xor_bytes(received_packet_plaintext.as_slice(), received_packet_ikm); - let received_packet_bytes: Vec = - [received_packet_id.to_vec(), received_packet_ciphertext].concat(); + // packet owner address terminal 8 bytes (8 bytes) + packet_plaintext[8..16].copy_from_slice(&owner_bytes[12..]); - received_packets.push((notification.notification_for.clone(), received_packet_bytes)); + // 16 bytes total + Ok(packet_plaintext.to_vec()) } +} - // filter out any notifications for recipients showing up more than once - let mut received_packets: Vec> = received_packets - .into_iter() - .filter(|(addr, _)| *recipient_counts.get(addr).unwrap_or(&0u16) <= 1) - .map(|(_, packet)| packet) - .collect(); - if received_packets.len() > MULTI_RECEIVED_CHANNEL_BLOOM_N as usize { - // still too many packets - received_packets = received_packets[0..MULTI_RECEIVED_CHANNEL_BLOOM_N as usize].to_vec(); +// maximum supported filter size is currently 512 bits +const_assert!(MultiRecvdNotification::BLOOM_M <= 512); + +// ensure m is a power of 2 +const_assert!(MultiRecvdNotification::BLOOM_M.trailing_zeros() == MultiRecvdNotification::BLOOM_M_LOG2); + +// ensure there are enough bits in the 32-byte source hash to provide entropy for the hashes +const_assert!(MultiRecvdNotification::BLOOM_K * MultiRecvdNotification::BLOOM_M_LOG2 <= 256); + +// this implementation is optimized to not check for packet sizes larger than 24 bytes +const_assert!(MultiRecvdNotification::PACKET_SIZE <= 24); + + +pub struct MultiSpentNotification(pub Vec>); + + +impl GroupChannel for MultiSpentNotification { + const CHANNEL_ID: &str = "multispent"; + + // bloom parameters for the `multispent` channel: + const BLOOM_N: usize = 4; + const BLOOM_M: u32 = 128; + const BLOOM_K: u32 = 22; + + // flagsAndAmount:8 + recipientId:8 + balance:8 == 24 bytes + const PACKET_SIZE: usize = 24; + + fn notifications(&self) -> &Vec> { + &self.0 } - // now add extra packets, if needed, to hide number of packets - let padding_size = - MULTI_RECEIVED_CHANNEL_BLOOM_N.saturating_sub(received_packets.len() as u32) as usize; - if padding_size > 0 { - let padding_addresses = hkdf_sha_512( - &Some(vec![0u8; 64]), - &env_random, - format!("{}:decoys", MULTI_RECEIVED_CHANNEL_ID).as_bytes(), - padding_size * 20, // 20 bytes per random addr - )?; + fn build_packet(&self, api: &dyn Api, data: &SpentNotification) -> StdResult> { + // prep the packet plaintext + let mut packet_plaintext = [0u8; Self::PACKET_SIZE]; - // handle each padding package - for i in 0..padding_size { - let padding_address = &padding_addresses[i * 20..(i + 1) * 20]; - - // contribute padding packet to bloom filter - let seed = get_seed(&CanonicalAddr::from(padding_address), secret)?; - let id = notification_id(&seed, &MULTI_RECEIVED_CHANNEL_ID.to_string(), &tx_hash)?; - let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); - for _ in 0..MULTI_RECEIVED_CHANNEL_BLOOM_K { - let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize(); - received_bloom_filter = received_bloom_filter | (U512::from(1) << bit_index); - hash_bytes = hash_bytes >> 9; - } - - // padding packet plaintext - let padding_packet_plaintext = [0u8; MULTI_RECEIVED_CHANNEL_PACKET_SIZE as usize]; - let padding_packet_id = &id.0.as_slice()[0..8]; - let padding_packet_ikm = &id.0.as_slice()[8..32]; - let padding_packet_ciphertext = - xor_bytes(padding_packet_plaintext.as_slice(), padding_packet_ikm); - let padding_packet_bytes: Vec = - [padding_packet_id.to_vec(), padding_packet_ciphertext].concat(); - received_packets.push(padding_packet_bytes); + // encode flags and amount into 8 bytes (leftmost 2 bits reserved) + let amount_bytes = &(data.amount.clamp(0, U62_MAX) + | (((data.memo_len != 0) as u128) << 63) + ).to_be_bytes()[8..]; + + // packet flags and amount bytes (u64 == 8 bytes) + packet_plaintext[0..8].copy_from_slice(amount_bytes); + + // determine recipient address + let recipient_addr: CanonicalAddr; + let recipient_bytes: &[u8]; + if let Some(recipient) = &data.recipient { + recipient_addr = api.addr_canonicalize(recipient.as_str())?; + recipient_bytes = recipient_addr.as_slice(); + } else { + recipient_bytes = &ZERO_ADDR; } - } - let mut received_bloom_filter_bytes: Vec = vec![]; - for biguint in received_bloom_filter.0 { - received_bloom_filter_bytes.extend_from_slice(&biguint.to_be_bytes()); - } - for packet in received_packets { - received_bloom_filter_bytes.extend(packet.iter()); + // packet recipient address terminal 8 bytes (8 bytes) + packet_plaintext[8..16].copy_from_slice(&recipient_bytes[12..]); + + // balance bytes (u64 == 8 bytes) + packet_plaintext[16..24].copy_from_slice( + &data.balance + .clamp(0, u64::MAX.into()) + .to_be_bytes()[8..] + ); + + // 24 bytes total + Ok(packet_plaintext.to_vec()) } +} + +// maximum supported filter size is currently 512 bits +const_assert!(MultiSpentNotification::BLOOM_M <= 512); + +// ensure m is a power of 2 +const_assert!(MultiSpentNotification::BLOOM_M.trailing_zeros() == MultiSpentNotification::BLOOM_M_LOG2); + +// ensure there are enough bits in the 32-byte source hash to provide entropy for the hashes +const_assert!(MultiSpentNotification::BLOOM_K * MultiSpentNotification::BLOOM_M_LOG2 <= 256); + +// this implementation is optimized to not check for packet sizes larger than 24 bytes +const_assert!(MultiSpentNotification::PACKET_SIZE <= 24); + + +struct BloomFilter { + filter: U512, + tx_hash: String, + secret: Vec, +} + +impl BloomFilter { + fn add>( + &mut self, + recipient: &CanonicalAddr, + packet_plaintext: &Vec, + ) -> StdResult> { + // contribute to received bloom filter + let seed = get_seed(&recipient, &self.secret)?; + let id = notification_id(&seed, G::CHANNEL_ID, &self.tx_hash)?; + let hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); + let bloom_mask: U256 = U256::from(G::BLOOM_M - 1); + + // each hash section for up to k times + for i in 0..G::BLOOM_K { + let bit_index = ((hash_bytes >> (256 - G::BLOOM_M_LOG2 - (i * G::BLOOM_M_LOG2))) & bloom_mask).as_usize(); + self.filter |= U512::from(1) << bit_index; + } + + // use top 64 bits of notification ID for packet ID + let packet_id = &id.0.as_slice()[0..8]; - Ok(received_bloom_filter_bytes) + // take the bottom bits from the notification ID for key material + let packet_ikm = &id.0.as_slice()[8..32]; + + // create ciphertext by XOR'ing the plaintext with the notification ID + let packet_ciphertext = xor_bytes( + &packet_plaintext[..], + &packet_ikm[0..G::PACKET_SIZE] + ); + + // construct the packet bytes + let packet_bytes: Vec = [ + packet_id.to_vec(), + packet_ciphertext, + ].concat(); + + Ok(packet_bytes) + } } -pub fn multi_spent_data( + +pub fn render_group_notification>( api: &dyn Api, - notifications: Vec>, + group: G, tx_hash: &String, env_random: Binary, secret: &[u8], -) -> StdResult> { - let mut spent_bloom_filter: U512 = U512::from(0); - let mut spent_packets: Vec<(Addr, Vec)> = vec![]; - - // keep track of how often addresses might show up in packet data. - // we need to remove any address that might show up more than once. - let mut spent_counts: HashMap = HashMap::new(); - - for notification in ¬ifications { - spent_counts.insert( - notification.notification_for.clone(), - spent_counts - .get(¬ification.notification_for) - .unwrap_or(&0u16) - + 1, + resp: Response, +) -> StdResult { + // bloom filter + let mut bloom_filter = BloomFilter { + filter: U512::from(0), + tx_hash: tx_hash.to_string(), + secret: secret.to_vec(), + }; + + // packet structs + let mut packets: Vec<(CanonicalAddr, Vec)> = vec![]; + + // keep track of how many times an address shows up in packet data + let mut recipient_counts: HashMap = HashMap::new(); + + // each notification + for notification in group.notifications() { + // who notification is intended for + let notification_for = api.addr_canonicalize(notification.notification_for.as_str())?; + let notifyee = notification_for.clone(); + + // increment count of recipient occurrence + recipient_counts.insert( + notification_for, + recipient_counts + .get(¬ifyee) + .unwrap_or(&0u16) + 1, ); - // we can short circuit this if recipient count > 1, since we will throw out this packet - // anyway, and address has already been added to bloom filter - if *spent_counts.get(¬ification.notification_for).unwrap() > 1 { + // skip adding this packet if recipient was already seen + if *recipient_counts.get(¬ifyee).unwrap() > 1 { continue; } - let spender_addr_raw = api.addr_canonicalize(notification.notification_for.as_str())?; - let seed = get_seed(&spender_addr_raw, secret)?; - let id = notification_id(&seed, &MULTI_SPENT_CHANNEL_ID.to_string(), &tx_hash)?; - let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); - for _ in 0..MULTI_SPENT_CHANNEL_BLOOM_K { - let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize(); - spent_bloom_filter = spent_bloom_filter | (U512::from(1) << bit_index); - hash_bytes = hash_bytes >> 9; - } - - // make the spent packet - let mut spent_packet_plaintext: Vec = vec![]; - // amount bytes (u128 == 16 bytes) - spent_packet_plaintext.extend_from_slice(¬ification.data.amount.to_be_bytes()); - // balance bytes (u128 == 16 bytes) - spent_packet_plaintext.extend_from_slice(¬ification.data.balance.to_be_bytes()); - // recipient account last 8 bytes - let recipient_bytes: &[u8]; - let recipient_raw; - if let Some(recipient) = ¬ification.data.recipient { - recipient_raw = api.addr_canonicalize(recipient.as_str())?; - recipient_bytes = &recipient_raw.as_slice()[recipient_raw.0.len() - 8..]; - } else { - recipient_bytes = &ZERO_ADDR[ZERO_ADDR.len() - 8..]; - } - // 40 bytes total - spent_packet_plaintext.extend_from_slice(recipient_bytes); + // build packet + let packet_plaintext = &group.build_packet(api, ¬ification.data)?; - let spent_packet_size = spent_packet_plaintext.len(); - let spent_packet_id = &id.0.as_slice()[0..8]; - let spent_packet_ikm = &id.0.as_slice()[8..32]; - let spent_packet_key = hkdf_sha_512( - &Some(vec![0u8; 64]), - spent_packet_ikm, - "".as_bytes(), - spent_packet_size, + // add to bloom filter + let packet_bytes = bloom_filter.add::( + ¬ifyee, + packet_plaintext, )?; - let spent_packet_ciphertext = xor_bytes( - spent_packet_plaintext.as_slice(), - spent_packet_key.as_slice(), - ); - let spent_packet_bytes: Vec = - [spent_packet_id.to_vec(), spent_packet_ciphertext].concat(); - spent_packets.push((notification.notification_for.clone(), spent_packet_bytes)); + // add to packets data + packets.push((notifyee, packet_bytes)); } - // filter out any notifications for senders showing up more than once - let mut spent_packets: Vec> = spent_packets + // filter out any notifications for recipients showing up more than once + let mut packets: Vec> = packets .into_iter() - .filter(|(addr, _)| *spent_counts.get(addr).unwrap_or(&0u16) <= 1) + .filter(|(addr, _)| *recipient_counts.get(addr).unwrap_or(&0u16) <= 1) .map(|(_, packet)| packet) .collect(); - if spent_packets.len() > MULTI_SPENT_CHANNEL_BLOOM_N as usize { - // still too many packets - spent_packets = spent_packets[0..MULTI_SPENT_CHANNEL_BLOOM_N as usize].to_vec(); + + // still too many packets; trim down to size + if packets.len() > G::BLOOM_N { + packets = packets[0..G::BLOOM_N].to_vec(); } // now add extra packets, if needed, to hide number of packets - let padding_size = - MULTI_SPENT_CHANNEL_BLOOM_N.saturating_sub(spent_packets.len() as u32) as usize; + let padding_size = G::BLOOM_N.saturating_sub(packets.len()); if padding_size > 0 { - let padding_addresses = hkdf_sha_512( + // fill buffer with secure random bytes + let decoy_addresses = hkdf_sha_512( &Some(vec![0u8; 64]), &env_random, - format!("{}:decoys", MULTI_SPENT_CHANNEL_ID).as_bytes(), - padding_size * 20, // 20 bytes per random addr + format!("{}:decoys", G::CHANNEL_ID).as_bytes(), + padding_size * 20, // 20 bytes per random addr )?; // handle each padding package for i in 0..padding_size { - let padding_address = &padding_addresses[i * 20..(i + 1) * 20]; - - // contribute padding packet to bloom filter - let seed = get_seed(&CanonicalAddr::from(padding_address), secret)?; - let id = notification_id(&seed, &MULTI_SPENT_CHANNEL_ID.to_string(), &tx_hash)?; - let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); - for _ in 0..MULTI_SPENT_CHANNEL_BLOOM_K { - let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize(); - spent_bloom_filter = spent_bloom_filter | (U512::from(1) << bit_index); - hash_bytes = hash_bytes >> 9; - } - - // padding packet plaintext - let padding_packet_plaintext = [0u8; MULTI_SPENT_CHANNEL_PACKET_SIZE as usize]; - let padding_plaintext_size = MULTI_SPENT_CHANNEL_PACKET_SIZE as usize; - let padding_packet_id = &id.0.as_slice()[0..8]; - let padding_packet_ikm = &id.0.as_slice()[8..32]; - let padding_packet_key = hkdf_sha_512( - &Some(vec![0u8; 64]), - padding_packet_ikm, - "".as_bytes(), - padding_plaintext_size, - )?; - let padding_packet_ciphertext = xor_bytes( - padding_packet_plaintext.as_slice(), - padding_packet_key.as_slice(), - ); - let padding_packet_bytes: Vec = - [padding_packet_id.to_vec(), padding_packet_ciphertext].concat(); - spent_packets.push(padding_packet_bytes); + // generate address + let address = CanonicalAddr::from(&decoy_addresses[i * 20..(i + 1) * 20]); + + // nil plaintext + let packet_plaintext = vec![0u8; G::PACKET_SIZE]; + + // produce bytes + let packet_bytes = bloom_filter.add::(&address, &packet_plaintext)?; + + // add to packets list + packets.push(packet_bytes); } } - let mut spent_bloom_filter_bytes: Vec = vec![]; - for biguint in spent_bloom_filter.0 { - spent_bloom_filter_bytes.extend_from_slice(&biguint.to_be_bytes()); - } - for packet in spent_packets { - spent_bloom_filter_bytes.extend(packet.iter()); + // prep output bytes + let mut output_bytes: Vec = vec![]; + + // append bloom filter (taking m bottom bits of 512-bit filter) + output_bytes.extend_from_slice( + &bloom_filter.filter.to_big_endian()[((512 - G::BLOOM_M as usize) >> 3)..]); + + // append packets + for packet in packets { + output_bytes.extend(packet.iter()); } - Ok(spent_bloom_filter_bytes) -} \ No newline at end of file + // Ok(output_bytes) + Ok(resp.add_attribute_plaintext( + format!("snip52:#{}", G::CHANNEL_ID), + Binary::from(output_bytes).to_base64())) +} diff --git a/src/state.rs b/src/state.rs index 3c44b4fb..5dcea935 100644 --- a/src/state.rs +++ b/src/state.rs @@ -222,8 +222,14 @@ impl ReceiverHashStore { } } -pub static INTERNAL_SECRET: Item> = Item::new(b"internal-secret"); +/// internal secret used for sensitive data such as address hashes in the btbe and notifications +pub static INTERNAL_SECRET_SENSITIVE: Item> = Item::new(b"internal-secret-secure"); -// SNIP-52 channels +/// internal secret used for less sensitive data such as obfuscating tx IDs +pub static INTERNAL_SECRET_RELAXED: Item> = Item::new(b"internal-secret-prng"); + +/// SNIP-52 channels pub static CHANNELS: Keyset = Keyset::new(b"channel-ids"); +/// SNIP-52 status +pub static NOTIFICATIONS_ENABLED: Item = Item::new(b"notify-status"); \ No newline at end of file diff --git a/src/strings.rs b/src/strings.rs index 71a574be..3ac3321b 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1,2 +1,4 @@ pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str = "`transfer_history` query is UNSUPPORTED. Use `transaction_history` instead."; +pub const SEND_TO_SSCRT_CONTRACT_MSG: &str = + "Tokens cannot be sent to sscrt contract."; \ No newline at end of file From 55d195c5bc323b409a16cb3514d8cdbb376cda8f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 19 Dec 2024 11:38:42 +1300 Subject: [PATCH 69/87] update docs --- README.md | 135 ++++++++++-------------------------------------------- 1 file changed, 25 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 294b59d5..d04670b9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SNIP-20 Reference Implementation -This is an implementation of a [SNIP-20](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md), [SNIP-21](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-21.md), [SNIP-22](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-22.md), [SNIP-23](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-23.md), [SNIP-24](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md), [SNIP-25](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-25.md) and [SNIP-26](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-26.md) compliant token contract. +This is an implementation of a [SNIP-20](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md), [SNIP-21](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-21.md), [SNIP-22](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-22.md), [SNIP-23](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-23.md), [SNIP-24](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md), [~~SNIP-25~~](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-25.md), [SNIP-26](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-26.md), [~~SNIP-50~~](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-50.md) and [SNIP-52](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-52.md) compliant token contract. > **Note:** > The master branch contains new features not covered by officially-released SNIPs and may be subject to change. When releasing a token on mainnet, we recommend you start with a [tagged release](https://github.com/scrtlabs/snip20-reference-impl/tags) to ensure compatibility with SNIP standards. @@ -66,112 +66,27 @@ All transactions are encrypted, so if you want to see the error returned by a fa `secretcli q compute tx ` -# SNIP 25 Security Update - -## Security Changes -1. Implemented the ability to have decoy addresses for every operation that access account's balance -2. Converted every add operation related to account's balance and total supply -3. Started using u128 instead of Uint128 - -## Decoys -### Transaction That Support Decoys -1. Redeem -2. Deposit -3. Transfer -4. TransferFrom -5. Send -6. SendFrom -7. Burn -8. BurnFrom -9. Mint -10. BatchTransfer - For every action (The strength of the decoys will be the minimal strength of all of the actions) -11. BatchSend - For every action (The strength of the decoys will be the minimal strength of all of the actions) -12. BatchTransferFrom - For every action (The strength of the decoys will be the minimal strength of all of the actions) -13. BatchSendFrom - For every action (The strength of the decoys will be the minimal strength of all of the actions) -14. BatchMint - For every action (The strength of the decoys will be the minimal strength of all of the actions) -15. BatchBurnFrom - For every action (The strength of the decoys will be the minimal strength of all of the actions) - -### Example -```secretcli tx compute execute '{"transfer":{"recipient":"
","amount":"", "entropy":"", "decoys":<[addresses_list]>}}' --from ``` - -## Future Work -| Topic | Immediate-term solution | Medium-term solution | Long-term solution | -| --- | --- | --- | --- | -| Receiver privacy | Decoys - offer limited privacy, since it depends a lot on how you choose decoys. There’s probably no way to select decoys effectively enough, and thus it only makes it a bit harder but effectively doesn’t provide receiver privacy to a sophisticated long-term attacker | Some sort of bucketing? - still no clear path forward| ORAM? - still no clear path forward | -| Transfer amount privacy - subtractions (Transfer/Send/Burn) | None | None | Merkle proofs for storage reads - will make it very difficult to simulate transactions and play with storage. | - -# SNIP 25 Other Updates - -## All Allowances -Adds the ability for an owner to query for all allowances they have given out, as well as for a spender to query for all allowances they have received. - -## Queries - -### AllowancesGiven - -This query MUST be authenticated. - -Returns the list of allowances given out by the current account as an owner, as well as the total count of allowances given out. - -Results SHOULD be paginated. Results MUST be sorted in reverse chronological order by the datetime at which the allowance was first created (i.e., order is not determined by expiration, nor by last modified). - -#### Request - -| Name | Type | Description | optional | -| ---- | ---- | ----------- | -------- | -| [with_permit].query.allowances_given.owner | string | Account from which tokens are allowed to be taken | no | -| [with_permit].query.allowances_given.page_size | number | Number of allowances to return, starting from the latest. i.e. n=1 will return only the latest allowance | no | -| [with_permit].query.allowances_given.page | number | Defaults to 0. Specifying a positive number will skip page * page_size txs from the start. | yes | - -#### Response -```json -{ - "allowances_given": { - "owner": "
", - "allowances": [ - { - "spender": "
", - "allowance": "Uint128", - "expiration": 1234, - }, - { "...": "..." } - ], - "count": 200 - } -} -``` - -### AllowancesReceived - -This query MUST be authenticated. - -Returns the list of allowances given to the current account as a spender, as well as the total count of allowances received. - -Results SHOULD be paginated. Results MUST be sorted in reverse chronological order by the datetime at which the allowance was first created (i.e., order is not determined by expiration). - -#### Request - -| Name | Type | Description | optional | -| ---- | ---- | ----------- | -------- | -| [with_permit.]query.allowances_received.spender | string | Account which is allowed to spend tokens on behalf of the owner | no | -| [with_permit.]query.allowances_received.page_size | number | Number of allowances to return, starting from the latest. i.e. n=1 will return only the latest allowance | no | -| [with_permit.]query.allowances_received.page | number | Defaults to 0. Specifying a positive number will skip page * page_size txs from the start. | yes | - -#### Response - -```json -{ - "allowances_received": { - "spender": "
", - "allowances": [ - { - "owner": "
", - "allowance": "Uint128", - "expiration": 1234, - }, - { "...": "..." } - ], - "count": 200 - } -} -``` +## Privacy Enhancements + + - All transfers/sends (including batch and *_from) use the delayed write buffer (dwb) to address "spicy printf" storage access pattern attacks. + - Additionally, a bitwise trie of bucketed entries (dwb) creates dynamic anonymity sets for senders/owners, whose balance must be checked when transferring/sending. It also enhances privacy for recipients. + - When querying for Transaction History, each event's `id` field returned in responses are deterministically obfuscated by `ChaChaRng(XorBytes(ChaChaRng(actual_event_id), internal_secret)) >> (64 - 53)` for better privacy. Without this, an attacker could deduce the number of events that took place between two transactions. + +## SNIP-52: Private Push Notifications + +This contract publishes encrypted messages to the event log which carry data intended to notify recipients of actions that affect them, such as token transfer and allowances. + +Direct channels: + - `recvd` -- emitted to a recipient when their account receives funds via one of `transfer`, `send`, `transfer_from`, or `send_from`. The notification data includes the amount, the sender, and the memo length. + - `spent` -- emitted to an owner when their funds are spent, via one of `transfer`, `send`, `transfer_from` or `send_from`. The notification data includes the amount, the recipient, the owner's new balance, and a few other pieces of information such as memo length, number of actions, and whether the spender was the transaction's sender. + - `allowance` -- emitted to a spender when some allower account has granted them or modified an existing allowance to spend their tokens, via `increase_allowance` or `decrease_allowance`. The notification data includes the amount, the allower, and the expiration of the allowance. + +Group channels: + - `multirecvd` -- emitted to a group of recipients (up to 16) when a `batch_transfer`, `batch_send`, `batch_transfer_from`, or `batch_send_from` has been executed. Each recipient will receive a packet of data containing the amount they received, the last 8 bytes of the owner's address, and some additional metadata. + - `multispent` -- emitted to a group of spenders (up to 16) when a `batch_transfer_from`, or `batch_send_from` has been executed. Each spender will receive a packet of data containing the amount that was spent, the last 8 bytes of the recipient's address, and some additional metadata. + + +## Security Features + + - Transfers to the contract itself will be rejected to prevent accidental loss of funds. + - The migration allows for a one-time processing of refunding any previous transfers made to the contract itself. From a8cdb1d9c9cc5e464bc19738c466209266eddf4d Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 19 Dec 2024 12:07:19 +1300 Subject: [PATCH 70/87] remove sscrt specific error msg --- src/contract.rs | 22 +++++++++++----------- src/strings.rs | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index ac7f80be..af034f5f 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -38,7 +38,7 @@ use crate::receiver::Snip20ReceiveMsg; use crate::state::{ safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY }; -use crate::strings::{SEND_TO_SSCRT_CONTRACT_MSG, TRANSFER_HISTORY_UNSUPPORTED_MSG}; +use crate::strings::{SEND_TO_CONTRACT_ERR_MSG, TRANSFER_HISTORY_UNSUPPORTED_MSG}; use crate::transaction_history::{ store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx }; @@ -1669,9 +1669,9 @@ fn try_transfer( let symbol = CONFIG.load(deps.storage)?.symbol; - // make sure the sender is not accidentally sending tokens to the sscrt contract address + // make sure the sender is not accidentally sending tokens to the contract address if recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); } #[cfg(feature = "gas_tracking")] @@ -1765,9 +1765,9 @@ fn try_batch_transfer( for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; - // make sure the sender is not accidentally sending tokens to the sscrt contract address + // make sure the sender is not accidentally sending tokens to the contract address if recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); } total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); @@ -1937,9 +1937,9 @@ fn try_send( let mut messages = vec![]; let symbol = CONFIG.load(deps.storage)?.symbol; - // make sure the sender is not accidentally sending tokens to the sscrt contract address + // make sure the sender is not accidentally sending tokens to the contract address if recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); } #[cfg(feature = "gas_tracking")] @@ -2021,9 +2021,9 @@ fn try_batch_send( for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; - // make sure the sender is not accidentally sending tokens to the sscrt contract address + // make sure the sender is not accidentally sending tokens to the contract address if recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); } total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); @@ -2155,9 +2155,9 @@ fn try_transfer_from_impl( use_allowance(deps.storage, env, owner, spender, raw_amount)?; - // make sure the sender is not accidentally sending tokens to the sscrt contract address + // make sure the sender is not accidentally sending tokens to the contract address if *recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_SSCRT_CONTRACT_MSG)); + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); } #[cfg(feature = "gas_tracking")] diff --git a/src/strings.rs b/src/strings.rs index 3ac3321b..a456db18 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1,4 +1,4 @@ pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str = "`transfer_history` query is UNSUPPORTED. Use `transaction_history` instead."; -pub const SEND_TO_SSCRT_CONTRACT_MSG: &str = - "Tokens cannot be sent to sscrt contract."; \ No newline at end of file +pub const SEND_TO_CONTRACT_ERR_MSG: &str = + "Tokens cannot be sent to token contract."; \ No newline at end of file From f90eae4d58c24f536c5c6bb17e776014e6ef00e5 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 19 Dec 2024 14:23:42 +1300 Subject: [PATCH 71/87] refactor contract source into smaller files --- src/contract.rs | 2903 ++------------------------------- src/execute.rs | 242 +++ src/execute_admin.rs | 160 ++ src/execute_deposit_redeem.rs | 193 +++ src/execute_mint_burn.rs | 570 +++++++ src/execute_transfer_send.rs | 977 +++++++++++ src/lib.rs | 6 + src/query.rs | 573 +++++++ 8 files changed, 2846 insertions(+), 2778 deletions(-) create mode 100644 src/execute.rs create mode 100644 src/execute_admin.rs create mode 100644 src/execute_deposit_redeem.rs create mode 100644 src/execute_mint_burn.rs create mode 100644 src/execute_transfer_send.rs create mode 100644 src/query.rs diff --git a/src/contract.rs b/src/contract.rs index af034f5f..9edebae2 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,47 +1,39 @@ /// This contract implements SNIP-20 standard: /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md use cosmwasm_std::{ - entry_point, to_binary, Addr, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg, - Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128, Uint64, + entry_point, to_binary, Binary, + Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, }; #[cfg(feature = "gas_evaporation")] use cosmwasm_std::Api; -use rand_chacha::ChaChaRng; -use rand_core::{RngCore, SeedableRng}; -use secret_toolkit::notification::{get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, FlatDescriptor, GroupChannel, Notification, DirectChannel, StructDescriptor}; -use secret_toolkit::permit::{AllRevokedInterval, Permit, RevokedPermits, RevokedPermitsStore, TokenPermissions}; +use secret_toolkit::notification::{GroupChannel, DirectChannel,}; +use secret_toolkit::permit::{Permit, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{hkdf_sha_256, sha_256, ContractPrng}; -use crate::batch; +use crate::{execute, execute_admin, execute_deposit_redeem, execute_mint_burn, execute_transfer_send, query}; #[cfg(feature = "gas_tracking")] use crate::dwb::log_dwb; -use crate::dwb::{DelayedWriteBuffer, DWB, TX_NODES}; +use crate::dwb::{DelayedWriteBuffer, DWB}; + +use crate::btbe::initialize_btbe; -use crate::btbe::{ - find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count -}; #[cfg(feature = "gas_tracking")] use crate::gas_tracker::{GasTracker, LoggingExt}; #[cfg(feature = "gas_evaporation")] use crate::msg::Evaporator; use crate::msg::{ - AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg, - InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success, + ContractStatusLevel, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, }; use crate::notifications::{ - render_group_notification, AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification + AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification }; -use crate::receiver::Snip20ReceiveMsg; use crate::state::{ - safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY -}; -use crate::strings::{SEND_TO_CONTRACT_ERR_MSG, TRANSFER_HISTORY_UNSUPPORTED_MSG}; -use crate::transaction_history::{ - store_burn_action, store_deposit_action, store_mint_action, store_redeem_action, store_transfer_action, Tx + Config, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, TOTAL_SUPPLY }; +use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; /// We make sure that responses from `handle` are padded to a multiple of this size. pub const RESPONSE_BLOCK_SIZE: usize = 256; @@ -134,7 +126,7 @@ pub fn instantiate( let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?; #[cfg(feature = "gas_tracking")] let mut tracker = GasTracker::new(deps.api); - perform_mint( + execute_mint_burn::perform_mint( deps.storage, &mut rng, &raw_admin, @@ -210,12 +202,22 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ContractStatusLevel::StopAll | ContractStatusLevel::StopAllButRedeems => { let response = match msg { ExecuteMsg::SetContractStatus { level, .. } => { - set_contract_status(deps, info, level) + // load contract config from storage + let config = CONFIG.load(deps.storage)?; + + // check that message sender is the admin + if config.admin != info.sender { + return Err(StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + )); + } + + execute_admin::set_contract_status(deps, level) } ExecuteMsg::Redeem { amount, denom, .. } if contract_status == ContractStatusLevel::StopAllButRedeems => { - try_redeem(deps, env, info, amount, denom) + execute_deposit_redeem::try_redeem(deps, env, info, amount, denom) } _ => Err(StdError::generic_err( "This contract is stopped and this action is not allowed", @@ -228,8 +230,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S let response = match msg.clone() { // Native - ExecuteMsg::Deposit { .. } => try_deposit(deps, env, info, &mut rng), - ExecuteMsg::Redeem { amount, denom, .. } => try_redeem(deps, env, info, amount, denom), + ExecuteMsg::Deposit { .. } => execute_deposit_redeem::try_deposit(deps, env, info, &mut rng), + ExecuteMsg::Redeem { amount, denom, .. } => execute_deposit_redeem::try_redeem(deps, env, info, amount, denom), // Base ExecuteMsg::Transfer { @@ -237,7 +239,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => try_transfer(deps, env, info, &mut rng, recipient, amount, memo), + } => execute_transfer_send::try_transfer(deps, env, info, &mut rng, recipient, amount, memo), ExecuteMsg::Send { recipient, recipient_code_hash, @@ -245,7 +247,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S msg, memo, .. - } => try_send( + } => execute_transfer_send::try_send( deps, env, info, @@ -257,15 +259,15 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S msg, ), ExecuteMsg::BatchTransfer { actions, .. } => { - try_batch_transfer(deps, env, info, &mut rng, actions) + execute_transfer_send::try_batch_transfer(deps, env, info, &mut rng, actions) } - ExecuteMsg::BatchSend { actions, .. } => try_batch_send(deps, env, info, &mut rng, actions), - ExecuteMsg::Burn { amount, memo, .. } => try_burn(deps, env, info, amount, memo), + ExecuteMsg::BatchSend { actions, .. } => execute_transfer_send::try_batch_send(deps, env, info, &mut rng, actions), + ExecuteMsg::Burn { amount, memo, .. } => execute_mint_burn::try_burn(deps, env, info, amount, memo), ExecuteMsg::RegisterReceive { code_hash, .. } => { - try_register_receive(deps, info, code_hash) + execute::try_register_receive(deps, info, code_hash) } - ExecuteMsg::CreateViewingKey { entropy, .. } => try_create_key(deps, env, info, entropy, &mut rng), - ExecuteMsg::SetViewingKey { key, .. } => try_set_key(deps, info, key), + ExecuteMsg::CreateViewingKey { entropy, .. } => execute::try_create_key(deps, env, info, entropy, &mut rng), + ExecuteMsg::SetViewingKey { key, .. } => execute::try_set_key(deps, info, key), // Allowance ExecuteMsg::IncreaseAllowance { @@ -273,20 +275,20 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, expiration, .. - } => try_increase_allowance(deps, env, info, spender, amount, expiration), + } => execute::try_increase_allowance(deps, env, info, spender, amount, expiration), ExecuteMsg::DecreaseAllowance { spender, amount, expiration, .. - } => try_decrease_allowance(deps, env, info, spender, amount, expiration), + } => execute::try_decrease_allowance(deps, env, info, spender, amount, expiration), ExecuteMsg::TransferFrom { owner, recipient, amount, memo, .. - } => try_transfer_from(deps, &env, info, &mut rng, owner, recipient, amount, memo), + } => execute_transfer_send::try_transfer_from(deps, &env, info, &mut rng, owner, recipient, amount, memo), ExecuteMsg::SendFrom { owner, recipient, @@ -295,7 +297,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S msg, memo, .. - } => try_send_from( + } => execute_transfer_send::try_send_from( deps, env, &info, @@ -308,18 +310,18 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S msg, ), ExecuteMsg::BatchTransferFrom { actions, .. } => { - try_batch_transfer_from(deps, &env, info, &mut rng, actions) + execute_transfer_send::try_batch_transfer_from(deps, &env, info, &mut rng, actions) } ExecuteMsg::BatchSendFrom { actions, .. } => { - try_batch_send_from(deps, env, &info, &mut rng, actions) + execute_transfer_send::try_batch_send_from(deps, env, &info, &mut rng, actions) } ExecuteMsg::BurnFrom { owner, amount, memo, .. - } => try_burn_from(deps, &env, info, owner, amount, memo), - ExecuteMsg::BatchBurnFrom { actions, .. } => try_batch_burn_from(deps, &env, info, actions), + } => execute_mint_burn::try_burn_from(deps, &env, info, owner, amount, memo), + ExecuteMsg::BatchBurnFrom { actions, .. } => execute_mint_burn::try_batch_burn_from(deps, &env, info, actions), // Mint ExecuteMsg::Mint { @@ -327,32 +329,18 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => try_mint(deps, env, info, &mut rng, recipient, amount, memo), - ExecuteMsg::BatchMint { actions, .. } => try_batch_mint(deps, env, info, &mut rng, actions), - - // Other - ExecuteMsg::ChangeAdmin { address, .. } => change_admin(deps, info, address), - ExecuteMsg::SetContractStatus { level, .. } => set_contract_status(deps, info, level), - ExecuteMsg::AddMinters { minters, .. } => add_minters(deps, info, minters), - ExecuteMsg::RemoveMinters { minters, .. } => remove_minters(deps, info, minters), - ExecuteMsg::SetMinters { minters, .. } => set_minters(deps, info, minters), + } => execute_mint_burn::try_mint(deps, env, info, &mut rng, recipient, amount, memo), + ExecuteMsg::BatchMint { actions, .. } => execute_mint_burn::try_batch_mint(deps, env, info, &mut rng, actions), // SNIP-24 - ExecuteMsg::RevokePermit { permit_name, .. } => revoke_permit(deps, info, permit_name), + ExecuteMsg::RevokePermit { permit_name, .. } => execute::revoke_permit(deps, info, permit_name), // SNIP-24.1 - ExecuteMsg::RevokeAllPermits { interval, .. } => revoke_all_permits(deps, info, interval), - ExecuteMsg::DeletePermitRevocation { revocation_id, .. } => delete_permit_revocation(deps, info, revocation_id), - - ExecuteMsg::AddSupportedDenoms { denoms, .. } => add_supported_denoms(deps, info, denoms), - ExecuteMsg::RemoveSupportedDenoms { denoms, .. } => { - remove_supported_denoms(deps, info, denoms) - }, + ExecuteMsg::RevokeAllPermits { interval, .. } => execute::revoke_all_permits(deps, info, interval), + ExecuteMsg::DeletePermitRevocation { revocation_id, .. } => execute::delete_permit_revocation(deps, info, revocation_id), - // SNIP-52 - ExecuteMsg::SetNotificationStatus { enabled, .. } => { - set_notification_status(deps, info, enabled) - }, + // Admin functions + _ => admin_execute(deps, info, msg) }; let padded_result = pad_handle_result(response, RESPONSE_BLOCK_SIZE); @@ -363,16 +351,46 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S padded_result } +pub fn admin_execute(deps: DepsMut, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + // load contract config from storage + let mut config = CONFIG.load(deps.storage)?; + + // check that message sender is the admin + if config.admin != info.sender { + return Err(StdError::generic_err( + "This is an admin command. Admin commands can only be run from admin address", + )); + } + + match msg { + ExecuteMsg::ChangeAdmin { address, .. } => execute_admin::change_admin(deps, &mut config, address), + ExecuteMsg::SetContractStatus { level, .. } => execute_admin::set_contract_status(deps, level), + ExecuteMsg::AddMinters { minters, .. } => execute_admin::add_minters(deps, &config, minters), + ExecuteMsg::RemoveMinters { minters, .. } => execute_admin::remove_minters(deps, &config, minters), + ExecuteMsg::SetMinters { minters, .. } => execute_admin::set_minters(deps, &config, minters), + ExecuteMsg::AddSupportedDenoms { denoms, .. } => execute_admin::add_supported_denoms(deps, &mut config, denoms), + ExecuteMsg::RemoveSupportedDenoms { denoms, .. } => { + execute_admin::remove_supported_denoms(deps, &mut config, denoms) + }, + + // SNIP-52 + ExecuteMsg::SetNotificationStatus { enabled, .. } => { + execute_admin::set_notification_status(deps, enabled) + }, + _ => panic!("This execute type is not an admin function"), + } +} + #[entry_point] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { pad_query_result( match msg { - QueryMsg::TokenInfo {} => query_token_info(deps.storage), - QueryMsg::TokenConfig {} => query_token_config(deps.storage), - QueryMsg::ContractStatus {} => query_contract_status(deps.storage), - QueryMsg::ExchangeRate {} => query_exchange_rate(deps.storage), - QueryMsg::Minters { .. } => query_minters(deps), - QueryMsg::ListChannels {} => query_list_channels(deps), + QueryMsg::TokenInfo {} => query::query_token_info(deps.storage), + QueryMsg::TokenConfig {} => query::query_token_config(deps.storage), + QueryMsg::ContractStatus {} => query::query_contract_status(deps.storage), + QueryMsg::ExchangeRate {} => query::query_exchange_rate(deps.storage), + QueryMsg::Minters { .. } => query::query_minters(deps), + QueryMsg::ListChannels {} => query::query_list_channels(deps), QueryMsg::WithPermit { permit, query } => permit_queries(deps, env, permit, query), #[cfg(feature = "gas_tracking")] @@ -406,7 +424,7 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) ))); } - query_balance(deps, account) + query::query_balance(deps, account) } QueryWithPermit::TransferHistory { .. } => { return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)); @@ -419,7 +437,7 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) ))); } - query_transactions(deps, account, page.unwrap_or(0), page_size) + query::query_transactions(deps, account, page.unwrap_or(0), page_size) } QueryWithPermit::Allowance { owner, spender } => { if !permit.check_permission(&TokenPermissions::Allowance) { @@ -436,7 +454,7 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) ))); } - query_allowance(deps, owner, spender) + query::query_allowance(deps, owner, spender) } QueryWithPermit::AllowancesGiven { owner, @@ -459,7 +477,7 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) permit.params.permissions ))); } - query_allowances_given(deps, account, page.unwrap_or(0), page_size) + query::query_allowances_given(deps, account, page.unwrap_or(0), page_size) } QueryWithPermit::AllowancesReceived { spender, @@ -480,9 +498,9 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) permit.params.permissions ))); } - query_allowances_received(deps, account, page.unwrap_or(0), page_size) + query::query_allowances_received(deps, account, page.unwrap_or(0), page_size) } - QueryWithPermit::ChannelInfo { channels, txhash } => query_channel_info( + QueryWithPermit::ChannelInfo { channels, txhash } => query::query_channel_info( deps, env, channels, @@ -496,12 +514,12 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) permit.params.permissions ))); } - query_list_permit_revocations(deps, account.as_str()) + query::query_list_permit_revocations(deps, account.as_str()) }, } } -pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { let (addresses, key) = msg.get_validation_params(deps.api)?; for address in addresses { @@ -509,7 +527,7 @@ pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult query_balance(deps, address), + QueryMsg::Balance { address, .. } => query::query_balance(deps, address), QueryMsg::TransferHistory { .. } => { return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)); } @@ -518,32 +536,32 @@ pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult query_transactions(deps, address, page.unwrap_or(0), page_size), - QueryMsg::Allowance { owner, spender, .. } => query_allowance(deps, owner, spender), + } => query::query_transactions(deps, address, page.unwrap_or(0), page_size), + QueryMsg::Allowance { owner, spender, .. } => query::query_allowance(deps, owner, spender), QueryMsg::AllowancesGiven { owner, page, page_size, .. - } => query_allowances_given(deps, owner, page.unwrap_or(0), page_size), + } => query::query_allowances_given(deps, owner, page.unwrap_or(0), page_size), QueryMsg::AllowancesReceived { spender, page, page_size, .. - } => query_allowances_received(deps, spender, page.unwrap_or(0), page_size), + } => query::query_allowances_received(deps, spender, page.unwrap_or(0), page_size), QueryMsg::ChannelInfo { channels, txhash, viewer, - } => query_channel_info( + } => query::query_channel_info( deps, env, channels, txhash, deps.api.addr_canonicalize(viewer.address.as_str())?, ), - QueryMsg::ListPermitRevocations{viewer, .. } => query_list_permit_revocations(deps, viewer.address.as_str()), + QueryMsg::ListPermitRevocations{viewer, .. } => query::query_list_permit_revocations(deps, viewer.address.as_str()), _ => panic!("This query type does not require authentication"), }; } @@ -554,2716 +572,45 @@ pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult StdResult { - let constants = CONFIG.load(storage)?; - - if constants.deposit_is_enabled || constants.redeem_is_enabled { - let rate: Uint128; - let denom: String; - // if token has more decimals than SCRT, you get magnitudes of SCRT per token - if constants.decimals >= 6 { - rate = Uint128::new(10u128.pow(constants.decimals as u32 - 6)); - denom = "SCRT".to_string(); - // if token has less decimals, you get magnitudes token for SCRT - } else { - rate = Uint128::new(10u128.pow(6 - constants.decimals as u32)); - denom = constants.symbol; - } - return to_binary(&QueryAnswer::ExchangeRate { rate, denom }); - } - to_binary(&QueryAnswer::ExchangeRate { - rate: Uint128::zero(), - denom: String::new(), - }) -} - -fn query_token_info(storage: &dyn Storage) -> StdResult { - let constants = CONFIG.load(storage)?; - - let total_supply = if constants.total_supply_is_public { - Some(Uint128::new(TOTAL_SUPPLY.load(storage)?)) - } else { - None - }; - - to_binary(&QueryAnswer::TokenInfo { - name: constants.name, - symbol: constants.symbol, - decimals: constants.decimals, - total_supply, - }) -} - -fn query_token_config(storage: &dyn Storage) -> StdResult { - let constants = CONFIG.load(storage)?; - - to_binary(&QueryAnswer::TokenConfig { - public_total_supply: constants.total_supply_is_public, - deposit_enabled: constants.deposit_is_enabled, - redeem_enabled: constants.redeem_is_enabled, - mint_enabled: constants.mint_is_enabled, - burn_enabled: constants.burn_is_enabled, - supported_denoms: constants.supported_denoms, - }) -} - -fn query_contract_status(storage: &dyn Storage) -> StdResult { - let contract_status = CONTRACT_STATUS.load(storage)?; - - to_binary(&QueryAnswer::ContractStatus { - status: contract_status, - }) -} - -pub fn query_transactions( - deps: Deps, - account: String, - page: u32, - page_size: u32, -) -> StdResult { - if page_size == 0 { - return Err(StdError::generic_err("invalid page size")); - } - - // Notice that if query_transactions() was called by a viewing-key call, the address of - // 'account' has already been validated. - // The address of 'account' should not be validated if query_transactions() was called by a - // permit call, for compatibility with non-Secret addresses. - let account = Addr::unchecked(account); - let account_raw = deps.api.addr_canonicalize(account.as_str())?; - - let start = page * page_size; - let mut end = start + page_size; // one more than end index - - // first check if there are any transactions in dwb - let dwb = DWB.load(deps.storage)?; - let dwb_index = dwb.recipient_match(&account_raw); - let mut txs_in_dwb = vec![]; - let txs_in_dwb_count = dwb.entries[dwb_index].list_len()?; - if dwb_index > 0 && txs_in_dwb_count > 0 && start < txs_in_dwb_count as u32 { - // skip if start is after buffer entries - let head_node_index = dwb.entries[dwb_index].head_node()?; - - // only look if head node is not null - if head_node_index > 0 { - let head_node = TX_NODES - .add_suffix(&head_node_index.to_be_bytes()) - .load(deps.storage)?; - txs_in_dwb = head_node.to_vec(deps.storage, deps.api)?; - } - } - - //let account_slice = account_raw.as_slice(); - let account_stored_entry = stored_entry(deps.storage, &account_raw)?; - let settled_tx_count = stored_tx_count(deps.storage, &account_stored_entry)?; - let total = txs_in_dwb_count as u32 + settled_tx_count as u32; - if end > total { - end = total; - } - - let mut txs: Vec = vec![]; - - let txs_in_dwb_count = txs_in_dwb_count as u32; - if start < txs_in_dwb_count && end < txs_in_dwb_count { - // option 1, start and end are both in dwb - //println!("OPTION 1"); - txs = txs_in_dwb[start as usize..end as usize].to_vec(); // reverse chronological - } else if start < txs_in_dwb_count && end >= txs_in_dwb_count { - // option 2, start is in dwb and end is in settled txs - // in this case, we do not need to search for txs, just begin at last bundle and move backwards - //println!("OPTION 2"); - txs = txs_in_dwb[start as usize..].to_vec(); // reverse chronological - let mut txs_left = (end - start).saturating_sub(txs.len() as u32); - if let Some(entry) = account_stored_entry { - let tx_bundles_idx_len = entry.history_len()?; - if tx_bundles_idx_len > 0 { - let mut bundle_idx = tx_bundles_idx_len - 1; - loop { - let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; - - // only look if head node is not null - if tx_bundle.head_node > 0 { - let head_node = TX_NODES - .add_suffix(&tx_bundle.head_node.to_be_bytes()) - .load(deps.storage)?; - - let list_len = tx_bundle.list_len as u32; - if txs_left <= list_len { - txs.extend_from_slice( - &head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize], - ); - break; - } - txs.extend(head_node.to_vec(deps.storage, deps.api)?); - txs_left = txs_left.saturating_sub(list_len); - } - if bundle_idx > 0 { - bundle_idx -= 1; - } else { - break; - } - } - } - } - } else if start >= txs_in_dwb_count { - // option 3, start is not in dwb - // in this case, search for where the beginning bundle is using binary search - - // bundle tx offsets are chronological, but we need reverse chronological - // so get the settled start index as if order is reversed - //println!("OPTION 3"); - let settled_start = settled_tx_count - .saturating_sub(start - txs_in_dwb_count) - .saturating_sub(1); - - if let Some((bundle_idx, tx_bundle, start_at)) = - find_start_bundle(deps.storage, &account_raw, settled_start)? - { - let mut txs_left = end - start; - let list_len = tx_bundle.list_len as u32; - if start_at + txs_left <= list_len { - // only look if head node is not null - if tx_bundle.head_node > 0 { - let head_node = TX_NODES - .add_suffix(&tx_bundle.head_node.to_be_bytes()) - .load(deps.storage)?; - // this first bundle has all the txs we need - txs = head_node.to_vec(deps.storage, deps.api)? - [start_at as usize..(start_at + txs_left) as usize] - .to_vec(); - } - } else { - // only look if head node is not null - if tx_bundle.head_node > 0 { - let head_node = TX_NODES - .add_suffix(&tx_bundle.head_node.to_be_bytes()) - .load(deps.storage)?; - // get the rest of the txs in this bundle and then go back through history - txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); - txs_left = txs_left.saturating_sub(list_len - start_at); - } - - if bundle_idx > 0 && txs_left > 0 { - // get the next earlier bundle - let mut bundle_idx = bundle_idx - 1; - if let Some(entry) = account_stored_entry { - loop { - let tx_bundle = - entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; - // only look if head node is not null - if tx_bundle.head_node > 0 { - let head_node = TX_NODES - .add_suffix(&tx_bundle.head_node.to_be_bytes()) - .load(deps.storage)?; - let list_len = tx_bundle.list_len as u32; - if txs_left <= list_len { - txs.extend_from_slice( - &head_node.to_vec(deps.storage, deps.api)? - [0..txs_left as usize], - ); - break; - } - txs.extend(head_node.to_vec(deps.storage, deps.api)?); - txs_left = txs_left.saturating_sub(list_len); - } - if bundle_idx > 0 { - bundle_idx -= 1; - } else { - break; - } - } - } - } - } - } - } - - // deterministically obfuscate ids so they are not serial to prevent metadata leak - let internal_secret = INTERNAL_SECRET_RELAXED.load(deps.storage)?; - let internal_secret_u64: u64 = u64::from_be_bytes(internal_secret[..8].try_into().unwrap()); - let txs = txs - .iter() - .map(|tx| { - // PRNG(PRNG(serial_id) ^ secret) - let mut rng = ChaChaRng::seed_from_u64(tx.id); - let serial_id_rand = rng.next_u64(); - let new_seed = serial_id_rand ^ internal_secret_u64; - let mut rng = ChaChaRng::seed_from_u64(new_seed); - let new_id = rng.next_u64() >> (64 - 53); - Tx { - id: new_id, - action: tx.action.clone(), - coins: tx.coins.clone(), - memo: tx.memo.clone(), - block_height: tx.block_height, - block_time: tx.block_time, - } - }) - .collect(); - - let result = QueryAnswer::TransactionHistory { - txs, - total: Some(total as u64), - }; - to_binary(&result) -} - -pub fn query_balance(deps: Deps, account: String) -> StdResult { - // Notice that if query_balance() was called by a viewing key call, the address of 'account' - // has already been validated. - // The address of 'account' should not be validated if query_balance() was called by a permit - // call, for compatibility with non-Secret addresses. - let account = Addr::unchecked(account); - let account = deps.api.addr_canonicalize(account.as_str())?; - - let mut amount = stored_balance(deps.storage, &account)?; - let dwb = DWB.load(deps.storage)?; - let dwb_index = dwb.recipient_match(&account); - if dwb_index > 0 { - amount = amount.saturating_add(dwb.entries[dwb_index].amount()? as u128); - } - let amount = Uint128::new(amount); - let response = QueryAnswer::Balance { amount }; - to_binary(&response) -} - -fn query_minters(deps: Deps) -> StdResult { - let minters = MintersStore::load(deps.storage)?; - - let response = QueryAnswer::Minters { minters }; - to_binary(&response) -} - -// ***************** -// SNIP-52 query functions -// ***************** - -/// -/// ListChannels query -/// -/// Public query to list all notification channels. -/// -fn query_list_channels(deps: Deps) -> StdResult { - let channels: Vec = CHANNELS - .iter(deps.storage)? - .map(|channel| channel.unwrap()) - .collect(); - to_binary(&QueryAnswer::ListChannels { channels }) -} - -/// -/// ChannelInfo query -/// -/// Authenticated query allows clients to obtain the seed, -/// and Notification ID of an event for a specific tx_hash, for a specific channel. -/// -fn query_channel_info( - deps: Deps, - env: Env, - channels: Vec, - txhash: Option, - sender_raw: CanonicalAddr, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - let seed = get_seed(&sender_raw, secret)?; - let mut channels_data = vec![]; - for channel in channels { - let answer_id; - if let Some(tx_hash) = &txhash { - answer_id = Some(notification_id(&seed, &channel, tx_hash)?); - } else { - answer_id = None; - } - match channel.as_str() { - RecvdNotification::CHANNEL_ID => { - let channel_info_data = ChannelInfoData { - mode: "txhash".to_string(), - channel, - answer_id, - parameters: None, - data: None, - next_id: None, - counter: None, - cddl: Some(RecvdNotification::CDDL_SCHEMA.to_string()), - }; - channels_data.push(channel_info_data); - } - SpentNotification::CHANNEL_ID => { - let channel_info_data = ChannelInfoData { - mode: "txhash".to_string(), - channel, - answer_id, - parameters: None, - data: None, - next_id: None, - counter: None, - cddl: Some(SpentNotification::CDDL_SCHEMA.to_string()), - }; - channels_data.push(channel_info_data); - } - AllowanceNotification::CHANNEL_ID => { - let channel_info_data = ChannelInfoData { - mode: "txhash".to_string(), - channel, - answer_id, - parameters: None, - data: None, - next_id: None, - counter: None, - cddl: Some(AllowanceNotification::CDDL_SCHEMA.to_string()), - }; - channels_data.push(channel_info_data); - } - MultiRecvdNotification::CHANNEL_ID => { - let channel_info_data = ChannelInfoData { - mode: "bloom".to_string(), - channel, - answer_id, - parameters: Some(BloomParameters { - m: MultiRecvdNotification::BLOOM_M, - k: MultiRecvdNotification::BLOOM_K, - h: "sha256".to_string(), - }), - data: Some(Descriptor { - r#type: format!("packet[{}]", MultiRecvdNotification::BLOOM_N), - version: "1".to_string(), - packet_size: MultiRecvdNotification::PACKET_SIZE as u32, - data: StructDescriptor { - r#type: "struct".to_string(), - label: "transfer".to_string(), - members: vec![ - FlatDescriptor { - r#type: "uint64".to_string(), - label: "flagsAndAmount".to_string(), - description: Some( - "Bit field of [0]: non-empty memo; [2]: sender is owner; [2..]: uint62 transfer amount in base denomination".to_string(), - ), - }, - FlatDescriptor { - r#type: "bytes8".to_string(), - label: "ownerId".to_string(), - description: Some( - "The last 8 bytes of the owner's canonical address".to_string(), - ), - }, - ], - }, - }), - counter: None, - next_id: None, - cddl: None, - }; - channels_data.push(channel_info_data); - } - MultiSpentNotification::CHANNEL_ID => { - let channel_info_data = ChannelInfoData { - mode: "bloom".to_string(), - channel, - answer_id, - parameters: Some(BloomParameters { - m: MultiSpentNotification::BLOOM_M, - k: MultiSpentNotification::BLOOM_K, - h: "sha256".to_string(), - }), - data: Some(Descriptor { - r#type: format!("packet[{}]", MultiSpentNotification::BLOOM_N), - version: "1".to_string(), - packet_size: MultiSpentNotification::PACKET_SIZE as u32, - data: StructDescriptor { - r#type: "struct".to_string(), - label: "transfer".to_string(), - members: vec![ - FlatDescriptor { - r#type: "uint64".to_string(), - label: "flagsAndAmount".to_string(), - description: Some( - "Bit field of [0]: non-empty memo; [1]: reserved; [2..] uint62 transfer amount in base denomination".to_string(), - ), - }, - FlatDescriptor { - r#type: "bytes8".to_string(), - label: "recipientId".to_string(), - description: Some( - "The last 8 bytes of the recipient's canonical address".to_string(), - ), - }, - FlatDescriptor { - r#type: "uint64".to_string(), - label: "balance".to_string(), - description: Some( - "Spender's new balance after the transfer".to_string(), - ), - }, - ], - }, - }), - counter: None, - next_id: None, - cddl: None, - }; - channels_data.push(channel_info_data); - } - _ => { - return Err(StdError::generic_err(format!( - "`{}` channel is undefined", - channel - ))); - } - } - } - - to_binary(&QueryAnswer::ChannelInfo { - as_of_block: Uint64::from(env.block.height), - channels: channels_data, - seed, - }) -} - -// ***************** -// End SNIP-52 query functions -// ***************** - -// execute functions - -fn change_admin(deps: DepsMut, info: MessageInfo, address: String) -> StdResult { - let address = deps.api.addr_validate(address.as_str())?; - - let mut constants = CONFIG.load(deps.storage)?; - check_if_admin(&constants.admin, &info.sender)?; - - constants.admin = address; - CONFIG.save(deps.storage, &constants)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ChangeAdmin { status: Success })?)) -} - -fn add_supported_denoms( - deps: DepsMut, - info: MessageInfo, - denoms: Vec, -) -> StdResult { - let mut config = CONFIG.load(deps.storage)?; - - check_if_admin(&config.admin, &info.sender)?; - if !config.can_modify_denoms { - return Err(StdError::generic_err( - "Cannot modify denoms for this contract", - )); - } - - for denom in denoms.iter() { - if !config.supported_denoms.contains(denom) { - config.supported_denoms.push(denom.clone()); - } - } - - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::AddSupportedDenoms { - status: Success, - })?), - ) -} - -fn remove_supported_denoms( - deps: DepsMut, - info: MessageInfo, - denoms: Vec, -) -> StdResult { - let mut config = CONFIG.load(deps.storage)?; - - check_if_admin(&config.admin, &info.sender)?; - if !config.can_modify_denoms { - return Err(StdError::generic_err( - "Cannot modify denoms for this contract", - )); - } - - for denom in denoms.iter() { - config.supported_denoms.retain(|x| x != denom); - } - - CONFIG.save(deps.storage, &config)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RemoveSupportedDenoms { - status: Success, - })?), - ) -} - -// SNIP-52 functions - -fn set_notification_status( - deps: DepsMut, - info: MessageInfo, - enabled: bool, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - check_if_admin(&config.admin, &info.sender)?; - - NOTIFICATIONS_ENABLED.save(deps.storage, &enabled)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetNotificationStatus { - status: Success, - })?), - ) -} - -// end SNIP-52 functions - -#[allow(clippy::too_many_arguments)] -fn try_mint_impl( - deps: &mut DepsMut, - rng: &mut ContractPrng, - minter: Addr, - recipient: Addr, - amount: Uint128, - denom: String, - memo: Option, - block: &cosmwasm_std::BlockInfo, - #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<()> { - let raw_amount = amount.u128(); - let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; - let raw_minter = deps.api.addr_canonicalize(minter.as_str())?; - - perform_mint( - deps.storage, - rng, - &raw_minter, - &raw_recipient, - raw_amount, - denom, - memo, - block, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn try_mint( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - rng: &mut ContractPrng, - recipient: String, - amount: Uint128, - memo: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let recipient = deps.api.addr_validate(recipient.as_str())?; - - let constants = CONFIG.load(deps.storage)?; - - if !constants.mint_is_enabled { - return Err(StdError::generic_err( - "Mint functionality is not enabled for this token.", - )); - } - - let minters = MintersStore::load(deps.storage)?; - if !minters.contains(&info.sender) { - return Err(StdError::generic_err( - "Minting is allowed to minter accounts only", - )); - } - - let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; - let minted_amount = safe_add(&mut total_supply, amount.u128()); - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - - #[cfg(feature = "gas_tracking")] - let mut tracker: GasTracker = GasTracker::new(deps.api); - - let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); - - // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency - try_mint_impl( - &mut deps, - rng, - info.sender, - recipient.clone(), - Uint128::new(minted_amount), - constants.symbol, - memo, - &env.block, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let received_notification = Notification::new( - recipient, - RecvdNotification { - amount: minted_amount, - sender: None, - memo_len, - sender_is_owner: true, - }, - ) - .to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ); - } - - #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); - - #[cfg(not(feature = "gas_tracking"))] - Ok(resp) -} - -fn try_batch_mint( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - rng: &mut ContractPrng, - actions: Vec, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let constants = CONFIG.load(deps.storage)?; - - if !constants.mint_is_enabled { - return Err(StdError::generic_err( - "Mint functionality is not enabled for this token.", - )); - } - - let minters = MintersStore::load(deps.storage)?; - if !minters.contains(&info.sender) { - return Err(StdError::generic_err( - "Minting is allowed to minter accounts only", - )); - } - - let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; - - let mut notifications = vec![]; - // Quick loop to check that the total of amounts is valid - for action in actions { - let actual_amount = safe_add(&mut total_supply, action.amount.u128()); - - let recipient = deps.api.addr_validate(action.recipient.as_str())?; - - #[cfg(feature = "gas_tracking")] - let mut tracker: GasTracker = GasTracker::new(deps.api); - - notifications.push(Notification::new ( - recipient.clone(), - RecvdNotification { - amount: actual_amount, - sender: None, - memo_len: action.memo.as_ref().map(|s| s.len()).unwrap_or_default(), - sender_is_owner: true, - }, - )); - - try_mint_impl( - &mut deps, - rng, - info.sender.clone(), - recipient, - Uint128::new(actual_amount), - constants.symbol.clone(), - action.memo, - &env.block, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - } - - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - resp = render_group_notification( - deps.api, - MultiRecvdNotification(notifications), - &env.transaction.unwrap().hash, - env.block.random.unwrap(), - secret, - resp, - )?; - } - - Ok(resp) -} - -pub fn try_set_key(deps: DepsMut, info: MessageInfo, key: String) -> StdResult { - ViewingKey::set(deps.storage, info.sender.as_str(), key.as_str()); - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetViewingKey { - status: Success, - })?), - ) -} - -pub fn try_create_key( - deps: DepsMut, - env: Env, - info: MessageInfo, - entropy: Option, - rng: &mut ContractPrng, -) -> StdResult { - let entropy = [entropy.unwrap_or_default().as_bytes(), &rng.rand_bytes()].concat(); +// pub fn migrate( +// _deps: DepsMut, +// _env: Env, +// _msg: MigrateMsg, +// ) -> StdResult { +// Ok(MigrateResponse::default()) +// Ok(MigrateResponse::default()) +// } - let key = ViewingKey::create( - deps.storage, - &info, - &env, - info.sender.as_str(), - &entropy, - ); +// helper functions - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key })?)) +fn is_valid_name(name: &str) -> bool { + let len = name.len(); + (3..=30).contains(&len) } -fn set_contract_status( - deps: DepsMut, - info: MessageInfo, - status_level: ContractStatusLevel, -) -> StdResult { - let constants = CONFIG.load(deps.storage)?; - check_if_admin(&constants.admin, &info.sender)?; - - CONTRACT_STATUS.save(deps.storage, &status_level)?; +fn is_valid_symbol(symbol: &str) -> bool { + let len = symbol.len(); + let len_is_valid = (3..=20).contains(&len); - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::SetContractStatus { - status: Success, - })?), - ) + len_is_valid && symbol.bytes().all(|byte| byte.is_ascii_alphabetic()) } -pub fn query_allowance(deps: Deps, owner: String, spender: String) -> StdResult { - // Notice that if query_allowance() was called by a viewing-key call, the addresses of 'owner' - // and 'spender' have already been validated. - // The addresses of 'owner' and 'spender' should not be validated if query_allowance() was - // called by a permit call, for compatibility with non-Secret addresses. - let owner = Addr::unchecked(owner); - let spender = Addr::unchecked(spender); - - let allowance = AllowancesStore::load(deps.storage, &owner, &spender); - - let response = QueryAnswer::Allowance { - owner, - spender, - allowance: Uint128::new(allowance.amount), - expiration: allowance.expiration, - }; - to_binary(&response) -} - -pub fn query_allowances_given( - deps: Deps, - owner: String, - page: u32, - page_size: u32, -) -> StdResult { - // Notice that if query_all_allowances_given() was called by a viewing-key call, - // the address of 'owner' has already been validated. - // The addresses of 'owner' should not be validated if query_all_allowances_given() was - // called by a permit call, for compatibility with non-Secret addresses. - let owner = Addr::unchecked(owner); - - let all_allowances = - AllowancesStore::all_allowances(deps.storage, &owner, page, page_size).unwrap_or(vec![]); - - let allowances_result = all_allowances - .into_iter() - .map(|(spender, allowance)| AllowanceGivenResult { - spender, - allowance: Uint128::from(allowance.amount), - expiration: allowance.expiration, - }) - .collect(); - - let response = QueryAnswer::AllowancesGiven { - owner: owner.clone(), - allowances: allowances_result, - count: AllowancesStore::num_allowances(deps.storage, &owner), - }; - to_binary(&response) -} - -pub fn query_allowances_received( - deps: Deps, - spender: String, - page: u32, - page_size: u32, -) -> StdResult { - // Notice that if query_all_allowances_received() was called by a viewing-key call, - // the address of 'spender' has already been validated. - // The addresses of 'spender' should not be validated if query_all_allowances_received() was - // called by a permit call, for compatibility with non-Secret addresses. - let spender = Addr::unchecked(spender); - - let all_allowed = - AllowancesStore::all_allowed(deps.storage, &spender, page, page_size).unwrap_or(vec![]); - - let allowances = all_allowed - .into_iter() - .map(|(owner, allowance)| AllowanceReceivedResult { - owner, - allowance: Uint128::from(allowance.amount), - expiration: allowance.expiration, - }) - .collect(); - - let response = QueryAnswer::AllowancesReceived { - spender: spender.clone(), - allowances, - count: AllowancesStore::num_allowed(deps.storage, &spender), - }; - to_binary(&response) -} - -fn try_deposit( - deps: DepsMut, - env: Env, - info: MessageInfo, - rng: &mut ContractPrng, -) -> StdResult { - let constants = CONFIG.load(deps.storage)?; - - let mut amount = Uint128::zero(); - - for coin in &info.funds { - if constants.supported_denoms.contains(&coin.denom) { - amount += coin.amount - } else { - return Err(StdError::generic_err(format!( - "Tried to deposit an unsupported coin {}", - coin.denom - ))); - } - } - - if amount.is_zero() { - return Err(StdError::generic_err("No funds were sent to be deposited")); - } - - let mut raw_amount = amount.u128(); - - if !constants.deposit_is_enabled { - return Err(StdError::generic_err( - "Deposit functionality is not enabled.", - )); - } - - let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; - raw_amount = safe_add(&mut total_supply, raw_amount); - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - - let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; - - #[cfg(feature = "gas_tracking")] - let mut tracker: GasTracker = GasTracker::new(deps.api); - - perform_deposit( - deps.storage, - rng, - &sender_address, - raw_amount, - "uscrt".to_string(), - &env.block, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?); - - #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); - - #[cfg(not(feature = "gas_tracking"))] - Ok(resp) -} - -fn try_redeem( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, - denom: Option, -) -> StdResult { - let constants = CONFIG.load(deps.storage)?; - if !constants.redeem_is_enabled { - return Err(StdError::generic_err( - "Redeem functionality is not enabled for this token.", - )); - } - - // if denom is none and there is only 1 supported denom then we don't need to check anything - let withdraw_denom = if denom.is_none() && constants.supported_denoms.len() == 1 { - constants.supported_denoms.first().unwrap().clone() - // if denom is specified make sure it's on the list before trying to withdraw with it - } else if denom.is_some() && constants.supported_denoms.contains(denom.as_ref().unwrap()) { - denom.unwrap() - // error handling - } else if denom.is_none() { - return Err(StdError::generic_err( - "Tried to redeem without specifying denom, but multiple coins are supported", - )); - } else { - return Err(StdError::generic_err( - "Tried to redeem for an unsupported coin", - )); - }; - - let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; - let amount_raw = amount.u128(); - - let tx_id = store_redeem_action(deps.storage, amount.u128(), constants.symbol, &env.block)?; - - // load delayed write buffer - let mut dwb = DWB.load(deps.storage)?; - - #[cfg(feature = "gas_tracking")] - let mut tracker = GasTracker::new(deps.api); - - // settle the signer's account in buffer - dwb.settle_sender_or_owner_account( - deps.storage, - &sender_address, - tx_id, - amount_raw, - "redeem", - false, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - DWB.save(deps.storage, &dwb)?; - - let total_supply = TOTAL_SUPPLY.load(deps.storage)?; - if let Some(total_supply) = total_supply.checked_sub(amount_raw) { - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - } else { - return Err(StdError::generic_err( - "You are trying to redeem more tokens than what is available in the total supply", - )); - } - - let token_reserve = deps - .querier - .query_balance(&env.contract.address, &withdraw_denom)? - .amount; - if amount > token_reserve { - return Err(StdError::generic_err(format!( - "You are trying to redeem for more {withdraw_denom} than the contract has in its reserve", - ))); - } - - let withdrawal_coins: Vec = vec![Coin { - denom: withdraw_denom, - amount, - }]; - - let message = CosmosMsg::Bank(BankMsg::Send { - to_address: info.sender.clone().into_string(), - amount: withdrawal_coins, - }); - let data = to_binary(&ExecuteAnswer::Redeem { status: Success })?; - let res = Response::new().add_message(message).set_data(data); - Ok(res) -} - -#[allow(clippy::too_many_arguments)] -fn try_transfer_impl( - deps: &mut DepsMut, - rng: &mut ContractPrng, - owner: &Addr, - recipient: &Addr, - amount: Uint128, - denom: String, - memo: Option, - block: &cosmwasm_std::BlockInfo, - #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<(Notification, Notification)> { - // canonicalize owner and recipient addresses - let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; - let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; - - // memo length - let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); - - // create the tokens received notification for recipient - let received_notification = Notification::new( - recipient.clone(), - RecvdNotification { - amount: amount.u128(), - sender: Some(owner.clone()), - memo_len, - sender_is_owner: true, - } - ); - - // perform the transfer from owner to recipient - let owner_balance = perform_transfer( - deps.storage, - rng, - &raw_owner, - &raw_recipient, - &raw_owner, - amount.u128(), - denom, - memo.clone(), - block, - false, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - - // create the tokens spent notification for owner - let spent_notification = Notification::new ( - owner.clone(), - SpentNotification { - amount: amount.u128(), - actions: 1, - recipient: Some(recipient.clone()), - balance: owner_balance, - memo_len, - } - ); - - Ok((received_notification, spent_notification)) -} - -#[allow(clippy::too_many_arguments)] -fn try_transfer( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - rng: &mut ContractPrng, - recipient: String, - amount: Uint128, - memo: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let recipient: Addr = deps.api.addr_validate(recipient.as_str())?; - - let symbol = CONFIG.load(deps.storage)?.symbol; - - // make sure the sender is not accidentally sending tokens to the contract address - if recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); - } - - #[cfg(feature = "gas_tracking")] - let mut tracker: GasTracker = GasTracker::new(deps.api); - - // perform the transfer - let ( - received_notification, - spent_notification - ) = try_transfer_impl( - &mut deps, - rng, - &info.sender, - &recipient, - amount, - symbol, - memo, - &env.block, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - #[cfg(feature = "gas_tracking")] - let mut group1 = tracker.group("try_transfer.rest"); - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - // render the tokens received notification - let received_notification = received_notification.to_txhash_notification( - deps.api, - &env, - secret, - None, - )?; - - // render the tokens spent notification - let spent_notification = spent_notification.to_txhash_notification( - deps.api, - &env, - secret, - None, - )?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); - } - - #[cfg(feature = "gas_tracking")] - group1.log("rest"); - - #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); - - #[cfg(not(feature = "gas_tracking"))] - Ok(resp) -} - -fn try_batch_transfer( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - rng: &mut ContractPrng, - actions: Vec, -) -> StdResult { - let num_actions = actions.len(); - if num_actions == 0 { - return Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?) - ); - } - - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let symbol = CONFIG.load(deps.storage)?.symbol; - - let mut total_memo_len = 0; - - #[cfg(feature = "gas_tracking")] - let mut tracker: GasTracker = GasTracker::new(deps.api); - - let mut notifications = vec![]; - for action in actions { - let recipient = deps.api.addr_validate(action.recipient.as_str())?; - - // make sure the sender is not accidentally sending tokens to the contract address - if recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); - } - - total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); - - let ( - received_notification, - spent_notification - ) = try_transfer_impl( - &mut deps, - rng, - &info.sender, - &recipient, - action.amount, - symbol.clone(), - action.memo, - &env.block, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - notifications.push((received_notification, spent_notification)); - } - - let ( - received_notifications, - spent_notifications - ): ( - Vec>, - Vec>, - ) = notifications.into_iter().unzip(); - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - resp = render_group_notification( - deps.api, - MultiRecvdNotification(received_notifications), - &env.transaction.clone().unwrap().hash, - env.block.random.clone().unwrap(), - secret, - resp, - )?; - - let total_amount_spent = spent_notifications - .iter() - .fold(0u128, |acc, notification| acc.saturating_add(notification.data.amount)); - - let spent_notification = Notification::new ( - info.sender, - SpentNotification { - amount: total_amount_spent, - actions: num_actions as u32, - recipient: spent_notifications[0].data.recipient.clone(), - balance: spent_notifications.last().unwrap().data.balance, - memo_len: total_memo_len, - } - ) - .to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); - } - - #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); - - #[cfg(not(feature = "gas_tracking"))] - Ok(resp) -} - -#[allow(clippy::too_many_arguments)] -fn try_add_receiver_api_callback( - storage: &dyn Storage, - messages: &mut Vec, - recipient: Addr, - recipient_code_hash: Option, - msg: Option, - sender: Addr, - from: Addr, - amount: Uint128, - memo: Option, -) -> StdResult<()> { - if let Some(receiver_hash) = recipient_code_hash { - let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); - let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; - - messages.push(callback_msg); - return Ok(()); - } - - let receiver_hash = ReceiverHashStore::may_load(storage, &recipient)?; - if let Some(receiver_hash) = receiver_hash { - let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); - let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; - - messages.push(callback_msg); - } - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn try_send_impl( - deps: &mut DepsMut, - rng: &mut ContractPrng, - messages: &mut Vec, - sender: Addr, - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - denom: String, - memo: Option, - msg: Option, - block: &cosmwasm_std::BlockInfo, - #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<(Notification, Notification)> { - let ( - received_notification, - spent_notification - ) = try_transfer_impl( - deps, - rng, - &sender, - &recipient, - amount, - denom, - memo.clone(), - block, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - - try_add_receiver_api_callback( - deps.storage, - messages, - recipient, - recipient_code_hash, - msg, - sender.clone(), - sender, - amount, - memo, - )?; - - Ok((received_notification, spent_notification)) -} - -#[allow(clippy::too_many_arguments)] -fn try_send( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - rng: &mut ContractPrng, - recipient: String, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let recipient = deps.api.addr_validate(recipient.as_str())?; - - let mut messages = vec![]; - let symbol = CONFIG.load(deps.storage)?.symbol; - - // make sure the sender is not accidentally sending tokens to the contract address - if recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); - } - - #[cfg(feature = "gas_tracking")] - let mut tracker: GasTracker = GasTracker::new(deps.api); - - let ( - received_notification, - spent_notification - ) = try_send_impl( - &mut deps, - rng, - &mut messages, - info.sender, - recipient, - recipient_code_hash, - amount, - symbol, - memo, - msg, - &env.block, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - let mut resp = Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let received_notification = received_notification.to_txhash_notification(deps.api, &env, secret, None)?; - let spent_notification = spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); - } - - #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); - - #[cfg(not(feature = "gas_tracking"))] - Ok(resp) -} - -fn try_batch_send( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - rng: &mut ContractPrng, - actions: Vec, -) -> StdResult { - let num_actions = actions.len(); - if num_actions == 0 { - return Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) - ); - } - - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let mut messages = vec![]; - - let mut notifications = vec![]; - let num_actions: usize = actions.len(); - - let symbol = CONFIG.load(deps.storage)?.symbol; - - let mut total_memo_len = 0; - - #[cfg(feature = "gas_tracking")] - let mut tracker: GasTracker = GasTracker::new(deps.api); - - for action in actions { - let recipient = deps.api.addr_validate(action.recipient.as_str())?; - - // make sure the sender is not accidentally sending tokens to the contract address - if recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); - } - - total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); - - let ( - received_notification, - spent_notification - ) = try_send_impl( - &mut deps, - rng, - &mut messages, - info.sender.clone(), - recipient, - action.recipient_code_hash, - action.amount, - symbol.clone(), - action.memo, - action.msg, - &env.block, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - notifications.push((received_notification, spent_notification)); - } - - let mut resp = Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let (received_notifications, spent_notifications): ( - Vec>, - Vec>, - ) = notifications.into_iter().unzip(); - - resp = render_group_notification( - deps.api, - MultiRecvdNotification(received_notifications), - &env.transaction.clone().unwrap().hash, - env.block.random.clone().unwrap(), - secret, - resp, - )?; - - let total_amount_spent = spent_notifications - .iter() - .fold(0u128, |acc, notification| acc + notification.data.amount); - - let spent_notification = Notification::new ( - info.sender, - SpentNotification { - amount: total_amount_spent, - actions: num_actions as u32, - recipient: spent_notifications[0].data.recipient.clone(), - balance: spent_notifications.last().unwrap().data.balance, - memo_len: total_memo_len, - } - ) - .to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); - } - - Ok(resp) -} - -fn try_register_receive( - deps: DepsMut, - info: MessageInfo, - code_hash: String, -) -> StdResult { - ReceiverHashStore::save(deps.storage, &info.sender, code_hash)?; - - let data = to_binary(&ExecuteAnswer::RegisterReceive { status: Success })?; - Ok(Response::new() - .add_attribute("register_status", "success") - .set_data(data)) -} - -fn insufficient_allowance(allowance: u128, required: u128) -> StdError { - StdError::generic_err(format!( - "insufficient allowance: allowance={allowance}, required={required}", - )) -} - -fn use_allowance( - storage: &mut dyn Storage, - env: &Env, - owner: &Addr, - spender: &Addr, - amount: u128, -) -> StdResult<()> { - let mut allowance = AllowancesStore::load(storage, owner, spender); - - if allowance.is_expired_at(&env.block) || allowance.amount == 0 { - return Err(insufficient_allowance(0, amount)); - } - if let Some(new_allowance) = allowance.amount.checked_sub(amount) { - allowance.amount = new_allowance; - } else { - return Err(insufficient_allowance(allowance.amount, amount)); - } - - AllowancesStore::save(storage, owner, spender, &allowance)?; - - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn try_transfer_from_impl( - deps: &mut DepsMut, - rng: &mut ContractPrng, - env: &Env, - spender: &Addr, - owner: &Addr, - recipient: &Addr, - amount: Uint128, - denom: String, - memo: Option, -) -> StdResult<(Notification, Notification)> { - let raw_amount = amount.u128(); - let raw_spender = deps.api.addr_canonicalize(spender.as_str())?; - let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; - let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; - - use_allowance(deps.storage, env, owner, spender, raw_amount)?; - - // make sure the sender is not accidentally sending tokens to the contract address - if *recipient == env.contract.address { - return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); - } - - #[cfg(feature = "gas_tracking")] - let mut tracker: GasTracker = GasTracker::new(deps.api); - - let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); - - // create tokens received notification for recipient - let received_notification = Notification::new( - recipient.clone(), - RecvdNotification { - amount: amount.u128(), - sender: Some(owner.clone()), - memo_len, - sender_is_owner: spender == owner, - } - ); - - // perform the transfer from owner to recipient - let owner_balance = perform_transfer( - deps.storage, - rng, - &raw_owner, - &raw_recipient, - &raw_spender, - raw_amount, - denom, - memo, - &env.block, - true, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - // create tokens spent notification for owner - let spent_notification = Notification::new ( - owner.clone(), - SpentNotification { - amount: amount.u128(), - actions: 1, - recipient: Some(recipient.clone()), - balance: owner_balance, - memo_len, - } - ); - - Ok((received_notification, spent_notification)) -} - -#[allow(clippy::too_many_arguments)] -fn try_transfer_from( - mut deps: DepsMut, - env: &Env, - info: MessageInfo, - rng: &mut ContractPrng, - owner: String, - recipient: String, - amount: Uint128, - memo: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let owner = deps.api.addr_validate(owner.as_str())?; - let recipient = deps.api.addr_validate(recipient.as_str())?; - let symbol = CONFIG.load(deps.storage)?.symbol; - let ( - received_notification, - spent_notification - ) = try_transfer_from_impl( - &mut deps, - rng, - env, - &info.sender, - &owner, - &recipient, - amount, - symbol, - memo, - )?; - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let received_notification = received_notification.to_txhash_notification( - deps.api, - &env, - secret, - None, - )?; - - let spent_notification = spent_notification.to_txhash_notification( - deps.api, - &env, - secret, - None - )?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); - } - - Ok(resp) -} - -fn try_batch_transfer_from( - mut deps: DepsMut, - env: &Env, - info: MessageInfo, - rng: &mut ContractPrng, - actions: Vec, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let mut notifications = vec![]; - - let symbol = CONFIG.load(deps.storage)?.symbol; - for action in actions { - let owner = deps.api.addr_validate(action.owner.as_str())?; - let recipient = deps.api.addr_validate(action.recipient.as_str())?; - - let ( - received_notification, - spent_notification - ) = try_transfer_from_impl( - &mut deps, - rng, - env, - &info.sender, - &owner, - &recipient, - action.amount, - symbol.clone(), - action.memo, - )?; - - notifications.push((received_notification, spent_notification)); - } - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchTransferFrom {status: Success})?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let (received_notifications, spent_notifications): ( - Vec>, - Vec>, - ) = notifications.into_iter().unzip(); - - let tx_hash = env.transaction.clone().unwrap().hash; - - resp = render_group_notification( - deps.api, - MultiRecvdNotification(received_notifications), - &tx_hash, - env.block.random.clone().unwrap(), - secret, - resp, - )?; - - resp = render_group_notification( - deps.api, - MultiSpentNotification(spent_notifications), - &tx_hash, - env.block.random.clone().unwrap(), - secret, - resp, - )?; - } - - Ok(resp) -} - -#[allow(clippy::too_many_arguments)] -fn try_send_from_impl( - deps: &mut DepsMut, - env: Env, - info: &MessageInfo, - rng: &mut ContractPrng, - messages: &mut Vec, - owner: Addr, - recipient: Addr, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, -) -> StdResult<(Notification, Notification)> { - let spender = info.sender.clone(); - let symbol = CONFIG.load(deps.storage)?.symbol; - let ( - received_notification, - spent_notification - ) = try_transfer_from_impl( - deps, - rng, - &env, - &spender, - &owner, - &recipient, - amount, - symbol, - memo.clone(), - )?; - - try_add_receiver_api_callback( - deps.storage, - messages, - recipient, - recipient_code_hash, - msg, - info.sender.clone(), - owner, - amount, - memo, - )?; - - Ok((received_notification, spent_notification)) -} - -#[allow(clippy::too_many_arguments)] -fn try_send_from( - mut deps: DepsMut, - env: Env, - info: &MessageInfo, - rng: &mut ContractPrng, - owner: String, - recipient: String, - recipient_code_hash: Option, - amount: Uint128, - memo: Option, - msg: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let owner = deps.api.addr_validate(owner.as_str())?; - let recipient = deps.api.addr_validate(recipient.as_str())?; - let mut messages = vec![]; - let ( - received_notification, - spent_notification - ) = try_send_from_impl( - &mut deps, - env.clone(), - info, - rng, - &mut messages, - owner, - recipient, - recipient_code_hash, - amount, - memo, - msg, - )?; - - let mut resp = Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let received_notification = received_notification.to_txhash_notification(deps.api, &env, secret, None,)?; - let spent_notification = spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ) - } - - Ok(resp) -} - -fn try_batch_send_from( - mut deps: DepsMut, - env: Env, - info: &MessageInfo, - rng: &mut ContractPrng, - actions: Vec, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let mut messages = vec![]; - let mut notifications = vec![]; - - for action in actions { - let owner = deps.api.addr_validate(action.owner.as_str())?; - let recipient = deps.api.addr_validate(action.recipient.as_str())?; - let ( - received_notification, - spent_notification - ) = try_send_from_impl( - &mut deps, - env.clone(), - info, - rng, - &mut messages, - owner, - recipient, - action.recipient_code_hash, - action.amount, - action.memo, - action.msg, - )?; - notifications.push((received_notification, spent_notification)); - } - - let mut resp = Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::BatchSendFrom { - status: Success, - })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let (received_notifications, spent_notifications): ( - Vec>, - Vec>, - ) = notifications.into_iter().unzip(); - - let tx_hash = env.transaction.clone().unwrap().hash; - - resp = render_group_notification( - deps.api, - MultiRecvdNotification(received_notifications), - &tx_hash, - env.block.random.clone().unwrap(), - secret, - resp, - )?; - - resp = render_group_notification( - deps.api, - MultiSpentNotification(spent_notifications), - &tx_hash, - env.block.random.clone().unwrap(), - secret, - resp, - )?; - } - - Ok(resp) -} - -#[allow(clippy::too_many_arguments)] -fn try_burn_from( - deps: DepsMut, - env: &Env, - info: MessageInfo, - owner: String, - amount: Uint128, - memo: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let owner = deps.api.addr_validate(owner.as_str())?; - let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; - let constants = CONFIG.load(deps.storage)?; - if !constants.burn_is_enabled { - return Err(StdError::generic_err( - "Burn functionality is not enabled for this token.", - )); - } - - let raw_amount = amount.u128(); - use_allowance(deps.storage, env, &owner, &info.sender, raw_amount)?; - let raw_burner = deps.api.addr_canonicalize(info.sender.as_str())?; - - let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); - - // store the event - let tx_id = store_burn_action( - deps.storage, - raw_owner.clone(), - raw_burner.clone(), - raw_amount, - constants.symbol, - memo, - &env.block, - )?; - - // load delayed write buffer - let mut dwb = DWB.load(deps.storage)?; - - #[cfg(feature = "gas_tracking")] - let mut tracker = GasTracker::new(deps.api); - - // settle the owner's account in buffer - let owner_balance = dwb.settle_sender_or_owner_account( - deps.storage, - &raw_owner, - tx_id, - raw_amount, - "burn", - raw_burner == raw_owner, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - // sender and owner are different - if raw_burner != raw_owner { - // also settle sender's account - dwb.settle_sender_or_owner_account( - deps.storage, - &raw_burner, - tx_id, - 0, - "burn", - false, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - } - - DWB.save(deps.storage, &dwb)?; - - // remove from supply - let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; - - if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { - total_supply = new_total_supply; - } else { - return Err(StdError::generic_err( - "You're trying to burn more than is available in the total supply", - )); - } - - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let spent_notification = Notification::new ( - owner, - SpentNotification { - amount: raw_amount, - actions: 1, - recipient: None, - balance: owner_balance, - memo_len, - } - ) - .to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); - } - - Ok(resp) -} - -fn try_batch_burn_from( - deps: DepsMut, - env: &Env, - info: MessageInfo, - actions: Vec, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let constants = CONFIG.load(deps.storage)?; - if !constants.burn_is_enabled { - return Err(StdError::generic_err( - "Burn functionality is not enabled for this token.", - )); - } - - let raw_spender = deps.api.addr_canonicalize(info.sender.as_str())?; - let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; - let mut spent_notifications = vec![]; - - for action in actions { - let owner = deps.api.addr_validate(action.owner.as_str())?; - let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; - let amount = action.amount.u128(); - use_allowance(deps.storage, env, &owner, &info.sender, amount)?; - - let tx_id = store_burn_action( - deps.storage, - raw_owner.clone(), - raw_spender.clone(), - amount, - constants.symbol.clone(), - action.memo.clone(), - &env.block, - )?; - - // load delayed write buffer - let mut dwb = DWB.load(deps.storage)?; - - #[cfg(feature = "gas_tracking")] - let mut tracker = GasTracker::new(deps.api); - - // settle the owner's account in buffer - let owner_balance = dwb.settle_sender_or_owner_account( - deps.storage, - &raw_owner, - tx_id, - amount, - "burn", - raw_spender == raw_owner, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - // sender and owner are different - if raw_spender != raw_owner { - // also settle the sender's account - dwb.settle_sender_or_owner_account( - deps.storage, - &raw_spender, - tx_id, - 0, - "burn", - false, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - } - - DWB.save(deps.storage, &dwb)?; - - // remove from supply - if let Some(new_total_supply) = total_supply.checked_sub(amount) { - total_supply = new_total_supply; - } else { - return Err(StdError::generic_err(format!( - "You're trying to burn more than is available in the total supply: {action:?}", - ))); - } - - spent_notifications.push(Notification::new ( - info.sender.clone(), - SpentNotification { - amount, - actions: 1, - recipient: None, - balance: owner_balance, - memo_len: action.memo.as_ref().map(|s| s.len()).unwrap_or_default() - } - )); - } - - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchBurnFrom {status: Success,})?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - resp = render_group_notification( - deps.api, - MultiSpentNotification(spent_notifications), - &env.transaction.clone().unwrap().hash, - env.block.random.clone().unwrap(), - secret, - resp, - )?; - } - - Ok(resp) -} - -fn try_increase_allowance( - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: String, - amount: Uint128, - expiration: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let spender = deps.api.addr_validate(spender.as_str())?; - let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender); - - // If the previous allowance has expired, reset the allowance. - // Without this users can take advantage of an expired allowance given to - // them long ago. - if allowance.is_expired_at(&env.block) { - allowance.amount = amount.u128(); - allowance.expiration = None; - } else { - allowance.amount = allowance.amount.saturating_add(amount.u128()); - } - - if expiration.is_some() { - allowance.expiration = expiration; - } - let new_amount = allowance.amount; - AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { - owner: info.sender.clone(), - spender: spender.clone(), - allowance: Uint128::from(new_amount), - })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let notification = Notification::new ( - spender, - AllowanceNotification { - amount: new_amount, - allower: info.sender, - expiration, - } - ) - .to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - notification.id_plaintext(), - notification.data_plaintext() - ); - } - - Ok(resp) -} - -fn try_decrease_allowance( - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: String, - amount: Uint128, - expiration: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let spender = deps.api.addr_validate(spender.as_str())?; - let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender); - - // If the previous allowance has expired, reset the allowance. - // Without this users can take advantage of an expired allowance given to - // them long ago. - if allowance.is_expired_at(&env.block) { - allowance.amount = 0; - allowance.expiration = None; - } else { - allowance.amount = allowance.amount.saturating_sub(amount.u128()); - } - - if expiration.is_some() { - allowance.expiration = expiration; - } - let new_amount = allowance.amount; - AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { - owner: info.sender.clone(), - spender: spender.clone(), - allowance: Uint128::from(new_amount), - })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let notification = Notification::new ( - spender, - AllowanceNotification { - amount: new_amount, - allower: info.sender, - expiration, - } - ) - .to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - notification.id_plaintext(), - notification.data_plaintext() - ); - } - - Ok(resp) -} - -fn add_minters( - deps: DepsMut, - info: MessageInfo, - minters_to_add: Vec, -) -> StdResult { - let constants = CONFIG.load(deps.storage)?; - if !constants.mint_is_enabled { - return Err(StdError::generic_err( - "Mint functionality is not enabled for this token.", - )); - } - - check_if_admin(&constants.admin, &info.sender)?; - - let minters_to_add: Vec = minters_to_add - .iter() - .map(|minter| deps.api.addr_validate(minter.as_str()).unwrap()) - .collect(); - MintersStore::add_minters(deps.storage, minters_to_add)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddMinters { status: Success })?)) -} - -fn remove_minters( - deps: DepsMut, - info: MessageInfo, - minters_to_remove: Vec, -) -> StdResult { - let constants = CONFIG.load(deps.storage)?; - if !constants.mint_is_enabled { - return Err(StdError::generic_err( - "Mint functionality is not enabled for this token.", - )); - } - - check_if_admin(&constants.admin, &info.sender)?; - - let minters_to_remove: StdResult> = minters_to_remove - .iter() - .map(|minter| deps.api.addr_validate(minter.as_str())) - .collect(); - MintersStore::remove_minters(deps.storage, minters_to_remove?)?; - - Ok( - Response::new().set_data(to_binary(&ExecuteAnswer::RemoveMinters { - status: Success, - })?), - ) -} - -fn set_minters( - deps: DepsMut, - info: MessageInfo, - minters_to_set: Vec, -) -> StdResult { - let constants = CONFIG.load(deps.storage)?; - if !constants.mint_is_enabled { - return Err(StdError::generic_err( - "Mint functionality is not enabled for this token.", - )); - } - - check_if_admin(&constants.admin, &info.sender)?; - - let minters_to_set: Vec = minters_to_set - .iter() - .map(|minter| deps.api.addr_validate(minter.as_str()).unwrap()) - .collect(); - MintersStore::save(deps.storage, minters_to_set)?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetMinters { status: Success })?)) -} - -/// Burn tokens -/// -/// Remove `amount` tokens from the system irreversibly, from signer account -/// -/// @param amount the amount of money to burn -fn try_burn( - deps: DepsMut, - env: Env, - info: MessageInfo, - amount: Uint128, - memo: Option, -) -> StdResult { - let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; - let secret = secret.as_slice(); - - let constants = CONFIG.load(deps.storage)?; - if !constants.burn_is_enabled { - return Err(StdError::generic_err( - "Burn functionality is not enabled for this token.", - )); - } - - let raw_amount = amount.u128(); - let raw_burn_address = deps.api.addr_canonicalize(info.sender.as_str())?; - - let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); - - let tx_id = store_burn_action( - deps.storage, - raw_burn_address.clone(), - raw_burn_address.clone(), - raw_amount, - constants.symbol, - memo, - &env.block, - )?; - - // load delayed write buffer - let mut dwb = DWB.load(deps.storage)?; - - #[cfg(feature = "gas_tracking")] - let mut tracker = GasTracker::new(deps.api); - - // settle the signer's account in buffer - let owner_balance = dwb.settle_sender_or_owner_account( - deps.storage, - &raw_burn_address, - tx_id, - raw_amount, - "burn", - false, - #[cfg(feature = "gas_tracking")] - &mut tracker, - )?; - - DWB.save(deps.storage, &dwb)?; - - let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; - if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { - total_supply = new_total_supply; - } else { - return Err(StdError::generic_err( - "You're trying to burn more than is available in the total supply", - )); - } - TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?); - - if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let spent_notification = Notification::new ( - info.sender, - SpentNotification { - amount: raw_amount, - actions: 1, - recipient: None, - balance: owner_balance, - memo_len, - } - ) - .to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); - } - - Ok(resp) -} - -fn perform_transfer( - store: &mut dyn Storage, - rng: &mut ContractPrng, - from: &CanonicalAddr, - to: &CanonicalAddr, - sender: &CanonicalAddr, - amount: u128, - denom: String, - memo: Option, - block: &BlockInfo, - is_from_action: bool, - #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult { - #[cfg(feature = "gas_tracking")] - let mut group1 = tracker.group("perform_transfer.1"); - - // first store the tx information in the global append list of txs and get the new tx id - let tx_id = store_transfer_action(store, from, sender, to, amount, denom, memo, block)?; - - #[cfg(feature = "gas_tracking")] - group1.log("@store_transfer_action"); - - // load delayed write buffer - let mut dwb = DWB.load(store)?; - - #[cfg(feature = "gas_tracking")] - group1.log("DWB.load"); - - let transfer_str = "transfer"; - - // settle the owner's account - let owner_balance = dwb.settle_sender_or_owner_account( - store, - from, - tx_id, - amount, - transfer_str, - is_from_action && sender == from, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - - // sender and owner are different - if sender != from { - // settle the sender's account too - dwb.settle_sender_or_owner_account( - store, - sender, - tx_id, - 0, - transfer_str, - false, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - } - - // add the tx info for the recipient to the buffer - dwb.add_recipient( - store, - rng, - to, - tx_id, - amount, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - - #[cfg(feature = "gas_tracking")] - let mut group2 = tracker.group("perform_transfer.2"); - - DWB.save(store, &dwb)?; - - #[cfg(feature = "gas_tracking")] - group2.log("DWB.save"); - - Ok(owner_balance) -} - -fn perform_mint( - store: &mut dyn Storage, - rng: &mut ContractPrng, - minter: &CanonicalAddr, - to: &CanonicalAddr, - amount: u128, - denom: String, - memo: Option, - block: &BlockInfo, - #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<()> { - // first store the tx information in the global append list of txs and get the new tx id - let tx_id = store_mint_action(store, minter, to, amount, denom, memo, block)?; - - // load delayed write buffer - let mut dwb = DWB.load(store)?; - - // sender and owner are different - if minter != to { - // settle the sender's account too - dwb.settle_sender_or_owner_account( - store, - minter, - tx_id, - 0, - "mint", - false, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - } - - // add the tx info for the recipient to the buffer - dwb.add_recipient( - store, - rng, - to, - tx_id, - amount, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - - DWB.save(store, &dwb)?; - - Ok(()) -} - -fn perform_deposit( - store: &mut dyn Storage, - rng: &mut ContractPrng, - to: &CanonicalAddr, - amount: u128, - denom: String, - block: &BlockInfo, - #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<()> { - // first store the tx information in the global append list of txs and get the new tx id - let tx_id = store_deposit_action(store, amount, denom, block)?; - - // load delayed write buffer - let mut dwb = DWB.load(store)?; - - // add the tx info for the recipient to the buffer - dwb.add_recipient( - store, - rng, - to, - tx_id, - amount, - #[cfg(feature = "gas_tracking")] - tracker, - )?; - - DWB.save(store, &dwb)?; - - Ok(()) -} - -fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdResult { - RevokedPermits::revoke_permit( - deps.storage, - info.sender.as_str(), - &permit_name, - ); - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?)) -} - -// SNIP 24.1 - -fn revoke_all_permits(deps: DepsMut, info: MessageInfo, interval: AllRevokedInterval) -> StdResult { - let revocation_id = RevokedPermits::revoke_all_permits( - deps.storage, - info.sender.as_str(), - &interval, - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokeAllPermits { - status: Success, - revocation_id: Some(revocation_id.to_string()), - })?)) -} - -fn delete_permit_revocation(deps: DepsMut, info: MessageInfo, revocation_id: String) -> StdResult { - RevokedPermits::delete_revocation( - deps.storage, - info.sender.as_str(), - revocation_id.as_str(), - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::DeletePermitRevocation { - status: Success, - })?)) -} - -fn query_list_permit_revocations(deps: Deps, account: &str) -> StdResult { - let revocations = RevokedPermits::list_revocations( - deps.storage, - account - )?; - - to_binary(&QueryAnswer::ListPermitRevocations { revocations }) -} - -// end SNIP 24.1 - -fn check_if_admin(config_admin: &Addr, account: &Addr) -> StdResult<()> { - if config_admin != account { - return Err(StdError::generic_err( - "This is an admin command. Admin commands can only be run from admin address", - )); - } - - Ok(()) -} - -fn is_valid_name(name: &str) -> bool { - let len = name.len(); - (3..=30).contains(&len) -} - -fn is_valid_symbol(symbol: &str) -> bool { - let len = symbol.len(); - let len_is_valid = (3..=20).contains(&len); - - len_is_valid && symbol.bytes().all(|byte| byte.is_ascii_alphabetic()) -} - -// pub fn migrate( -// _deps: DepsMut, -// _env: Env, -// _msg: MigrateMsg, -// ) -> StdResult { -// Ok(MigrateResponse::default()) -// Ok(MigrateResponse::default()) -// } - #[cfg(test)] mod tests { use std::any::Any; use cosmwasm_std::{ - from_binary, testing::*, Api, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, - QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, WasmMsg, + from_binary, testing::*, Addr, Api, BlockInfo, Coin, ContractInfo, CosmosMsg, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, Uint128, WasmMsg }; use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; - use crate::dwb::TX_NODES_COUNT; - use crate::msg::{InitConfig, InitialBalance, ResponseStatus}; - use crate::state::TX_COUNT; - use crate::transaction_history::TxAction; + use crate::batch; + use crate::btbe::stored_balance; + use crate::dwb::{TX_NODES, TX_NODES_COUNT}; + use crate::msg::{ExecuteAnswer, InitConfig, InitialBalance, ResponseStatus, ResponseStatus::Success}; + use crate::receiver::Snip20ReceiveMsg; + use crate::state::{AllowancesStore, ReceiverHashStore, TX_COUNT}; + use crate::transaction_history::{Tx, TxAction}; use super::*; diff --git a/src/execute.rs b/src/execute.rs new file mode 100644 index 00000000..09b391fe --- /dev/null +++ b/src/execute.rs @@ -0,0 +1,242 @@ +use cosmwasm_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; +use secret_toolkit::notification::Notification; +use secret_toolkit::permit::{AllRevokedInterval, RevokedPermits, RevokedPermitsStore}; +use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; +use secret_toolkit_crypto::ContractPrng; + +use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; +use crate::notifications::AllowanceNotification; +use crate::state::{AllowancesStore, ReceiverHashStore, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED}; + +// viewing key functions + +pub fn try_set_key(deps: DepsMut, info: MessageInfo, key: String) -> StdResult { + ViewingKey::set(deps.storage, info.sender.as_str(), key.as_str()); + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetViewingKey { + status: Success, + })?), + ) +} + +pub fn try_create_key( + deps: DepsMut, + env: Env, + info: MessageInfo, + entropy: Option, + rng: &mut ContractPrng, +) -> StdResult { + let entropy = [entropy.unwrap_or_default().as_bytes(), &rng.rand_bytes()].concat(); + + let key = ViewingKey::create( + deps.storage, + &info, + &env, + info.sender.as_str(), + &entropy, + ); + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key })?)) +} + +// register receive function + +pub fn try_register_receive( + deps: DepsMut, + info: MessageInfo, + code_hash: String, +) -> StdResult { + ReceiverHashStore::save(deps.storage, &info.sender, code_hash)?; + + let data = to_binary(&ExecuteAnswer::RegisterReceive { status: Success })?; + Ok(Response::new() + .add_attribute("register_status", "success") + .set_data(data)) +} + +// allowance functions + +fn insufficient_allowance(allowance: u128, required: u128) -> StdError { + StdError::generic_err(format!( + "insufficient allowance: allowance={allowance}, required={required}", + )) +} + +pub fn use_allowance( + storage: &mut dyn Storage, + env: &Env, + owner: &Addr, + spender: &Addr, + amount: u128, +) -> StdResult<()> { + let mut allowance = AllowancesStore::load(storage, owner, spender); + + if allowance.is_expired_at(&env.block) || allowance.amount == 0 { + return Err(insufficient_allowance(0, amount)); + } + if let Some(new_allowance) = allowance.amount.checked_sub(amount) { + allowance.amount = new_allowance; + } else { + return Err(insufficient_allowance(allowance.amount, amount)); + } + + AllowancesStore::save(storage, owner, spender, &allowance)?; + + Ok(()) +} + +pub fn try_increase_allowance( + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: String, + amount: Uint128, + expiration: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let spender = deps.api.addr_validate(spender.as_str())?; + let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender); + + // If the previous allowance has expired, reset the allowance. + // Without this users can take advantage of an expired allowance given to + // them long ago. + if allowance.is_expired_at(&env.block) { + allowance.amount = amount.u128(); + allowance.expiration = None; + } else { + allowance.amount = allowance.amount.saturating_add(amount.u128()); + } + + if expiration.is_some() { + allowance.expiration = expiration; + } + let new_amount = allowance.amount; + AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { + owner: info.sender.clone(), + spender: spender.clone(), + allowance: Uint128::from(new_amount), + })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let notification = Notification::new ( + spender, + AllowanceNotification { + amount: new_amount, + allower: info.sender, + expiration, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + notification.id_plaintext(), + notification.data_plaintext() + ); + } + + Ok(resp) +} + +pub fn try_decrease_allowance( + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: String, + amount: Uint128, + expiration: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let spender = deps.api.addr_validate(spender.as_str())?; + let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender); + + // If the previous allowance has expired, reset the allowance. + // Without this users can take advantage of an expired allowance given to + // them long ago. + if allowance.is_expired_at(&env.block) { + allowance.amount = 0; + allowance.expiration = None; + } else { + allowance.amount = allowance.amount.saturating_sub(amount.u128()); + } + + if expiration.is_some() { + allowance.expiration = expiration; + } + let new_amount = allowance.amount; + AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { + owner: info.sender.clone(), + spender: spender.clone(), + allowance: Uint128::from(new_amount), + })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let notification = Notification::new ( + spender, + AllowanceNotification { + amount: new_amount, + allower: info.sender, + expiration, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + notification.id_plaintext(), + notification.data_plaintext() + ); + } + + Ok(resp) +} + +// SNIP 24, 24.1 permit functions + +pub fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdResult { + RevokedPermits::revoke_permit( + deps.storage, + info.sender.as_str(), + &permit_name, + ); + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?)) +} + +pub fn revoke_all_permits(deps: DepsMut, info: MessageInfo, interval: AllRevokedInterval) -> StdResult { + let revocation_id = RevokedPermits::revoke_all_permits( + deps.storage, + info.sender.as_str(), + &interval, + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokeAllPermits { + status: Success, + revocation_id: Some(revocation_id.to_string()), + })?)) +} + +pub fn delete_permit_revocation(deps: DepsMut, info: MessageInfo, revocation_id: String) -> StdResult { + RevokedPermits::delete_revocation( + deps.storage, + info.sender.as_str(), + revocation_id.as_str(), + )?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::DeletePermitRevocation { + status: Success, + })?)) +} + + + + + diff --git a/src/execute_admin.rs b/src/execute_admin.rs new file mode 100644 index 00000000..7d7983ce --- /dev/null +++ b/src/execute_admin.rs @@ -0,0 +1,160 @@ +use cosmwasm_std::{to_binary, Addr, DepsMut, Response, StdError, StdResult,}; + +use crate::msg::ContractStatusLevel; +use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; +use crate::state::{Config, MintersStore, CONFIG, CONTRACT_STATUS, NOTIFICATIONS_ENABLED}; + +// All the functions in this file MUST only be executed after confirming the sender is the admin + +pub fn change_admin(deps: DepsMut, constants: &mut Config, address: String) -> StdResult { + let address = deps.api.addr_validate(address.as_str())?; + + constants.admin = address; + CONFIG.save(deps.storage, &constants)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ChangeAdmin { status: Success })?)) +} + +pub fn add_supported_denoms( + deps: DepsMut, + config: &mut Config, + denoms: Vec, +) -> StdResult { + if !config.can_modify_denoms { + return Err(StdError::generic_err( + "Cannot modify denoms for this contract", + )); + } + + for denom in denoms.iter() { + if !config.supported_denoms.contains(denom) { + config.supported_denoms.push(denom.clone()); + } + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::AddSupportedDenoms { + status: Success, + })?), + ) +} + +pub fn remove_supported_denoms( + deps: DepsMut, + config: &mut Config, + denoms: Vec, +) -> StdResult { + if !config.can_modify_denoms { + return Err(StdError::generic_err( + "Cannot modify denoms for this contract", + )); + } + + for denom in denoms.iter() { + config.supported_denoms.retain(|x| x != denom); + } + + CONFIG.save(deps.storage, &config)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemoveSupportedDenoms { + status: Success, + })?), + ) +} + +pub fn set_contract_status( + deps: DepsMut, + status_level: ContractStatusLevel, +) -> StdResult { + CONTRACT_STATUS.save(deps.storage, &status_level)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetContractStatus { + status: Success, + })?), + ) +} + +pub fn add_minters( + deps: DepsMut, + constants: &Config, + minters_to_add: Vec, +) -> StdResult { + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + let minters_to_add: Vec = minters_to_add + .iter() + .map(|minter| deps.api.addr_validate(minter.as_str()).unwrap()) + .collect(); + MintersStore::add_minters(deps.storage, minters_to_add)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddMinters { status: Success })?)) +} + +pub fn remove_minters( + deps: DepsMut, + constants: &Config, + minters_to_remove: Vec, +) -> StdResult { + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + let minters_to_remove: StdResult> = minters_to_remove + .iter() + .map(|minter| deps.api.addr_validate(minter.as_str())) + .collect(); + MintersStore::remove_minters(deps.storage, minters_to_remove?)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RemoveMinters { + status: Success, + })?), + ) +} + +pub fn set_minters( + deps: DepsMut, + constants: &Config, + minters_to_set: Vec, +) -> StdResult { + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + let minters_to_set: Vec = minters_to_set + .iter() + .map(|minter| deps.api.addr_validate(minter.as_str()).unwrap()) + .collect(); + MintersStore::save(deps.storage, minters_to_set)?; + + Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetMinters { status: Success })?)) +} + +// SNIP-52 functions + +pub fn set_notification_status( + deps: DepsMut, + enabled: bool, +) -> StdResult { + NOTIFICATIONS_ENABLED.save(deps.storage, &enabled)?; + + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::SetNotificationStatus { + status: Success, + })?), + ) +} + +// end SNIP-52 functions diff --git a/src/execute_deposit_redeem.rs b/src/execute_deposit_redeem.rs new file mode 100644 index 00000000..394121e8 --- /dev/null +++ b/src/execute_deposit_redeem.rs @@ -0,0 +1,193 @@ +use cosmwasm_std::{to_binary, BankMsg, BlockInfo, CanonicalAddr, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; +use secret_toolkit_crypto::ContractPrng; + +use crate::dwb::DWB; +use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; +use crate::state::{safe_add, CONFIG, TOTAL_SUPPLY}; +use crate::transaction_history::{store_deposit_action, store_redeem_action,}; + +// deposit functions + +pub fn try_deposit( + deps: DepsMut, + env: Env, + info: MessageInfo, + rng: &mut ContractPrng, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + + let mut amount = Uint128::zero(); + + for coin in &info.funds { + if constants.supported_denoms.contains(&coin.denom) { + amount += coin.amount + } else { + return Err(StdError::generic_err(format!( + "Tried to deposit an unsupported coin {}", + coin.denom + ))); + } + } + + if amount.is_zero() { + return Err(StdError::generic_err("No funds were sent to be deposited")); + } + + let mut raw_amount = amount.u128(); + + if !constants.deposit_is_enabled { + return Err(StdError::generic_err( + "Deposit functionality is not enabled.", + )); + } + + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + raw_amount = safe_add(&mut total_supply, raw_amount); + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; + + #[cfg(feature = "gas_tracking")] + let mut tracker: GasTracker = GasTracker::new(deps.api); + + perform_deposit( + deps.storage, + rng, + &sender_address, + raw_amount, + "uscrt".to_string(), + &env.block, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?); + + #[cfg(feature = "gas_tracking")] + return Ok(resp.add_gas_tracker(tracker)); + + #[cfg(not(feature = "gas_tracking"))] + Ok(resp) +} + +fn perform_deposit( + store: &mut dyn Storage, + rng: &mut ContractPrng, + to: &CanonicalAddr, + amount: u128, + denom: String, + block: &BlockInfo, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, +) -> StdResult<()> { + // first store the tx information in the global append list of txs and get the new tx id + let tx_id = store_deposit_action(store, amount, denom, block)?; + + // load delayed write buffer + let mut dwb = DWB.load(store)?; + + // add the tx info for the recipient to the buffer + dwb.add_recipient( + store, + rng, + to, + tx_id, + amount, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + + DWB.save(store, &dwb)?; + + Ok(()) +} + +// redeem functions + +pub fn try_redeem( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, + denom: Option, +) -> StdResult { + let constants = CONFIG.load(deps.storage)?; + if !constants.redeem_is_enabled { + return Err(StdError::generic_err( + "Redeem functionality is not enabled for this token.", + )); + } + + // if denom is none and there is only 1 supported denom then we don't need to check anything + let withdraw_denom = if denom.is_none() && constants.supported_denoms.len() == 1 { + constants.supported_denoms.first().unwrap().clone() + // if denom is specified make sure it's on the list before trying to withdraw with it + } else if denom.is_some() && constants.supported_denoms.contains(denom.as_ref().unwrap()) { + denom.unwrap() + // error handling + } else if denom.is_none() { + return Err(StdError::generic_err( + "Tried to redeem without specifying denom, but multiple coins are supported", + )); + } else { + return Err(StdError::generic_err( + "Tried to redeem for an unsupported coin", + )); + }; + + let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?; + let amount_raw = amount.u128(); + + let tx_id = store_redeem_action(deps.storage, amount.u128(), constants.symbol, &env.block)?; + + // load delayed write buffer + let mut dwb = DWB.load(deps.storage)?; + + #[cfg(feature = "gas_tracking")] + let mut tracker = GasTracker::new(deps.api); + + // settle the signer's account in buffer + dwb.settle_sender_or_owner_account( + deps.storage, + &sender_address, + tx_id, + amount_raw, + "redeem", + false, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + DWB.save(deps.storage, &dwb)?; + + let total_supply = TOTAL_SUPPLY.load(deps.storage)?; + if let Some(total_supply) = total_supply.checked_sub(amount_raw) { + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + } else { + return Err(StdError::generic_err( + "You are trying to redeem more tokens than what is available in the total supply", + )); + } + + let token_reserve = deps + .querier + .query_balance(&env.contract.address, &withdraw_denom)? + .amount; + if amount > token_reserve { + return Err(StdError::generic_err(format!( + "You are trying to redeem for more {withdraw_denom} than the contract has in its reserve", + ))); + } + + let withdrawal_coins: Vec = vec![Coin { + denom: withdraw_denom, + amount, + }]; + + let message = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.clone().into_string(), + amount: withdrawal_coins, + }); + let data = to_binary(&ExecuteAnswer::Redeem { status: Success })?; + let res = Response::new().add_message(message).set_data(data); + Ok(res) +} diff --git a/src/execute_mint_burn.rs b/src/execute_mint_burn.rs new file mode 100644 index 00000000..83f66d42 --- /dev/null +++ b/src/execute_mint_burn.rs @@ -0,0 +1,570 @@ +use cosmwasm_std::{to_binary, Addr, BlockInfo, CanonicalAddr, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; +use secret_toolkit::notification::Notification; +use secret_toolkit_crypto::ContractPrng; + +use crate::batch; +use crate::dwb::DWB; +use crate::execute::use_allowance; +use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; +use crate::notifications::{render_group_notification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification}; +use crate::state::{safe_add, MintersStore, CONFIG, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY}; +use crate::transaction_history::{store_burn_action, store_mint_action}; + +// mint functions + +#[allow(clippy::too_many_arguments)] +pub fn try_mint( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + rng: &mut ContractPrng, + recipient: String, + amount: Uint128, + memo: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let recipient = deps.api.addr_validate(recipient.as_str())?; + + let constants = CONFIG.load(deps.storage)?; + + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + let minters = MintersStore::load(deps.storage)?; + if !minters.contains(&info.sender) { + return Err(StdError::generic_err( + "Minting is allowed to minter accounts only", + )); + } + + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + let minted_amount = safe_add(&mut total_supply, amount.u128()); + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + #[cfg(feature = "gas_tracking")] + let mut tracker: GasTracker = GasTracker::new(deps.api); + + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency + try_mint_impl( + &mut deps, + rng, + info.sender, + recipient.clone(), + Uint128::new(minted_amount), + constants.symbol, + memo, + &env.block, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let received_notification = Notification::new( + recipient, + RecvdNotification { + amount: minted_amount, + sender: None, + memo_len, + sender_is_owner: true, + }, + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ); + } + + #[cfg(feature = "gas_tracking")] + return Ok(resp.add_gas_tracker(tracker)); + + #[cfg(not(feature = "gas_tracking"))] + Ok(resp) +} + +pub fn try_batch_mint( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + rng: &mut ContractPrng, + actions: Vec, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let constants = CONFIG.load(deps.storage)?; + + if !constants.mint_is_enabled { + return Err(StdError::generic_err( + "Mint functionality is not enabled for this token.", + )); + } + + let minters = MintersStore::load(deps.storage)?; + if !minters.contains(&info.sender) { + return Err(StdError::generic_err( + "Minting is allowed to minter accounts only", + )); + } + + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + + let mut notifications = vec![]; + // Quick loop to check that the total of amounts is valid + for action in actions { + let actual_amount = safe_add(&mut total_supply, action.amount.u128()); + + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + + #[cfg(feature = "gas_tracking")] + let mut tracker: GasTracker = GasTracker::new(deps.api); + + notifications.push(Notification::new ( + recipient.clone(), + RecvdNotification { + amount: actual_amount, + sender: None, + memo_len: action.memo.as_ref().map(|s| s.len()).unwrap_or_default(), + sender_is_owner: true, + }, + )); + + try_mint_impl( + &mut deps, + rng, + info.sender.clone(), + recipient, + Uint128::new(actual_amount), + constants.symbol.clone(), + action.memo, + &env.block, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + } + + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + resp = render_group_notification( + deps.api, + MultiRecvdNotification(notifications), + &env.transaction.unwrap().hash, + env.block.random.unwrap(), + secret, + resp, + )?; + } + + Ok(resp) +} + +#[allow(clippy::too_many_arguments)] +fn try_mint_impl( + deps: &mut DepsMut, + rng: &mut ContractPrng, + minter: Addr, + recipient: Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &cosmwasm_std::BlockInfo, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, +) -> StdResult<()> { + let raw_amount = amount.u128(); + let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; + let raw_minter = deps.api.addr_canonicalize(minter.as_str())?; + + perform_mint( + deps.storage, + rng, + &raw_minter, + &raw_recipient, + raw_amount, + denom, + memo, + block, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + + Ok(()) +} + +pub fn perform_mint( + store: &mut dyn Storage, + rng: &mut ContractPrng, + minter: &CanonicalAddr, + to: &CanonicalAddr, + amount: u128, + denom: String, + memo: Option, + block: &BlockInfo, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, +) -> StdResult<()> { + // first store the tx information in the global append list of txs and get the new tx id + let tx_id = store_mint_action(store, minter, to, amount, denom, memo, block)?; + + // load delayed write buffer + let mut dwb = DWB.load(store)?; + + // sender and owner are different + if minter != to { + // settle the sender's account too + dwb.settle_sender_or_owner_account( + store, + minter, + tx_id, + 0, + "mint", + false, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + } + + // add the tx info for the recipient to the buffer + dwb.add_recipient( + store, + rng, + to, + tx_id, + amount, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + + DWB.save(store, &dwb)?; + + Ok(()) +} + +// burn functions + +/// Burn tokens +/// +/// Remove `amount` tokens from the system irreversibly, from signer account +/// +/// @param amount the amount of money to burn +pub fn try_burn( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, + memo: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let constants = CONFIG.load(deps.storage)?; + if !constants.burn_is_enabled { + return Err(StdError::generic_err( + "Burn functionality is not enabled for this token.", + )); + } + + let raw_amount = amount.u128(); + let raw_burn_address = deps.api.addr_canonicalize(info.sender.as_str())?; + + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + let tx_id = store_burn_action( + deps.storage, + raw_burn_address.clone(), + raw_burn_address.clone(), + raw_amount, + constants.symbol, + memo, + &env.block, + )?; + + // load delayed write buffer + let mut dwb = DWB.load(deps.storage)?; + + #[cfg(feature = "gas_tracking")] + let mut tracker = GasTracker::new(deps.api); + + // settle the signer's account in buffer + let owner_balance = dwb.settle_sender_or_owner_account( + deps.storage, + &raw_burn_address, + tx_id, + raw_amount, + "burn", + false, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + DWB.save(deps.storage, &dwb)?; + + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err( + "You're trying to burn more than is available in the total supply", + )); + } + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let spent_notification = Notification::new ( + info.sender, + SpentNotification { + amount: raw_amount, + actions: 1, + recipient: None, + balance: owner_balance, + memo_len, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + Ok(resp) +} + +#[allow(clippy::too_many_arguments)] +pub fn try_burn_from( + deps: DepsMut, + env: &Env, + info: MessageInfo, + owner: String, + amount: Uint128, + memo: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let owner = deps.api.addr_validate(owner.as_str())?; + let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; + let constants = CONFIG.load(deps.storage)?; + if !constants.burn_is_enabled { + return Err(StdError::generic_err( + "Burn functionality is not enabled for this token.", + )); + } + + let raw_amount = amount.u128(); + use_allowance(deps.storage, env, &owner, &info.sender, raw_amount)?; + let raw_burner = deps.api.addr_canonicalize(info.sender.as_str())?; + + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + // store the event + let tx_id = store_burn_action( + deps.storage, + raw_owner.clone(), + raw_burner.clone(), + raw_amount, + constants.symbol, + memo, + &env.block, + )?; + + // load delayed write buffer + let mut dwb = DWB.load(deps.storage)?; + + #[cfg(feature = "gas_tracking")] + let mut tracker = GasTracker::new(deps.api); + + // settle the owner's account in buffer + let owner_balance = dwb.settle_sender_or_owner_account( + deps.storage, + &raw_owner, + tx_id, + raw_amount, + "burn", + raw_burner == raw_owner, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + // sender and owner are different + if raw_burner != raw_owner { + // also settle sender's account + dwb.settle_sender_or_owner_account( + deps.storage, + &raw_burner, + tx_id, + 0, + "burn", + false, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + } + + DWB.save(deps.storage, &dwb)?; + + // remove from supply + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + + if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err( + "You're trying to burn more than is available in the total supply", + )); + } + + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let spent_notification = Notification::new ( + owner, + SpentNotification { + amount: raw_amount, + actions: 1, + recipient: None, + balance: owner_balance, + memo_len, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + Ok(resp) +} + +pub fn try_batch_burn_from( + deps: DepsMut, + env: &Env, + info: MessageInfo, + actions: Vec, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let constants = CONFIG.load(deps.storage)?; + if !constants.burn_is_enabled { + return Err(StdError::generic_err( + "Burn functionality is not enabled for this token.", + )); + } + + let raw_spender = deps.api.addr_canonicalize(info.sender.as_str())?; + let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?; + let mut spent_notifications = vec![]; + + for action in actions { + let owner = deps.api.addr_validate(action.owner.as_str())?; + let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; + let amount = action.amount.u128(); + use_allowance(deps.storage, env, &owner, &info.sender, amount)?; + + let tx_id = store_burn_action( + deps.storage, + raw_owner.clone(), + raw_spender.clone(), + amount, + constants.symbol.clone(), + action.memo.clone(), + &env.block, + )?; + + // load delayed write buffer + let mut dwb = DWB.load(deps.storage)?; + + #[cfg(feature = "gas_tracking")] + let mut tracker = GasTracker::new(deps.api); + + // settle the owner's account in buffer + let owner_balance = dwb.settle_sender_or_owner_account( + deps.storage, + &raw_owner, + tx_id, + amount, + "burn", + raw_spender == raw_owner, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + // sender and owner are different + if raw_spender != raw_owner { + // also settle the sender's account + dwb.settle_sender_or_owner_account( + deps.storage, + &raw_spender, + tx_id, + 0, + "burn", + false, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + } + + DWB.save(deps.storage, &dwb)?; + + // remove from supply + if let Some(new_total_supply) = total_supply.checked_sub(amount) { + total_supply = new_total_supply; + } else { + return Err(StdError::generic_err(format!( + "You're trying to burn more than is available in the total supply: {action:?}", + ))); + } + + spent_notifications.push(Notification::new ( + info.sender.clone(), + SpentNotification { + amount, + actions: 1, + recipient: None, + balance: owner_balance, + memo_len: action.memo.as_ref().map(|s| s.len()).unwrap_or_default() + } + )); + } + + TOTAL_SUPPLY.save(deps.storage, &total_supply)?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchBurnFrom {status: Success,})?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + resp = render_group_notification( + deps.api, + MultiSpentNotification(spent_notifications), + &env.transaction.clone().unwrap().hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + } + + Ok(resp) +} \ No newline at end of file diff --git a/src/execute_transfer_send.rs b/src/execute_transfer_send.rs new file mode 100644 index 00000000..4d6988d8 --- /dev/null +++ b/src/execute_transfer_send.rs @@ -0,0 +1,977 @@ +use cosmwasm_std::{to_binary, Addr, Binary, BlockInfo, CanonicalAddr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; +use secret_toolkit::notification::Notification; +use secret_toolkit_crypto::ContractPrng; + +use crate::batch; +use crate::dwb::DWB; +use crate::execute::use_allowance; +use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; +use crate::notifications::{render_group_notification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification}; +use crate::receiver::Snip20ReceiveMsg; +use crate::state::{ReceiverHashStore, CONFIG, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED}; +use crate::strings::SEND_TO_CONTRACT_ERR_MSG; +use crate::transaction_history::store_transfer_action; + +// transfer functions + +#[allow(clippy::too_many_arguments)] +pub fn try_transfer( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + rng: &mut ContractPrng, + recipient: String, + amount: Uint128, + memo: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let recipient: Addr = deps.api.addr_validate(recipient.as_str())?; + + let symbol = CONFIG.load(deps.storage)?.symbol; + + // make sure the sender is not accidentally sending tokens to the contract address + if recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); + } + + #[cfg(feature = "gas_tracking")] + let mut tracker: GasTracker = GasTracker::new(deps.api); + + // perform the transfer + let ( + received_notification, + spent_notification + ) = try_transfer_impl( + &mut deps, + rng, + &info.sender, + &recipient, + amount, + symbol, + memo, + &env.block, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + #[cfg(feature = "gas_tracking")] + let mut group1 = tracker.group("try_transfer.rest"); + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + // render the tokens received notification + let received_notification = received_notification.to_txhash_notification( + deps.api, + &env, + secret, + None, + )?; + + // render the tokens spent notification + let spent_notification = spent_notification.to_txhash_notification( + deps.api, + &env, + secret, + None, + )?; + + resp = resp.add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + #[cfg(feature = "gas_tracking")] + group1.log("rest"); + + #[cfg(feature = "gas_tracking")] + return Ok(resp.add_gas_tracker(tracker)); + + #[cfg(not(feature = "gas_tracking"))] + Ok(resp) +} + +pub fn try_batch_transfer( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + rng: &mut ContractPrng, + actions: Vec, +) -> StdResult { + let num_actions = actions.len(); + if num_actions == 0 { + return Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?) + ); + } + + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let symbol = CONFIG.load(deps.storage)?.symbol; + + let mut total_memo_len = 0; + + #[cfg(feature = "gas_tracking")] + let mut tracker: GasTracker = GasTracker::new(deps.api); + + let mut notifications = vec![]; + for action in actions { + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + + // make sure the sender is not accidentally sending tokens to the contract address + if recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); + } + + total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + let ( + received_notification, + spent_notification + ) = try_transfer_impl( + &mut deps, + rng, + &info.sender, + &recipient, + action.amount, + symbol.clone(), + action.memo, + &env.block, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + notifications.push((received_notification, spent_notification)); + } + + let ( + received_notifications, + spent_notifications + ): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + resp = render_group_notification( + deps.api, + MultiRecvdNotification(received_notifications), + &env.transaction.clone().unwrap().hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + + let total_amount_spent = spent_notifications + .iter() + .fold(0u128, |acc, notification| acc.saturating_add(notification.data.amount)); + + let spent_notification = Notification::new ( + info.sender, + SpentNotification { + amount: total_amount_spent, + actions: num_actions as u32, + recipient: spent_notifications[0].data.recipient.clone(), + balance: spent_notifications.last().unwrap().data.balance, + memo_len: total_memo_len, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + #[cfg(feature = "gas_tracking")] + return Ok(resp.add_gas_tracker(tracker)); + + #[cfg(not(feature = "gas_tracking"))] + Ok(resp) +} + +#[allow(clippy::too_many_arguments)] +pub fn try_transfer_from( + mut deps: DepsMut, + env: &Env, + info: MessageInfo, + rng: &mut ContractPrng, + owner: String, + recipient: String, + amount: Uint128, + memo: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let owner = deps.api.addr_validate(owner.as_str())?; + let recipient = deps.api.addr_validate(recipient.as_str())?; + let symbol = CONFIG.load(deps.storage)?.symbol; + let ( + received_notification, + spent_notification + ) = try_transfer_from_impl( + &mut deps, + rng, + env, + &info.sender, + &owner, + &recipient, + amount, + symbol, + memo, + )?; + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let received_notification = received_notification.to_txhash_notification( + deps.api, + &env, + secret, + None, + )?; + + let spent_notification = spent_notification.to_txhash_notification( + deps.api, + &env, + secret, + None + )?; + + resp = resp.add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + Ok(resp) +} + +pub fn try_batch_transfer_from( + mut deps: DepsMut, + env: &Env, + info: MessageInfo, + rng: &mut ContractPrng, + actions: Vec, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let mut notifications = vec![]; + + let symbol = CONFIG.load(deps.storage)?.symbol; + for action in actions { + let owner = deps.api.addr_validate(action.owner.as_str())?; + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + + let ( + received_notification, + spent_notification + ) = try_transfer_from_impl( + &mut deps, + rng, + env, + &info.sender, + &owner, + &recipient, + action.amount, + symbol.clone(), + action.memo, + )?; + + notifications.push((received_notification, spent_notification)); + } + + let mut resp = Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchTransferFrom {status: Success})?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + + let tx_hash = env.transaction.clone().unwrap().hash; + + resp = render_group_notification( + deps.api, + MultiRecvdNotification(received_notifications), + &tx_hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + + resp = render_group_notification( + deps.api, + MultiSpentNotification(spent_notifications), + &tx_hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + } + + Ok(resp) +} + +// send functions + +#[allow(clippy::too_many_arguments)] +pub fn try_send( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + rng: &mut ContractPrng, + recipient: String, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let recipient = deps.api.addr_validate(recipient.as_str())?; + + let mut messages = vec![]; + let symbol = CONFIG.load(deps.storage)?.symbol; + + // make sure the sender is not accidentally sending tokens to the contract address + if recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); + } + + #[cfg(feature = "gas_tracking")] + let mut tracker: GasTracker = GasTracker::new(deps.api); + + let ( + received_notification, + spent_notification + ) = try_send_impl( + &mut deps, + rng, + &mut messages, + info.sender, + recipient, + recipient_code_hash, + amount, + symbol, + memo, + msg, + &env.block, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + let mut resp = Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let received_notification = received_notification.to_txhash_notification(deps.api, &env, secret, None)?; + let spent_notification = spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + #[cfg(feature = "gas_tracking")] + return Ok(resp.add_gas_tracker(tracker)); + + #[cfg(not(feature = "gas_tracking"))] + Ok(resp) +} + +pub fn try_batch_send( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + rng: &mut ContractPrng, + actions: Vec, +) -> StdResult { + let num_actions = actions.len(); + if num_actions == 0 { + return Ok(Response::new() + .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) + ); + } + + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let mut messages = vec![]; + + let mut notifications = vec![]; + let num_actions: usize = actions.len(); + + let symbol = CONFIG.load(deps.storage)?.symbol; + + let mut total_memo_len = 0; + + #[cfg(feature = "gas_tracking")] + let mut tracker: GasTracker = GasTracker::new(deps.api); + + for action in actions { + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + + // make sure the sender is not accidentally sending tokens to the contract address + if recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); + } + + total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + let ( + received_notification, + spent_notification + ) = try_send_impl( + &mut deps, + rng, + &mut messages, + info.sender.clone(), + recipient, + action.recipient_code_hash, + action.amount, + symbol.clone(), + action.memo, + action.msg, + &env.block, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + notifications.push((received_notification, spent_notification)); + } + + let mut resp = Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + + resp = render_group_notification( + deps.api, + MultiRecvdNotification(received_notifications), + &env.transaction.clone().unwrap().hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + + let total_amount_spent = spent_notifications + .iter() + .fold(0u128, |acc, notification| acc + notification.data.amount); + + let spent_notification = Notification::new ( + info.sender, + SpentNotification { + amount: total_amount_spent, + actions: num_actions as u32, + recipient: spent_notifications[0].data.recipient.clone(), + balance: spent_notifications.last().unwrap().data.balance, + memo_len: total_memo_len, + } + ) + .to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); + } + + Ok(resp) +} + +#[allow(clippy::too_many_arguments)] +pub fn try_send_from( + mut deps: DepsMut, + env: Env, + info: &MessageInfo, + rng: &mut ContractPrng, + owner: String, + recipient: String, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let owner = deps.api.addr_validate(owner.as_str())?; + let recipient = deps.api.addr_validate(recipient.as_str())?; + let mut messages = vec![]; + let ( + received_notification, + spent_notification + ) = try_send_from_impl( + &mut deps, + env.clone(), + info, + rng, + &mut messages, + owner, + recipient, + recipient_code_hash, + amount, + memo, + msg, + )?; + + let mut resp = Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let received_notification = received_notification.to_txhash_notification(deps.api, &env, secret, None,)?; + let spent_notification = spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp.add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ) + } + + Ok(resp) +} + +pub fn try_batch_send_from( + mut deps: DepsMut, + env: Env, + info: &MessageInfo, + rng: &mut ContractPrng, + actions: Vec, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + + let mut messages = vec![]; + let mut notifications = vec![]; + + for action in actions { + let owner = deps.api.addr_validate(action.owner.as_str())?; + let recipient = deps.api.addr_validate(action.recipient.as_str())?; + let ( + received_notification, + spent_notification + ) = try_send_from_impl( + &mut deps, + env.clone(), + info, + rng, + &mut messages, + owner, + recipient, + action.recipient_code_hash, + action.amount, + action.memo, + action.msg, + )?; + notifications.push((received_notification, spent_notification)); + } + + let mut resp = Response::new() + .add_messages(messages) + .set_data(to_binary(&ExecuteAnswer::BatchSendFrom { + status: Success, + })?); + + if NOTIFICATIONS_ENABLED.load(deps.storage)? { + let (received_notifications, spent_notifications): ( + Vec>, + Vec>, + ) = notifications.into_iter().unzip(); + + let tx_hash = env.transaction.clone().unwrap().hash; + + resp = render_group_notification( + deps.api, + MultiRecvdNotification(received_notifications), + &tx_hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + + resp = render_group_notification( + deps.api, + MultiSpentNotification(spent_notifications), + &tx_hash, + env.block.random.clone().unwrap(), + secret, + resp, + )?; + } + + Ok(resp) +} + +// helper functions + +#[allow(clippy::too_many_arguments)] +fn try_transfer_impl( + deps: &mut DepsMut, + rng: &mut ContractPrng, + owner: &Addr, + recipient: &Addr, + amount: Uint128, + denom: String, + memo: Option, + block: &cosmwasm_std::BlockInfo, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, +) -> StdResult<(Notification, Notification)> { + // canonicalize owner and recipient addresses + let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; + let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; + + // memo length + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + // create the tokens received notification for recipient + let received_notification = Notification::new( + recipient.clone(), + RecvdNotification { + amount: amount.u128(), + sender: Some(owner.clone()), + memo_len, + sender_is_owner: true, + } + ); + + // perform the transfer from owner to recipient + let owner_balance = perform_transfer( + deps.storage, + rng, + &raw_owner, + &raw_recipient, + &raw_owner, + amount.u128(), + denom, + memo.clone(), + block, + false, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + + // create the tokens spent notification for owner + let spent_notification = Notification::new ( + owner.clone(), + SpentNotification { + amount: amount.u128(), + actions: 1, + recipient: Some(recipient.clone()), + balance: owner_balance, + memo_len, + } + ); + + Ok((received_notification, spent_notification)) +} + +#[allow(clippy::too_many_arguments)] +fn try_transfer_from_impl( + deps: &mut DepsMut, + rng: &mut ContractPrng, + env: &Env, + spender: &Addr, + owner: &Addr, + recipient: &Addr, + amount: Uint128, + denom: String, + memo: Option, +) -> StdResult<(Notification, Notification)> { + let raw_amount = amount.u128(); + let raw_spender = deps.api.addr_canonicalize(spender.as_str())?; + let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; + let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; + + use_allowance(deps.storage, env, owner, spender, raw_amount)?; + + // make sure the sender is not accidentally sending tokens to the contract address + if *recipient == env.contract.address { + return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); + } + + #[cfg(feature = "gas_tracking")] + let mut tracker: GasTracker = GasTracker::new(deps.api); + + let memo_len = memo.as_ref().map(|s| s.len()).unwrap_or_default(); + + // create tokens received notification for recipient + let received_notification = Notification::new( + recipient.clone(), + RecvdNotification { + amount: amount.u128(), + sender: Some(owner.clone()), + memo_len, + sender_is_owner: spender == owner, + } + ); + + // perform the transfer from owner to recipient + let owner_balance = perform_transfer( + deps.storage, + rng, + &raw_owner, + &raw_recipient, + &raw_spender, + raw_amount, + denom, + memo, + &env.block, + true, + #[cfg(feature = "gas_tracking")] + &mut tracker, + )?; + + // create tokens spent notification for owner + let spent_notification = Notification::new ( + owner.clone(), + SpentNotification { + amount: amount.u128(), + actions: 1, + recipient: Some(recipient.clone()), + balance: owner_balance, + memo_len, + } + ); + + Ok((received_notification, spent_notification)) +} + +#[allow(clippy::too_many_arguments)] +fn try_send_impl( + deps: &mut DepsMut, + rng: &mut ContractPrng, + messages: &mut Vec, + sender: Addr, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + denom: String, + memo: Option, + msg: Option, + block: &cosmwasm_std::BlockInfo, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, +) -> StdResult<(Notification, Notification)> { + let ( + received_notification, + spent_notification + ) = try_transfer_impl( + deps, + rng, + &sender, + &recipient, + amount, + denom, + memo.clone(), + block, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + + try_add_receiver_api_callback( + deps.storage, + messages, + recipient, + recipient_code_hash, + msg, + sender.clone(), + sender, + amount, + memo, + )?; + + Ok((received_notification, spent_notification)) +} + +#[allow(clippy::too_many_arguments)] +fn try_send_from_impl( + deps: &mut DepsMut, + env: Env, + info: &MessageInfo, + rng: &mut ContractPrng, + messages: &mut Vec, + owner: Addr, + recipient: Addr, + recipient_code_hash: Option, + amount: Uint128, + memo: Option, + msg: Option, +) -> StdResult<(Notification, Notification)> { + let spender = info.sender.clone(); + let symbol = CONFIG.load(deps.storage)?.symbol; + let ( + received_notification, + spent_notification + ) = try_transfer_from_impl( + deps, + rng, + &env, + &spender, + &owner, + &recipient, + amount, + symbol, + memo.clone(), + )?; + + try_add_receiver_api_callback( + deps.storage, + messages, + recipient, + recipient_code_hash, + msg, + info.sender.clone(), + owner, + amount, + memo, + )?; + + Ok((received_notification, spent_notification)) +} + +fn perform_transfer( + store: &mut dyn Storage, + rng: &mut ContractPrng, + from: &CanonicalAddr, + to: &CanonicalAddr, + sender: &CanonicalAddr, + amount: u128, + denom: String, + memo: Option, + block: &BlockInfo, + is_from_action: bool, + #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, +) -> StdResult { + #[cfg(feature = "gas_tracking")] + let mut group1 = tracker.group("perform_transfer.1"); + + // first store the tx information in the global append list of txs and get the new tx id + let tx_id = store_transfer_action(store, from, sender, to, amount, denom, memo, block)?; + + #[cfg(feature = "gas_tracking")] + group1.log("@store_transfer_action"); + + // load delayed write buffer + let mut dwb = DWB.load(store)?; + + #[cfg(feature = "gas_tracking")] + group1.log("DWB.load"); + + let transfer_str = "transfer"; + + // settle the owner's account + let owner_balance = dwb.settle_sender_or_owner_account( + store, + from, + tx_id, + amount, + transfer_str, + is_from_action && sender == from, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + + // sender and owner are different + if sender != from { + // settle the sender's account too + dwb.settle_sender_or_owner_account( + store, + sender, + tx_id, + 0, + transfer_str, + false, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + } + + // add the tx info for the recipient to the buffer + dwb.add_recipient( + store, + rng, + to, + tx_id, + amount, + #[cfg(feature = "gas_tracking")] + tracker, + )?; + + #[cfg(feature = "gas_tracking")] + let mut group2 = tracker.group("perform_transfer.2"); + + DWB.save(store, &dwb)?; + + #[cfg(feature = "gas_tracking")] + group2.log("DWB.save"); + + Ok(owner_balance) +} + +#[allow(clippy::too_many_arguments)] +fn try_add_receiver_api_callback( + storage: &dyn Storage, + messages: &mut Vec, + recipient: Addr, + recipient_code_hash: Option, + msg: Option, + sender: Addr, + from: Addr, + amount: Uint128, + memo: Option, +) -> StdResult<()> { + if let Some(receiver_hash) = recipient_code_hash { + let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); + let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; + + messages.push(callback_msg); + return Ok(()); + } + + let receiver_hash = ReceiverHashStore::may_load(storage, &recipient)?; + if let Some(receiver_hash) = receiver_hash { + let receiver_msg = Snip20ReceiveMsg::new(sender, from, amount, memo, msg); + let callback_msg = receiver_msg.into_cosmos_msg(receiver_hash, recipient)?; + + messages.push(callback_msg); + } + Ok(()) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9c7642bc..bbe941b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,12 @@ mod btbe; mod constants; pub mod contract; mod dwb; +pub mod execute; +pub mod execute_admin; +pub mod execute_deposit_redeem; +pub mod execute_mint_burn; +pub mod execute_transfer_send; +pub mod query; mod gas_tracker; pub mod msg; pub mod receiver; diff --git a/src/query.rs b/src/query.rs new file mode 100644 index 00000000..5241b335 --- /dev/null +++ b/src/query.rs @@ -0,0 +1,573 @@ +use cosmwasm_std::{to_binary, Addr, Binary, CanonicalAddr, Deps, Env, StdError, StdResult, Storage, Uint128, Uint64}; +use rand_chacha::ChaChaRng; +use rand_core::{RngCore, SeedableRng}; +use secret_toolkit::notification::{get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, DirectChannel, FlatDescriptor, GroupChannel, StructDescriptor}; +use secret_toolkit::permit::{RevokedPermits, RevokedPermitsStore}; + +use crate::{btbe::{find_start_bundle, stored_balance, stored_entry, stored_tx_count}, dwb::{DWB, TX_NODES}, msg::{AllowanceGivenResult, AllowanceReceivedResult, QueryAnswer}, notifications::{AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification}, state::{AllowancesStore, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, TOTAL_SUPPLY}, transaction_history::Tx}; + +pub fn query_exchange_rate(storage: &dyn Storage) -> StdResult { + let constants = CONFIG.load(storage)?; + + if constants.deposit_is_enabled || constants.redeem_is_enabled { + let rate: Uint128; + let denom: String; + // if token has more decimals than SCRT, you get magnitudes of SCRT per token + if constants.decimals >= 6 { + rate = Uint128::new(10u128.pow(constants.decimals as u32 - 6)); + denom = "SCRT".to_string(); + // if token has less decimals, you get magnitudes token for SCRT + } else { + rate = Uint128::new(10u128.pow(6 - constants.decimals as u32)); + denom = constants.symbol; + } + return to_binary(&QueryAnswer::ExchangeRate { rate, denom }); + } + to_binary(&QueryAnswer::ExchangeRate { + rate: Uint128::zero(), + denom: String::new(), + }) +} + +pub fn query_token_info(storage: &dyn Storage) -> StdResult { + let constants = CONFIG.load(storage)?; + + let total_supply = if constants.total_supply_is_public { + Some(Uint128::new(TOTAL_SUPPLY.load(storage)?)) + } else { + None + }; + + to_binary(&QueryAnswer::TokenInfo { + name: constants.name, + symbol: constants.symbol, + decimals: constants.decimals, + total_supply, + }) +} + +pub fn query_token_config(storage: &dyn Storage) -> StdResult { + let constants = CONFIG.load(storage)?; + + to_binary(&QueryAnswer::TokenConfig { + public_total_supply: constants.total_supply_is_public, + deposit_enabled: constants.deposit_is_enabled, + redeem_enabled: constants.redeem_is_enabled, + mint_enabled: constants.mint_is_enabled, + burn_enabled: constants.burn_is_enabled, + supported_denoms: constants.supported_denoms, + }) +} + +pub fn query_contract_status(storage: &dyn Storage) -> StdResult { + let contract_status = CONTRACT_STATUS.load(storage)?; + + to_binary(&QueryAnswer::ContractStatus { + status: contract_status, + }) +} + +pub fn query_transactions( + deps: Deps, + account: String, + page: u32, + page_size: u32, +) -> StdResult { + if page_size == 0 { + return Err(StdError::generic_err("invalid page size")); + } + + // Notice that if query_transactions() was called by a viewing-key call, the address of + // 'account' has already been validated. + // The address of 'account' should not be validated if query_transactions() was called by a + // permit call, for compatibility with non-Secret addresses. + let account = Addr::unchecked(account); + let account_raw = deps.api.addr_canonicalize(account.as_str())?; + + let start = page * page_size; + let mut end = start + page_size; // one more than end index + + // first check if there are any transactions in dwb + let dwb = DWB.load(deps.storage)?; + let dwb_index = dwb.recipient_match(&account_raw); + let mut txs_in_dwb = vec![]; + let txs_in_dwb_count = dwb.entries[dwb_index].list_len()?; + if dwb_index > 0 && txs_in_dwb_count > 0 && start < txs_in_dwb_count as u32 { + // skip if start is after buffer entries + let head_node_index = dwb.entries[dwb_index].head_node()?; + + // only look if head node is not null + if head_node_index > 0 { + let head_node = TX_NODES + .add_suffix(&head_node_index.to_be_bytes()) + .load(deps.storage)?; + txs_in_dwb = head_node.to_vec(deps.storage, deps.api)?; + } + } + + //let account_slice = account_raw.as_slice(); + let account_stored_entry = stored_entry(deps.storage, &account_raw)?; + let settled_tx_count = stored_tx_count(deps.storage, &account_stored_entry)?; + let total = txs_in_dwb_count as u32 + settled_tx_count as u32; + if end > total { + end = total; + } + + let mut txs: Vec = vec![]; + + let txs_in_dwb_count = txs_in_dwb_count as u32; + if start < txs_in_dwb_count && end < txs_in_dwb_count { + // option 1, start and end are both in dwb + //println!("OPTION 1"); + txs = txs_in_dwb[start as usize..end as usize].to_vec(); // reverse chronological + } else if start < txs_in_dwb_count && end >= txs_in_dwb_count { + // option 2, start is in dwb and end is in settled txs + // in this case, we do not need to search for txs, just begin at last bundle and move backwards + //println!("OPTION 2"); + txs = txs_in_dwb[start as usize..].to_vec(); // reverse chronological + let mut txs_left = (end - start).saturating_sub(txs.len() as u32); + if let Some(entry) = account_stored_entry { + let tx_bundles_idx_len = entry.history_len()?; + if tx_bundles_idx_len > 0 { + let mut bundle_idx = tx_bundles_idx_len - 1; + loop { + let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; + + // only look if head node is not null + if tx_bundle.head_node > 0 { + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; + + let list_len = tx_bundle.list_len as u32; + if txs_left <= list_len { + txs.extend_from_slice( + &head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize], + ); + break; + } + txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs_left = txs_left.saturating_sub(list_len); + } + if bundle_idx > 0 { + bundle_idx -= 1; + } else { + break; + } + } + } + } + } else if start >= txs_in_dwb_count { + // option 3, start is not in dwb + // in this case, search for where the beginning bundle is using binary search + + // bundle tx offsets are chronological, but we need reverse chronological + // so get the settled start index as if order is reversed + //println!("OPTION 3"); + let settled_start = settled_tx_count + .saturating_sub(start - txs_in_dwb_count) + .saturating_sub(1); + + if let Some((bundle_idx, tx_bundle, start_at)) = + find_start_bundle(deps.storage, &account_raw, settled_start)? + { + let mut txs_left = end - start; + let list_len = tx_bundle.list_len as u32; + if start_at + txs_left <= list_len { + // only look if head node is not null + if tx_bundle.head_node > 0 { + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; + // this first bundle has all the txs we need + txs = head_node.to_vec(deps.storage, deps.api)? + [start_at as usize..(start_at + txs_left) as usize] + .to_vec(); + } + } else { + // only look if head node is not null + if tx_bundle.head_node > 0 { + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; + // get the rest of the txs in this bundle and then go back through history + txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); + txs_left = txs_left.saturating_sub(list_len - start_at); + } + + if bundle_idx > 0 && txs_left > 0 { + // get the next earlier bundle + let mut bundle_idx = bundle_idx - 1; + if let Some(entry) = account_stored_entry { + loop { + let tx_bundle = + entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; + // only look if head node is not null + if tx_bundle.head_node > 0 { + let head_node = TX_NODES + .add_suffix(&tx_bundle.head_node.to_be_bytes()) + .load(deps.storage)?; + let list_len = tx_bundle.list_len as u32; + if txs_left <= list_len { + txs.extend_from_slice( + &head_node.to_vec(deps.storage, deps.api)? + [0..txs_left as usize], + ); + break; + } + txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs_left = txs_left.saturating_sub(list_len); + } + if bundle_idx > 0 { + bundle_idx -= 1; + } else { + break; + } + } + } + } + } + } + } + + // deterministically obfuscate ids so they are not serial to prevent metadata leak + let internal_secret = INTERNAL_SECRET_RELAXED.load(deps.storage)?; + let internal_secret_u64: u64 = u64::from_be_bytes(internal_secret[..8].try_into().unwrap()); + let txs = txs + .iter() + .map(|tx| { + // PRNG(PRNG(serial_id) ^ secret) + let mut rng = ChaChaRng::seed_from_u64(tx.id); + let serial_id_rand = rng.next_u64(); + let new_seed = serial_id_rand ^ internal_secret_u64; + let mut rng = ChaChaRng::seed_from_u64(new_seed); + let new_id = rng.next_u64() >> (64 - 53); + Tx { + id: new_id, + action: tx.action.clone(), + coins: tx.coins.clone(), + memo: tx.memo.clone(), + block_height: tx.block_height, + block_time: tx.block_time, + } + }) + .collect(); + + let result = QueryAnswer::TransactionHistory { + txs, + total: Some(total as u64), + }; + to_binary(&result) +} + +pub fn query_balance(deps: Deps, account: String) -> StdResult { + // Notice that if query_balance() was called by a viewing key call, the address of 'account' + // has already been validated. + // The address of 'account' should not be validated if query_balance() was called by a permit + // call, for compatibility with non-Secret addresses. + let account = Addr::unchecked(account); + let account = deps.api.addr_canonicalize(account.as_str())?; + + let mut amount = stored_balance(deps.storage, &account)?; + let dwb = DWB.load(deps.storage)?; + let dwb_index = dwb.recipient_match(&account); + if dwb_index > 0 { + amount = amount.saturating_add(dwb.entries[dwb_index].amount()? as u128); + } + let amount = Uint128::new(amount); + let response = QueryAnswer::Balance { amount }; + to_binary(&response) +} + +pub fn query_minters(deps: Deps) -> StdResult { + let minters = MintersStore::load(deps.storage)?; + + let response = QueryAnswer::Minters { minters }; + to_binary(&response) +} + +pub fn query_allowance(deps: Deps, owner: String, spender: String) -> StdResult { + // Notice that if query_allowance() was called by a viewing-key call, the addresses of 'owner' + // and 'spender' have already been validated. + // The addresses of 'owner' and 'spender' should not be validated if query_allowance() was + // called by a permit call, for compatibility with non-Secret addresses. + let owner = Addr::unchecked(owner); + let spender = Addr::unchecked(spender); + + let allowance = AllowancesStore::load(deps.storage, &owner, &spender); + + let response = QueryAnswer::Allowance { + owner, + spender, + allowance: Uint128::new(allowance.amount), + expiration: allowance.expiration, + }; + to_binary(&response) +} + +pub fn query_allowances_given( + deps: Deps, + owner: String, + page: u32, + page_size: u32, +) -> StdResult { + // Notice that if query_all_allowances_given() was called by a viewing-key call, + // the address of 'owner' has already been validated. + // The addresses of 'owner' should not be validated if query_all_allowances_given() was + // called by a permit call, for compatibility with non-Secret addresses. + let owner = Addr::unchecked(owner); + + let all_allowances = + AllowancesStore::all_allowances(deps.storage, &owner, page, page_size).unwrap_or(vec![]); + + let allowances_result = all_allowances + .into_iter() + .map(|(spender, allowance)| AllowanceGivenResult { + spender, + allowance: Uint128::from(allowance.amount), + expiration: allowance.expiration, + }) + .collect(); + + let response = QueryAnswer::AllowancesGiven { + owner: owner.clone(), + allowances: allowances_result, + count: AllowancesStore::num_allowances(deps.storage, &owner), + }; + to_binary(&response) +} + +pub fn query_allowances_received( + deps: Deps, + spender: String, + page: u32, + page_size: u32, +) -> StdResult { + // Notice that if query_all_allowances_received() was called by a viewing-key call, + // the address of 'spender' has already been validated. + // The addresses of 'spender' should not be validated if query_all_allowances_received() was + // called by a permit call, for compatibility with non-Secret addresses. + let spender = Addr::unchecked(spender); + + let all_allowed = + AllowancesStore::all_allowed(deps.storage, &spender, page, page_size).unwrap_or(vec![]); + + let allowances = all_allowed + .into_iter() + .map(|(owner, allowance)| AllowanceReceivedResult { + owner, + allowance: Uint128::from(allowance.amount), + expiration: allowance.expiration, + }) + .collect(); + + let response = QueryAnswer::AllowancesReceived { + spender: spender.clone(), + allowances, + count: AllowancesStore::num_allowed(deps.storage, &spender), + }; + to_binary(&response) +} + +// ***************** +// SNIP-24.1 query function +// ***************** + +pub fn query_list_permit_revocations(deps: Deps, account: &str) -> StdResult { + let revocations = RevokedPermits::list_revocations( + deps.storage, + account + )?; + + to_binary(&QueryAnswer::ListPermitRevocations { revocations }) +} + +// ***************** +// SNIP-52 query functions +// ***************** + +/// +/// ListChannels query +/// +/// Public query to list all notification channels. +/// +pub fn query_list_channels(deps: Deps) -> StdResult { + let channels: Vec = CHANNELS + .iter(deps.storage)? + .map(|channel| channel.unwrap()) + .collect(); + to_binary(&QueryAnswer::ListChannels { channels }) +} + +/// +/// ChannelInfo query +/// +/// Authenticated query allows clients to obtain the seed, +/// and Notification ID of an event for a specific tx_hash, for a specific channel. +/// +pub fn query_channel_info( + deps: Deps, + env: Env, + channels: Vec, + txhash: Option, + sender_raw: CanonicalAddr, +) -> StdResult { + let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; + let secret = secret.as_slice(); + let seed = get_seed(&sender_raw, secret)?; + let mut channels_data = vec![]; + for channel in channels { + let answer_id; + if let Some(tx_hash) = &txhash { + answer_id = Some(notification_id(&seed, &channel, tx_hash)?); + } else { + answer_id = None; + } + match channel.as_str() { + RecvdNotification::CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "txhash".to_string(), + channel, + answer_id, + parameters: None, + data: None, + next_id: None, + counter: None, + cddl: Some(RecvdNotification::CDDL_SCHEMA.to_string()), + }; + channels_data.push(channel_info_data); + } + SpentNotification::CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "txhash".to_string(), + channel, + answer_id, + parameters: None, + data: None, + next_id: None, + counter: None, + cddl: Some(SpentNotification::CDDL_SCHEMA.to_string()), + }; + channels_data.push(channel_info_data); + } + AllowanceNotification::CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "txhash".to_string(), + channel, + answer_id, + parameters: None, + data: None, + next_id: None, + counter: None, + cddl: Some(AllowanceNotification::CDDL_SCHEMA.to_string()), + }; + channels_data.push(channel_info_data); + } + MultiRecvdNotification::CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "bloom".to_string(), + channel, + answer_id, + parameters: Some(BloomParameters { + m: MultiRecvdNotification::BLOOM_M, + k: MultiRecvdNotification::BLOOM_K, + h: "sha256".to_string(), + }), + data: Some(Descriptor { + r#type: format!("packet[{}]", MultiRecvdNotification::BLOOM_N), + version: "1".to_string(), + packet_size: MultiRecvdNotification::PACKET_SIZE as u32, + data: StructDescriptor { + r#type: "struct".to_string(), + label: "transfer".to_string(), + members: vec![ + FlatDescriptor { + r#type: "uint64".to_string(), + label: "flagsAndAmount".to_string(), + description: Some( + "Bit field of [0]: non-empty memo; [2]: sender is owner; [2..]: uint62 transfer amount in base denomination".to_string(), + ), + }, + FlatDescriptor { + r#type: "bytes8".to_string(), + label: "ownerId".to_string(), + description: Some( + "The last 8 bytes of the owner's canonical address".to_string(), + ), + }, + ], + }, + }), + counter: None, + next_id: None, + cddl: None, + }; + channels_data.push(channel_info_data); + } + MultiSpentNotification::CHANNEL_ID => { + let channel_info_data = ChannelInfoData { + mode: "bloom".to_string(), + channel, + answer_id, + parameters: Some(BloomParameters { + m: MultiSpentNotification::BLOOM_M, + k: MultiSpentNotification::BLOOM_K, + h: "sha256".to_string(), + }), + data: Some(Descriptor { + r#type: format!("packet[{}]", MultiSpentNotification::BLOOM_N), + version: "1".to_string(), + packet_size: MultiSpentNotification::PACKET_SIZE as u32, + data: StructDescriptor { + r#type: "struct".to_string(), + label: "transfer".to_string(), + members: vec![ + FlatDescriptor { + r#type: "uint64".to_string(), + label: "flagsAndAmount".to_string(), + description: Some( + "Bit field of [0]: non-empty memo; [1]: reserved; [2..] uint62 transfer amount in base denomination".to_string(), + ), + }, + FlatDescriptor { + r#type: "bytes8".to_string(), + label: "recipientId".to_string(), + description: Some( + "The last 8 bytes of the recipient's canonical address".to_string(), + ), + }, + FlatDescriptor { + r#type: "uint64".to_string(), + label: "balance".to_string(), + description: Some( + "Spender's new balance after the transfer".to_string(), + ), + }, + ], + }, + }), + counter: None, + next_id: None, + cddl: None, + }; + channels_data.push(channel_info_data); + } + _ => { + return Err(StdError::generic_err(format!( + "`{}` channel is undefined", + channel + ))); + } + } + } + + to_binary(&QueryAnswer::ChannelInfo { + as_of_block: Uint64::from(env.block.height), + channels: channels_data, + seed, + }) +} + +// ***************** +// End SNIP-52 query functions +// ***************** From f3d7491a3ef2af2b5815e7e18bf2df2e9baa7a5f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Dec 2024 10:31:39 +1300 Subject: [PATCH 72/87] set notifications enabled in instantiate --- src/contract.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/contract.rs b/src/contract.rs index 9edebae2..a1279b58 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -31,7 +31,7 @@ use crate::notifications::{ AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification }; use crate::state::{ - Config, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, TOTAL_SUPPLY + Config, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; @@ -120,6 +120,8 @@ pub fn instantiate( CHANNELS.insert(deps.storage, &channel)?; } + NOTIFICATIONS_ENABLED.save(deps.storage, &true)?; + let mut rng = ContractPrng::new(rng_seed.as_slice(), &sha_256(&msg.prng_seed.0)); for balance in initial_balances { let amount = balance.amount.u128(); From 16fb3446b187b3a90bc47040d4673d4d476d1a31 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Dec 2024 11:48:15 +1300 Subject: [PATCH 73/87] make unit tests pass --- src/contract.rs | 51 +++++++++++++++++++++----------------------- src/execute.rs | 5 +++++ src/notifications.rs | 20 ++++++++++++++++- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index a1279b58..89235cde 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1330,7 +1330,7 @@ mod tests { //println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { - id: 168, + id: 8845804139732984, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1345,7 +1345,7 @@ mod tests { block_height: 12345, }, Tx { - id: 167, + id: 3692043167097969, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1360,7 +1360,7 @@ mod tests { block_height: 12345, }, Tx { - id: 166, + id: 3808363917805648, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1396,7 +1396,7 @@ mod tests { //println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { - id: 120, + id: 7611337451915155, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1411,7 +1411,7 @@ mod tests { block_height: 12345, }, Tx { - id: 119, + id: 7288023700190802, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1426,7 +1426,7 @@ mod tests { block_height: 12345, }, Tx { - id: 118, + id: 6449330804541894, action: TxAction::Transfer { from: Addr::unchecked("alice"), sender: Addr::unchecked("alice"), @@ -1441,7 +1441,7 @@ mod tests { block_height: 12345, }, Tx { - id: 117, + id: 1600285134972748, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1456,7 +1456,7 @@ mod tests { block_height: 12345, }, Tx { - id: 116, + id: 7899356969158249, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1471,7 +1471,7 @@ mod tests { block_height: 12345, }, Tx { - id: 115, + id: 5178919937687208, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1510,7 +1510,7 @@ mod tests { //println!("transfers: {transfers:?}"); let expected_transfers = vec![ Tx { - id: 69, + id: 7879504399954008, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1525,7 +1525,7 @@ mod tests { block_height: 12345, }, Tx { - id: 68, + id: 7625837293820843, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1540,7 +1540,7 @@ mod tests { block_height: 12345, }, Tx { - id: 6, + id: 2105964828411645, action: TxAction::Transfer { from: Addr::unchecked("alice"), sender: Addr::unchecked("alice"), @@ -1555,7 +1555,7 @@ mod tests { block_height: 12345, }, Tx { - id: 4, + id: 5298675660782133, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1570,7 +1570,7 @@ mod tests { block_height: 12345, }, Tx { - id: 2, + id: 3942814133456943, action: TxAction::Transfer { from: Addr::unchecked("bob"), sender: Addr::unchecked("bob"), @@ -1585,14 +1585,11 @@ mod tests { block_height: 12345, }, ]; + //let transfers_len = transfers.len(); //println!("transfers.len(): {transfers_len}"); - assert_eq!(transfers, expected_transfers); - // - // - // - // + assert_eq!(transfers, expected_transfers); // now try invalid transfer let handle_msg = ExecuteMsg::Transfer { @@ -4801,7 +4798,7 @@ mod tests { use crate::transaction_history::TxAction; let expected_transfers = [ Tx { - id: 8, + id: 8735437960206903, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), sender: Addr::unchecked("bob".to_string()), @@ -4816,7 +4813,7 @@ mod tests { block_height: 12345, }, Tx { - id: 7, + id: 6519057655056815, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), sender: Addr::unchecked("bob".to_string()), @@ -4831,7 +4828,7 @@ mod tests { block_height: 12345, }, Tx { - id: 6, + id: 2105964828411645, action: TxAction::Transfer { from: Addr::unchecked("bob".to_string()), sender: Addr::unchecked("bob".to_string()), @@ -4846,7 +4843,7 @@ mod tests { block_height: 12345, }, Tx { - id: 5, + id: 7517649082682890, action: TxAction::Deposit {}, coins: Coin { denom: "uscrt".to_string(), @@ -4857,7 +4854,7 @@ mod tests { block_height: 12345, }, Tx { - id: 4, + id: 5298675660782133, action: TxAction::Mint { minter: Addr::unchecked("admin".to_string()), recipient: Addr::unchecked("bob".to_string()), @@ -4871,7 +4868,7 @@ mod tests { block_height: 12345, }, Tx { - id: 3, + id: 3863562430182029, action: TxAction::Redeem {}, coins: Coin { denom: "SECSEC".to_string(), @@ -4882,7 +4879,7 @@ mod tests { block_height: 12345, }, Tx { - id: 2, + id: 3942814133456943, action: TxAction::Burn { burner: Addr::unchecked("bob".to_string()), owner: Addr::unchecked("bob".to_string()), @@ -4896,7 +4893,7 @@ mod tests { block_height: 12345, }, Tx { - id: 1, + id: 5746099005188254, action: TxAction::Mint { minter: Addr::unchecked("admin".to_string()), recipient: Addr::unchecked("bob".to_string()), diff --git a/src/execute.rs b/src/execute.rs index 09b391fe..1861fa77 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -121,6 +121,8 @@ pub fn try_increase_allowance( spender: spender.clone(), allowance: Uint128::from(new_amount), })?); + + println!("Got 1 {:?}", resp); if NOTIFICATIONS_ENABLED.load(deps.storage)? { let notification = Notification::new ( @@ -137,6 +139,9 @@ pub fn try_increase_allowance( notification.id_plaintext(), notification.data_plaintext() ); + + println!("Got 2 {:?}", resp); + } Ok(resp) diff --git a/src/notifications.rs b/src/notifications.rs index 2ceafa59..4c20637c 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, Response, StdResult}; +use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, Response, StdError, StdResult}; use primitive_types::{U256, U512}; use secret_toolkit::notification::{get_seed, notification_id, xor_bytes, EncoderExt, CBL_ADDRESS, CBL_ARRAY_SHORT, CBL_BIGNUM_U64, CBL_TIMESTAMP, CBL_U8, Notification, DirectChannel, GroupChannel}; use minicbor::Encoder; @@ -35,8 +35,14 @@ pub struct RecvdNotification { /// ``` impl DirectChannel for RecvdNotification { const CHANNEL_ID: &'static str = "recvd"; + #[cfg(test)] + const CDDL_SCHEMA: &'static str = "recvd=[amount:biguint .size 8,sender:bstr .size 54,memo_len:uint .size 1]"; + #[cfg(not(test))] const CDDL_SCHEMA: &'static str = "recvd=[amount:biguint .size 8,sender:bstr .size 20,memo_len:uint .size 1]"; const ELEMENTS: u64 = 3; + #[cfg(test)] + const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + 55 + CBL_U8; + #[cfg(not(test))] const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_ADDRESS + CBL_U8; fn encode_cbor(&self, api: &dyn Api, encoder: &mut Encoder<&mut [u8]>) -> StdResult<()> { @@ -79,8 +85,14 @@ pub struct SpentNotification { impl DirectChannel for SpentNotification { const CHANNEL_ID: &'static str = "spent"; + #[cfg(test)] + const CDDL_SCHEMA: &'static str = "spent=[amount:biguint .size 8,actions:uint .size 1,recipient:bstr .size 54,balance:biguint .size 8]"; + #[cfg(not(test))] const CDDL_SCHEMA: &'static str = "spent=[amount:biguint .size 8,actions:uint .size 1,recipient:bstr .size 20,balance:biguint .size 8]"; const ELEMENTS: u64 = 4; + #[cfg(test)] + const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_U8 + 55 + CBL_BIGNUM_U64; + #[cfg(not(test))] const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_U8 + CBL_ADDRESS + CBL_BIGNUM_U64; fn encode_cbor(&self, api: &dyn Api, encoder: &mut Encoder<&mut [u8]>) -> StdResult<()> { @@ -122,8 +134,14 @@ pub struct AllowanceNotification { impl DirectChannel for AllowanceNotification { const CHANNEL_ID: &'static str = "allowance"; + #[cfg(test)] + const CDDL_SCHEMA: &'static str = "allowance=[amount:biguint .size 8,allower:bstr .size 54,expiration:uint .size 8]"; + #[cfg(not(test))] const CDDL_SCHEMA: &'static str = "allowance=[amount:biguint .size 8,allower:bstr .size 20,expiration:uint .size 8]"; const ELEMENTS: u64 = 3; + #[cfg(test)] + const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + 55 + CBL_TIMESTAMP; + #[cfg(not(test))] const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_ADDRESS + CBL_TIMESTAMP; fn encode_cbor(&self, api: &dyn Api, encoder: &mut Encoder<&mut [u8]>) -> StdResult<()> { From a1a6125322283e3d6c24fc83d3ffecb27ddbd8f2 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Dec 2024 12:53:28 +1300 Subject: [PATCH 74/87] unit tests pass --- src/btbe.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/btbe.rs b/src/btbe.rs index 999fee20..92a5790d 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -861,14 +861,14 @@ mod tests { let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(storage).unwrap(); assert_eq!(btbe_node_count, 1); - for i in 1..=128 { + for i in 1..=64 { let canonical = deps .api .addr_canonicalize(Addr::unchecked(format!("{i}zzzzzz")).as_str()) .unwrap(); let mut entry = StoredEntry::new(&canonical).unwrap(); - entry.save_hash_cache(storage).unwrap(); + let _ = entry.save_hash_cache(storage).unwrap(); assert_eq!(entry.address().unwrap(), canonical); assert_eq!(entry.balance().unwrap(), 0_u64); @@ -893,13 +893,13 @@ mod tests { assert_eq!(bit_pos, 0); } - // btbe trie should split nodes when get to 129th entry + // btbe trie should split nodes when get to 65th entry let canonical = deps .api .addr_canonicalize(Addr::unchecked(format!("bob")).as_str()) .unwrap(); let mut entry = StoredEntry::new(&canonical).unwrap(); - entry.save_hash_cache(storage); + let _ = entry.save_hash_cache(storage); assert_eq!(entry.address().unwrap(), canonical); assert_eq!(entry.balance().unwrap(), 0_u64); @@ -948,10 +948,10 @@ mod tests { BitwiseTrieNode { left: 0, right: 0, - bucket: 3, + bucket: 2, } ); - assert_eq!(node_id, 3); + assert_eq!(node_id, 2); assert_eq!(bit_pos, 1); let canonical_entry = stored_entry(&deps.storage, &canonical).unwrap().unwrap(); From 70135b20b52a81b01c1bb35e713c02c56ca141bb Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Dec 2024 13:04:18 +1300 Subject: [PATCH 75/87] formatting --- src/btbe.rs | 10 +++------- src/contract.rs | 3 ++- src/execute.rs | 5 ----- src/execute_deposit_redeem.rs | 5 ++++- src/execute_transfer_send.rs | 5 ++++- src/notifications.rs | 7 +++++-- src/query.rs | 13 +++++++++++-- 7 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/btbe.rs b/src/btbe.rs index 92a5790d..c1e03049 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -12,8 +12,9 @@ use secret_toolkit_crypto::hkdf_sha_256; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use crate::{constants::{ADDRESS_BYTES_LEN, IMPOSSIBLE_ADDR}, dwb::constant_time_if_else_u32, state::{safe_add, safe_add_u64, INTERNAL_SECRET_SENSITIVE}}; -use crate::dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle}; +use crate::constants::{ADDRESS_BYTES_LEN, IMPOSSIBLE_ADDR}; +use crate::dwb::{amount_u64, constant_time_if_else_u32, DelayedWriteBufferEntry, TxBundle}; +use crate::state::{safe_add, safe_add_u64, INTERNAL_SECRET_SENSITIVE}; #[cfg(feature = "gas_tracking")] use crate::gas_tracker::GasTracker; @@ -816,9 +817,6 @@ mod tests { "Init failed: {}", init_result.err().unwrap() ); - let _env = mock_env(); - let _info = mock_info("bob", &[]); - let storage = &mut deps.storage; let canonical = deps .api @@ -852,8 +850,6 @@ mod tests { "Init failed: {}", init_result.err().unwrap() ); - let env = mock_env(); - let _info = mock_info("bob", &[]); let storage = &mut deps.storage; let _ = initialize_btbe(storage).unwrap(); diff --git a/src/contract.rs b/src/contract.rs index 89235cde..927ea312 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -31,7 +31,8 @@ use crate::notifications::{ AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification }; use crate::state::{ - Config, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY + Config, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, + TOTAL_SUPPLY, }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; diff --git a/src/execute.rs b/src/execute.rs index 1861fa77..09b391fe 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -121,8 +121,6 @@ pub fn try_increase_allowance( spender: spender.clone(), allowance: Uint128::from(new_amount), })?); - - println!("Got 1 {:?}", resp); if NOTIFICATIONS_ENABLED.load(deps.storage)? { let notification = Notification::new ( @@ -139,9 +137,6 @@ pub fn try_increase_allowance( notification.id_plaintext(), notification.data_plaintext() ); - - println!("Got 2 {:?}", resp); - } Ok(resp) diff --git a/src/execute_deposit_redeem.rs b/src/execute_deposit_redeem.rs index 394121e8..681119c9 100644 --- a/src/execute_deposit_redeem.rs +++ b/src/execute_deposit_redeem.rs @@ -1,4 +1,7 @@ -use cosmwasm_std::{to_binary, BankMsg, BlockInfo, CanonicalAddr, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; +use cosmwasm_std::{ + to_binary, BankMsg, BlockInfo, CanonicalAddr, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, Storage, Uint128, +}; use secret_toolkit_crypto::ContractPrng; use crate::dwb::DWB; diff --git a/src/execute_transfer_send.rs b/src/execute_transfer_send.rs index 4d6988d8..c33ebcbd 100644 --- a/src/execute_transfer_send.rs +++ b/src/execute_transfer_send.rs @@ -1,4 +1,7 @@ -use cosmwasm_std::{to_binary, Addr, Binary, BlockInfo, CanonicalAddr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; +use cosmwasm_std::{ + to_binary, Addr, Binary, BlockInfo, CanonicalAddr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, + Uint128 +}; use secret_toolkit::notification::Notification; use secret_toolkit_crypto::ContractPrng; diff --git a/src/notifications.rs b/src/notifications.rs index 4c20637c..2714ea35 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; -use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, Response, StdError, StdResult}; +use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, Response, StdResult}; use primitive_types::{U256, U512}; -use secret_toolkit::notification::{get_seed, notification_id, xor_bytes, EncoderExt, CBL_ADDRESS, CBL_ARRAY_SHORT, CBL_BIGNUM_U64, CBL_TIMESTAMP, CBL_U8, Notification, DirectChannel, GroupChannel}; +use secret_toolkit::notification::{ + get_seed, notification_id, xor_bytes, EncoderExt, CBL_ADDRESS, CBL_ARRAY_SHORT, CBL_BIGNUM_U64, CBL_TIMESTAMP, CBL_U8, Notification, + DirectChannel, GroupChannel, +}; use minicbor::Encoder; use secret_toolkit_crypto::{hkdf_sha_512, sha_256}; use serde::{Deserialize, Serialize}; diff --git a/src/query.rs b/src/query.rs index 5241b335..b311bb5e 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,10 +1,19 @@ use cosmwasm_std::{to_binary, Addr, Binary, CanonicalAddr, Deps, Env, StdError, StdResult, Storage, Uint128, Uint64}; use rand_chacha::ChaChaRng; use rand_core::{RngCore, SeedableRng}; -use secret_toolkit::notification::{get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, DirectChannel, FlatDescriptor, GroupChannel, StructDescriptor}; +use secret_toolkit::notification::{ + get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, DirectChannel, FlatDescriptor, GroupChannel, StructDescriptor, +}; use secret_toolkit::permit::{RevokedPermits, RevokedPermitsStore}; -use crate::{btbe::{find_start_bundle, stored_balance, stored_entry, stored_tx_count}, dwb::{DWB, TX_NODES}, msg::{AllowanceGivenResult, AllowanceReceivedResult, QueryAnswer}, notifications::{AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification}, state::{AllowancesStore, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, TOTAL_SUPPLY}, transaction_history::Tx}; +use crate::btbe::{find_start_bundle, stored_balance, stored_entry, stored_tx_count}; +use crate::dwb::{DWB, TX_NODES}; +use crate::msg::{AllowanceGivenResult, AllowanceReceivedResult, QueryAnswer}; +use crate::notifications::{AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification}; +use crate::state::{ + AllowancesStore, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, TOTAL_SUPPLY +}; +use crate::transaction_history::Tx; pub fn query_exchange_rate(storage: &dyn Storage) -> StdResult { let constants = CONFIG.load(storage)?; From 3c8418b227496198067ac98b73eab06b74020114 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Dec 2024 13:09:34 +1300 Subject: [PATCH 76/87] cargo fmt --- src/btbe.rs | 37 +++-- src/constants.rs | 2 +- src/contract.rs | 135 +++++++++++------ src/dwb.rs | 8 +- src/execute.rs | 118 +++++++-------- src/execute_admin.rs | 9 +- src/execute_deposit_redeem.rs | 6 +- src/execute_mint_burn.rs | 55 ++++--- src/execute_transfer_send.rs | 276 ++++++++++++++++------------------ src/lib.rs | 4 +- src/msg.rs | 17 ++- src/notifications.rs | 89 +++++------ src/query.rs | 21 ++- src/state.rs | 2 +- src/strings.rs | 3 +- 15 files changed, 395 insertions(+), 387 deletions(-) diff --git a/src/btbe.rs b/src/btbe.rs index c1e03049..21e4717e 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -14,9 +14,9 @@ use serde_big_array::BigArray; use crate::constants::{ADDRESS_BYTES_LEN, IMPOSSIBLE_ADDR}; use crate::dwb::{amount_u64, constant_time_if_else_u32, DelayedWriteBufferEntry, TxBundle}; -use crate::state::{safe_add, safe_add_u64, INTERNAL_SECRET_SENSITIVE}; #[cfg(feature = "gas_tracking")] use crate::gas_tracker::GasTracker; +use crate::state::{safe_add, safe_add_u64, INTERNAL_SECRET_SENSITIVE}; pub const KEY_BTBE_ENTRY_HISTORY: &[u8] = b"btbe-entry-hist"; pub const KEY_BTBE_BUCKETS_COUNT: &[u8] = b"btbe-buckets-cnt"; @@ -37,11 +37,11 @@ const BTBE_BUCKET_CACHE_BYTES: usize = 0; const_assert!(BTBE_BUCKET_BALANCE_BYTES <= U128_BYTES); const_assert!(BTBE_BUCKET_HISTORY_BYTES <= U32_BYTES); -const BTBE_BUCKET_ENTRY_BYTES: usize = - BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES +const BTBE_BUCKET_ENTRY_BYTES: usize = BTBE_BUCKET_ADDRESS_BYTES + + BTBE_BUCKET_BALANCE_BYTES + + BTBE_BUCKET_HISTORY_BYTES + BTBE_BUCKET_CACHE_BYTES; - /// A `StoredEntry` consists of the address, balance, and tx bundle history length in a byte array representation. /// The methods of the struct implementation also handle pushing and getting the tx bundle history in a simplified /// append store. @@ -150,7 +150,8 @@ impl StoredEntry { 32, )?; - let start = BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES; + let start = + BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES; let end = start + BTBE_BUCKET_CACHE_BYTES; self.0[start..end].copy_from_slice(&hash_bytes.as_slice()[0..BTBE_BUCKET_CACHE_BYTES]); Ok(()) @@ -163,7 +164,10 @@ impl StoredEntry { // bit pos is cached if bit_pos < (BTBE_BUCKET_CACHE_BYTES << 3) { // select the byte from cache corresponding to this bit position - byte = self.0[BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES + (bit_pos >> 3)]; + byte = self.0[BTBE_BUCKET_ADDRESS_BYTES + + BTBE_BUCKET_BALANCE_BYTES + + BTBE_BUCKET_HISTORY_BYTES + + (bit_pos >> 3)]; } // not cached; calculate on the fly else { @@ -181,7 +185,6 @@ impl StoredEntry { // extract value at bit position and turn into bool return Ok(((byte >> (7 - (bit_pos % 8))) & 1) != 0); - } pub fn merge_dwb_entry( @@ -218,9 +221,9 @@ impl StoredEntry { // position of last tx bundle to read let bundle_pos = constant_time_if_else_u32( - empty_history, - 0u32, - history_len.wrapping_sub(1) // constant-time subtraction with underflow + empty_history, + 0u32, + history_len.wrapping_sub(1), // constant-time subtraction with underflow ); // peek at the last tx bundle added (read the dummy one if its void) @@ -238,7 +241,7 @@ impl StoredEntry { let bundle_offset = constant_time_if_else_u32( empty_history, 0u32, - last_tx_bundle.offset + (last_tx_bundle.list_len as u32) + last_tx_bundle.offset + (last_tx_bundle.list_len as u32), ); // create new tx bundle @@ -305,7 +308,7 @@ impl StoredEntry { fn push_tx_bundle(&mut self, storage: &mut dyn Storage, bundle: &TxBundle) -> StdResult<()> { let len = self.history_len()?; self.set_tx_bundle_at_unchecked(storage, len, bundle)?; - // if the head node is null, then add this as a ghost bundle that does not contribute to len of list, + // if the head node is null, then add this as a ghost bundle that does not contribute to len of list, // and will be overwritten next time let len_add = constant_time_if_else_u32((bundle.head_node == 0) as u32, 0, 1); self.set_history_len(len.saturating_add(len_add))?; @@ -578,8 +581,7 @@ pub fn settle_dwb_entry( let mut bucket_id = node.bucket; // search for an existing entry - if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(address) - { + if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(address) { // found existing entry // merge amount and history from dwb entry found_entry.merge_dwb_entry(storage, &dwb_entry, amount_spent)?; @@ -588,16 +590,13 @@ pub fn settle_dwb_entry( #[cfg(feature = "gas_tracking")] group1.logf(format!( "merged {} into node #{}, bucket #{} at position {} ", - address, - node_id, - bucket_id, - idx + address, node_id, bucket_id, idx )); // save updated bucket to storage node.set_and_save_bucket(storage, bucket)?; } - // nothing was stored yet + // nothing was stored yet else { // need to insert new entry // create new stored balance entry diff --git a/src/constants.rs b/src/constants.rs index 0f1bde69..d3705aaa 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -15,4 +15,4 @@ pub const IMPOSSIBLE_ADDR: [u8; ADDRESS_BYTES_LEN] = [ pub const IMPOSSIBLE_ADDR: [u8; ADDRESS_BYTES_LEN] = [ 0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC, 0xFF, 0xA2, 0xFE, 0xEF, -]; \ No newline at end of file +]; diff --git a/src/contract.rs b/src/contract.rs index 927ea312..f0f95957 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,18 +1,19 @@ +#[cfg(feature = "gas_evaporation")] +use cosmwasm_std::Api; /// This contract implements SNIP-20 standard: /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md use cosmwasm_std::{ - entry_point, to_binary, Binary, - Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, }; -#[cfg(feature = "gas_evaporation")] -use cosmwasm_std::Api; -use secret_toolkit::notification::{GroupChannel, DirectChannel,}; +use secret_toolkit::notification::{DirectChannel, GroupChannel}; use secret_toolkit::permit::{Permit, TokenPermissions}; use secret_toolkit::utils::{pad_handle_result, pad_query_result}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; use secret_toolkit_crypto::{hkdf_sha_256, sha_256, ContractPrng}; -use crate::{execute, execute_admin, execute_deposit_redeem, execute_mint_burn, execute_transfer_send, query}; +use crate::{ + execute, execute_admin, execute_deposit_redeem, execute_mint_burn, execute_transfer_send, query, +}; #[cfg(feature = "gas_tracking")] use crate::dwb::log_dwb; @@ -28,11 +29,12 @@ use crate::msg::{ ContractStatusLevel, ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, }; use crate::notifications::{ - AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification + AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, + SpentNotification, }; use crate::state::{ - Config, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, - TOTAL_SUPPLY, + Config, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, + INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY, }; use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG; @@ -233,8 +235,12 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S let response = match msg.clone() { // Native - ExecuteMsg::Deposit { .. } => execute_deposit_redeem::try_deposit(deps, env, info, &mut rng), - ExecuteMsg::Redeem { amount, denom, .. } => execute_deposit_redeem::try_redeem(deps, env, info, amount, denom), + ExecuteMsg::Deposit { .. } => { + execute_deposit_redeem::try_deposit(deps, env, info, &mut rng) + } + ExecuteMsg::Redeem { amount, denom, .. } => { + execute_deposit_redeem::try_redeem(deps, env, info, amount, denom) + } // Base ExecuteMsg::Transfer { @@ -242,7 +248,9 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => execute_transfer_send::try_transfer(deps, env, info, &mut rng, recipient, amount, memo), + } => { + execute_transfer_send::try_transfer(deps, env, info, &mut rng, recipient, amount, memo) + } ExecuteMsg::Send { recipient, recipient_code_hash, @@ -264,12 +272,18 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::BatchTransfer { actions, .. } => { execute_transfer_send::try_batch_transfer(deps, env, info, &mut rng, actions) } - ExecuteMsg::BatchSend { actions, .. } => execute_transfer_send::try_batch_send(deps, env, info, &mut rng, actions), - ExecuteMsg::Burn { amount, memo, .. } => execute_mint_burn::try_burn(deps, env, info, amount, memo), + ExecuteMsg::BatchSend { actions, .. } => { + execute_transfer_send::try_batch_send(deps, env, info, &mut rng, actions) + } + ExecuteMsg::Burn { amount, memo, .. } => { + execute_mint_burn::try_burn(deps, env, info, amount, memo) + } ExecuteMsg::RegisterReceive { code_hash, .. } => { execute::try_register_receive(deps, info, code_hash) } - ExecuteMsg::CreateViewingKey { entropy, .. } => execute::try_create_key(deps, env, info, entropy, &mut rng), + ExecuteMsg::CreateViewingKey { entropy, .. } => { + execute::try_create_key(deps, env, info, entropy, &mut rng) + } ExecuteMsg::SetViewingKey { key, .. } => execute::try_set_key(deps, info, key), // Allowance @@ -291,7 +305,9 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S amount, memo, .. - } => execute_transfer_send::try_transfer_from(deps, &env, info, &mut rng, owner, recipient, amount, memo), + } => execute_transfer_send::try_transfer_from( + deps, &env, info, &mut rng, owner, recipient, amount, memo, + ), ExecuteMsg::SendFrom { owner, recipient, @@ -324,7 +340,9 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S memo, .. } => execute_mint_burn::try_burn_from(deps, &env, info, owner, amount, memo), - ExecuteMsg::BatchBurnFrom { actions, .. } => execute_mint_burn::try_batch_burn_from(deps, &env, info, actions), + ExecuteMsg::BatchBurnFrom { actions, .. } => { + execute_mint_burn::try_batch_burn_from(deps, &env, info, actions) + } // Mint ExecuteMsg::Mint { @@ -333,17 +351,25 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S memo, .. } => execute_mint_burn::try_mint(deps, env, info, &mut rng, recipient, amount, memo), - ExecuteMsg::BatchMint { actions, .. } => execute_mint_burn::try_batch_mint(deps, env, info, &mut rng, actions), + ExecuteMsg::BatchMint { actions, .. } => { + execute_mint_burn::try_batch_mint(deps, env, info, &mut rng, actions) + } // SNIP-24 - ExecuteMsg::RevokePermit { permit_name, .. } => execute::revoke_permit(deps, info, permit_name), + ExecuteMsg::RevokePermit { permit_name, .. } => { + execute::revoke_permit(deps, info, permit_name) + } // SNIP-24.1 - ExecuteMsg::RevokeAllPermits { interval, .. } => execute::revoke_all_permits(deps, info, interval), - ExecuteMsg::DeletePermitRevocation { revocation_id, .. } => execute::delete_permit_revocation(deps, info, revocation_id), + ExecuteMsg::RevokeAllPermits { interval, .. } => { + execute::revoke_all_permits(deps, info, interval) + } + ExecuteMsg::DeletePermitRevocation { revocation_id, .. } => { + execute::delete_permit_revocation(deps, info, revocation_id) + } // Admin functions - _ => admin_execute(deps, info, msg) + _ => admin_execute(deps, info, msg), }; let padded_result = pad_handle_result(response, RESPONSE_BLOCK_SIZE); @@ -366,20 +392,32 @@ pub fn admin_execute(deps: DepsMut, info: MessageInfo, msg: ExecuteMsg) -> StdRe } match msg { - ExecuteMsg::ChangeAdmin { address, .. } => execute_admin::change_admin(deps, &mut config, address), - ExecuteMsg::SetContractStatus { level, .. } => execute_admin::set_contract_status(deps, level), - ExecuteMsg::AddMinters { minters, .. } => execute_admin::add_minters(deps, &config, minters), - ExecuteMsg::RemoveMinters { minters, .. } => execute_admin::remove_minters(deps, &config, minters), - ExecuteMsg::SetMinters { minters, .. } => execute_admin::set_minters(deps, &config, minters), - ExecuteMsg::AddSupportedDenoms { denoms, .. } => execute_admin::add_supported_denoms(deps, &mut config, denoms), + ExecuteMsg::ChangeAdmin { address, .. } => { + execute_admin::change_admin(deps, &mut config, address) + } + ExecuteMsg::SetContractStatus { level, .. } => { + execute_admin::set_contract_status(deps, level) + } + ExecuteMsg::AddMinters { minters, .. } => { + execute_admin::add_minters(deps, &config, minters) + } + ExecuteMsg::RemoveMinters { minters, .. } => { + execute_admin::remove_minters(deps, &config, minters) + } + ExecuteMsg::SetMinters { minters, .. } => { + execute_admin::set_minters(deps, &config, minters) + } + ExecuteMsg::AddSupportedDenoms { denoms, .. } => { + execute_admin::add_supported_denoms(deps, &mut config, denoms) + } ExecuteMsg::RemoveSupportedDenoms { denoms, .. } => { execute_admin::remove_supported_denoms(deps, &mut config, denoms) - }, + } // SNIP-52 ExecuteMsg::SetNotificationStatus { enabled, .. } => { execute_admin::set_notification_status(deps, enabled) - }, + } _ => panic!("This execute type is not an admin function"), } } @@ -405,17 +443,17 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { ) } -fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) -> Result { +fn permit_queries( + deps: Deps, + env: Env, + permit: Permit, + query: QueryWithPermit, +) -> Result { // Validate permit content let token_address = CONFIG.load(deps.storage)?.contract_address; - let account = secret_toolkit::permit::validate( - deps, - &env, - &permit, - token_address.into_string(), - None, - )?; + let account = + secret_toolkit::permit::validate(deps, &env, &permit, token_address.into_string(), None)?; // Permit validated! We can now execute the query. match query { @@ -516,9 +554,9 @@ fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) "No permission to query list permit revocations, got permissions {:?}", permit.params.permissions ))); - } - query::query_list_permit_revocations(deps, account.as_str()) - }, + } + query::query_list_permit_revocations(deps, account.as_str()) + } } } @@ -540,7 +578,9 @@ pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult query::query_transactions(deps, address, page.unwrap_or(0), page_size), - QueryMsg::Allowance { owner, spender, .. } => query::query_allowance(deps, owner, spender), + QueryMsg::Allowance { owner, spender, .. } => { + query::query_allowance(deps, owner, spender) + } QueryMsg::AllowancesGiven { owner, page, @@ -564,7 +604,9 @@ pub fn viewing_keys_queries(deps: Deps, env: Env, msg: QueryMsg) -> StdResult query::query_list_permit_revocations(deps, viewer.address.as_str()), + QueryMsg::ListPermitRevocations { viewer, .. } => { + query::query_list_permit_revocations(deps, viewer.address.as_str()) + } _ => panic!("This query type does not require authentication"), }; } @@ -603,14 +645,17 @@ mod tests { use std::any::Any; use cosmwasm_std::{ - from_binary, testing::*, Addr, Api, BlockInfo, Coin, ContractInfo, CosmosMsg, MessageInfo, OwnedDeps, QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, Uint128, WasmMsg + from_binary, testing::*, Addr, Api, BlockInfo, Coin, ContractInfo, CosmosMsg, MessageInfo, + OwnedDeps, QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, Uint128, WasmMsg, }; use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey}; use crate::batch; use crate::btbe::stored_balance; use crate::dwb::{TX_NODES, TX_NODES_COUNT}; - use crate::msg::{ExecuteAnswer, InitConfig, InitialBalance, ResponseStatus, ResponseStatus::Success}; + use crate::msg::{ + ExecuteAnswer, InitConfig, InitialBalance, ResponseStatus, ResponseStatus::Success, + }; use crate::receiver::Snip20ReceiveMsg; use crate::state::{AllowancesStore, ReceiverHashStore, TX_COUNT}; use crate::transaction_history::{Tx, TxAction}; diff --git a/src/dwb.rs b/src/dwb.rs index 2a7dd255..4f3585d6 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -7,14 +7,14 @@ use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; use crate::btbe::{settle_dwb_entry, stored_balance}; -use crate::state::{safe_add, safe_add_u64}; -use crate::transaction_history::{Tx, TRANSACTIONS}; #[cfg(feature = "gas_tracking")] use crate::gas_tracker::GasTracker; #[cfg(feature = "gas_tracking")] -use cosmwasm_std::{Binary, to_binary}; -#[cfg(feature = "gas_tracking")] use crate::msg::QueryAnswer; +use crate::state::{safe_add, safe_add_u64}; +use crate::transaction_history::{Tx, TRANSACTIONS}; +#[cfg(feature = "gas_tracking")] +use cosmwasm_std::{to_binary, Binary}; include!(concat!(env!("OUT_DIR"), "/config.rs")); diff --git a/src/execute.rs b/src/execute.rs index 09b391fe..59af4377 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,4 +1,6 @@ -use cosmwasm_std::{to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; +use cosmwasm_std::{ + to_binary, Addr, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128, +}; use secret_toolkit::notification::Notification; use secret_toolkit::permit::{AllRevokedInterval, RevokedPermits, RevokedPermitsStore}; use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore}; @@ -6,7 +8,9 @@ use secret_toolkit_crypto::ContractPrng; use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; use crate::notifications::AllowanceNotification; -use crate::state::{AllowancesStore, ReceiverHashStore, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED}; +use crate::state::{ + AllowancesStore, ReceiverHashStore, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, +}; // viewing key functions @@ -28,13 +32,7 @@ pub fn try_create_key( ) -> StdResult { let entropy = [entropy.unwrap_or_default().as_bytes(), &rng.rand_bytes()].concat(); - let key = ViewingKey::create( - deps.storage, - &info, - &env, - info.sender.as_str(), - &entropy, - ); + let key = ViewingKey::create(deps.storage, &info, &env, info.sender.as_str(), &entropy); Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key })?)) } @@ -115,31 +113,28 @@ pub fn try_increase_allowance( let new_amount = allowance.amount; AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { - owner: info.sender.clone(), - spender: spender.clone(), - allowance: Uint128::from(new_amount), - })?); - + let mut resp = Response::new().set_data(to_binary(&ExecuteAnswer::IncreaseAllowance { + owner: info.sender.clone(), + spender: spender.clone(), + allowance: Uint128::from(new_amount), + })?); + if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let notification = Notification::new ( + let notification = Notification::new( spender, AllowanceNotification { amount: new_amount, allower: info.sender, expiration, - } + }, ) .to_txhash_notification(deps.api, &env, secret, None)?; - resp = resp.add_attribute_plaintext( - notification.id_plaintext(), - notification.data_plaintext() - ); + resp = resp + .add_attribute_plaintext(notification.id_plaintext(), notification.data_plaintext()); } - Ok(resp) + Ok(resp) } pub fn try_decrease_allowance( @@ -172,28 +167,25 @@ pub fn try_decrease_allowance( let new_amount = allowance.amount; AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?; - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { - owner: info.sender.clone(), - spender: spender.clone(), - allowance: Uint128::from(new_amount), - })?); + let mut resp = Response::new().set_data(to_binary(&ExecuteAnswer::DecreaseAllowance { + owner: info.sender.clone(), + spender: spender.clone(), + allowance: Uint128::from(new_amount), + })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let notification = Notification::new ( + let notification = Notification::new( spender, AllowanceNotification { amount: new_amount, allower: info.sender, expiration, - } + }, ) .to_txhash_notification(deps.api, &env, secret, None)?; - resp = resp.add_attribute_plaintext( - notification.id_plaintext(), - notification.data_plaintext() - ); + resp = resp + .add_attribute_plaintext(notification.id_plaintext(), notification.data_plaintext()); } Ok(resp) @@ -202,41 +194,37 @@ pub fn try_decrease_allowance( // SNIP 24, 24.1 permit functions pub fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdResult { - RevokedPermits::revoke_permit( - deps.storage, - info.sender.as_str(), - &permit_name, - ); + RevokedPermits::revoke_permit(deps.storage, info.sender.as_str(), &permit_name); Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?)) } -pub fn revoke_all_permits(deps: DepsMut, info: MessageInfo, interval: AllRevokedInterval) -> StdResult { - let revocation_id = RevokedPermits::revoke_all_permits( - deps.storage, - info.sender.as_str(), - &interval, - )?; - - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokeAllPermits { - status: Success, - revocation_id: Some(revocation_id.to_string()), - })?)) -} - -pub fn delete_permit_revocation(deps: DepsMut, info: MessageInfo, revocation_id: String) -> StdResult { - RevokedPermits::delete_revocation( - deps.storage, - info.sender.as_str(), - revocation_id.as_str(), - )?; +pub fn revoke_all_permits( + deps: DepsMut, + info: MessageInfo, + interval: AllRevokedInterval, +) -> StdResult { + let revocation_id = + RevokedPermits::revoke_all_permits(deps.storage, info.sender.as_str(), &interval)?; - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::DeletePermitRevocation { - status: Success, - })?)) + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::RevokeAllPermits { + status: Success, + revocation_id: Some(revocation_id.to_string()), + })?), + ) } +pub fn delete_permit_revocation( + deps: DepsMut, + info: MessageInfo, + revocation_id: String, +) -> StdResult { + RevokedPermits::delete_revocation(deps.storage, info.sender.as_str(), revocation_id.as_str())?; - - - + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::DeletePermitRevocation { + status: Success, + })?), + ) +} diff --git a/src/execute_admin.rs b/src/execute_admin.rs index 7d7983ce..057d1a5e 100644 --- a/src/execute_admin.rs +++ b/src/execute_admin.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{to_binary, Addr, DepsMut, Response, StdError, StdResult,}; +use cosmwasm_std::{to_binary, Addr, DepsMut, Response, StdError, StdResult}; use crate::msg::ContractStatusLevel; use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; @@ -144,12 +144,9 @@ pub fn set_minters( // SNIP-52 functions -pub fn set_notification_status( - deps: DepsMut, - enabled: bool, -) -> StdResult { +pub fn set_notification_status(deps: DepsMut, enabled: bool) -> StdResult { NOTIFICATIONS_ENABLED.save(deps.storage, &enabled)?; - + Ok( Response::new().set_data(to_binary(&ExecuteAnswer::SetNotificationStatus { status: Success, diff --git a/src/execute_deposit_redeem.rs b/src/execute_deposit_redeem.rs index 681119c9..c5659f84 100644 --- a/src/execute_deposit_redeem.rs +++ b/src/execute_deposit_redeem.rs @@ -1,13 +1,13 @@ use cosmwasm_std::{ - to_binary, BankMsg, BlockInfo, CanonicalAddr, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError, - StdResult, Storage, Uint128, + to_binary, BankMsg, BlockInfo, CanonicalAddr, Coin, CosmosMsg, DepsMut, Env, MessageInfo, + Response, StdError, StdResult, Storage, Uint128, }; use secret_toolkit_crypto::ContractPrng; use crate::dwb::DWB; use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; use crate::state::{safe_add, CONFIG, TOTAL_SUPPLY}; -use crate::transaction_history::{store_deposit_action, store_redeem_action,}; +use crate::transaction_history::{store_deposit_action, store_redeem_action}; // deposit functions diff --git a/src/execute_mint_burn.rs b/src/execute_mint_burn.rs index 83f66d42..381b25bd 100644 --- a/src/execute_mint_burn.rs +++ b/src/execute_mint_burn.rs @@ -1,4 +1,7 @@ -use cosmwasm_std::{to_binary, Addr, BlockInfo, CanonicalAddr, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128}; +use cosmwasm_std::{ + to_binary, Addr, BlockInfo, CanonicalAddr, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, Storage, Uint128, +}; use secret_toolkit::notification::Notification; use secret_toolkit_crypto::ContractPrng; @@ -6,8 +9,13 @@ use crate::batch; use crate::dwb::DWB; use crate::execute::use_allowance; use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; -use crate::notifications::{render_group_notification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification}; -use crate::state::{safe_add, MintersStore, CONFIG, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY}; +use crate::notifications::{ + render_group_notification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, + SpentNotification, +}; +use crate::state::{ + safe_add, MintersStore, CONFIG, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY, +}; use crate::transaction_history::{store_burn_action, store_mint_action}; // mint functions @@ -65,8 +73,7 @@ pub fn try_mint( &mut tracker, )?; - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); + let mut resp = Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { let received_notification = Notification::new( @@ -130,7 +137,7 @@ pub fn try_batch_mint( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); - notifications.push(Notification::new ( + notifications.push(Notification::new( recipient.clone(), RecvdNotification { amount: actual_amount, @@ -156,9 +163,9 @@ pub fn try_batch_mint( TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?); - + let mut resp = + Response::new().set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?); + if NOTIFICATIONS_ENABLED.load(deps.storage)? { resp = render_group_notification( deps.api, @@ -322,11 +329,10 @@ pub fn try_burn( } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?); + let mut resp = Response::new().set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let spent_notification = Notification::new ( + let spent_notification = Notification::new( info.sender, SpentNotification { amount: raw_amount, @@ -334,7 +340,7 @@ pub fn try_burn( recipient: None, balance: owner_balance, memo_len, - } + }, ) .to_txhash_notification(deps.api, &env, secret, None)?; @@ -358,7 +364,7 @@ pub fn try_burn_from( ) -> StdResult { let secret = INTERNAL_SECRET_SENSITIVE.load(deps.storage)?; let secret = secret.as_slice(); - + let owner = deps.api.addr_validate(owner.as_str())?; let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; let constants = CONFIG.load(deps.storage)?; @@ -433,11 +439,11 @@ pub fn try_burn_from( TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?); + let mut resp = + Response::new().set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let spent_notification = Notification::new ( + let spent_notification = Notification::new( owner, SpentNotification { amount: raw_amount, @@ -445,7 +451,7 @@ pub fn try_burn_from( recipient: None, balance: owner_balance, memo_len, - } + }, ) .to_txhash_notification(deps.api, &env, secret, None)?; @@ -538,22 +544,23 @@ pub fn try_batch_burn_from( ))); } - spent_notifications.push(Notification::new ( + spent_notifications.push(Notification::new( info.sender.clone(), SpentNotification { amount, actions: 1, recipient: None, balance: owner_balance, - memo_len: action.memo.as_ref().map(|s| s.len()).unwrap_or_default() - } + memo_len: action.memo.as_ref().map(|s| s.len()).unwrap_or_default(), + }, )); } TOTAL_SUPPLY.save(deps.storage, &total_supply)?; - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchBurnFrom {status: Success,})?); + let mut resp = Response::new().set_data(to_binary(&ExecuteAnswer::BatchBurnFrom { + status: Success, + })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { resp = render_group_notification( @@ -567,4 +574,4 @@ pub fn try_batch_burn_from( } Ok(resp) -} \ No newline at end of file +} diff --git a/src/execute_transfer_send.rs b/src/execute_transfer_send.rs index c33ebcbd..ac61687e 100644 --- a/src/execute_transfer_send.rs +++ b/src/execute_transfer_send.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - to_binary, Addr, Binary, BlockInfo, CanonicalAddr, CosmosMsg, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, - Uint128 + to_binary, Addr, Binary, BlockInfo, CanonicalAddr, CosmosMsg, DepsMut, Env, MessageInfo, + Response, StdError, StdResult, Storage, Uint128, }; use secret_toolkit::notification::Notification; use secret_toolkit_crypto::ContractPrng; @@ -9,7 +9,10 @@ use crate::batch; use crate::dwb::DWB; use crate::execute::use_allowance; use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; -use crate::notifications::{render_group_notification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification}; +use crate::notifications::{ + render_group_notification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, + SpentNotification, +}; use crate::receiver::Snip20ReceiveMsg; use crate::state::{ReceiverHashStore, CONFIG, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED}; use crate::strings::SEND_TO_CONTRACT_ERR_MSG; @@ -43,10 +46,7 @@ pub fn try_transfer( let mut tracker: GasTracker = GasTracker::new(deps.api); // perform the transfer - let ( - received_notification, - spent_notification - ) = try_transfer_impl( + let (received_notification, spent_notification) = try_transfer_impl( &mut deps, rng, &info.sender, @@ -62,34 +62,27 @@ pub fn try_transfer( #[cfg(feature = "gas_tracking")] let mut group1 = tracker.group("try_transfer.rest"); - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); + let mut resp = + Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { // render the tokens received notification - let received_notification = received_notification.to_txhash_notification( - deps.api, - &env, - secret, - None, - )?; + let received_notification = + received_notification.to_txhash_notification(deps.api, &env, secret, None)?; // render the tokens spent notification - let spent_notification = spent_notification.to_txhash_notification( - deps.api, - &env, - secret, - None, - )?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); + let spent_notification = + spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); } #[cfg(feature = "gas_tracking")] @@ -111,8 +104,10 @@ pub fn try_batch_transfer( ) -> StdResult { let num_actions = actions.len(); if num_actions == 0 { - return Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?) + return Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer { + status: Success, + })?), ); } @@ -129,7 +124,7 @@ pub fn try_batch_transfer( let mut notifications = vec![]; for action in actions { let recipient = deps.api.addr_validate(action.recipient.as_str())?; - + // make sure the sender is not accidentally sending tokens to the contract address if recipient == env.contract.address { return Err(StdError::generic_err(SEND_TO_CONTRACT_ERR_MSG)); @@ -137,10 +132,7 @@ pub fn try_batch_transfer( total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); - let ( - received_notification, - spent_notification - ) = try_transfer_impl( + let (received_notification, spent_notification) = try_transfer_impl( &mut deps, rng, &info.sender, @@ -156,16 +148,14 @@ pub fn try_batch_transfer( notifications.push((received_notification, spent_notification)); } - let ( - received_notifications, - spent_notifications - ): ( + let (received_notifications, spent_notifications): ( Vec>, Vec>, ) = notifications.into_iter().unzip(); - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?); + let mut resp = Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer { + status: Success, + })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { resp = render_group_notification( @@ -177,11 +167,11 @@ pub fn try_batch_transfer( resp, )?; - let total_amount_spent = spent_notifications - .iter() - .fold(0u128, |acc, notification| acc.saturating_add(notification.data.amount)); + let total_amount_spent = spent_notifications.iter().fold(0u128, |acc, notification| { + acc.saturating_add(notification.data.amount) + }); - let spent_notification = Notification::new ( + let spent_notification = Notification::new( info.sender, SpentNotification { amount: total_amount_spent, @@ -189,7 +179,7 @@ pub fn try_batch_transfer( recipient: spent_notifications[0].data.recipient.clone(), balance: spent_notifications.last().unwrap().data.balance, memo_len: total_memo_len, - } + }, ) .to_txhash_notification(deps.api, &env, secret, None)?; @@ -223,10 +213,7 @@ pub fn try_transfer_from( let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; let symbol = CONFIG.load(deps.storage)?.symbol; - let ( - received_notification, - spent_notification - ) = try_transfer_from_impl( + let (received_notification, spent_notification) = try_transfer_from_impl( &mut deps, rng, env, @@ -238,32 +225,25 @@ pub fn try_transfer_from( memo, )?; - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?); + let mut resp = + Response::new().set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let received_notification = received_notification.to_txhash_notification( - deps.api, - &env, - secret, - None, - )?; - - let spent_notification = spent_notification.to_txhash_notification( - deps.api, - &env, - secret, - None - )?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); + let received_notification = + received_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + let spent_notification = + spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); } Ok(resp) @@ -286,10 +266,7 @@ pub fn try_batch_transfer_from( let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; - let ( - received_notification, - spent_notification - ) = try_transfer_from_impl( + let (received_notification, spent_notification) = try_transfer_from_impl( &mut deps, rng, env, @@ -304,8 +281,9 @@ pub fn try_batch_transfer_from( notifications.push((received_notification, spent_notification)); } - let mut resp = Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchTransferFrom {status: Success})?); + let mut resp = Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransferFrom { + status: Success, + })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { let (received_notifications, spent_notifications): ( @@ -323,7 +301,7 @@ pub fn try_batch_transfer_from( secret, resp, )?; - + resp = render_group_notification( deps.api, MultiSpentNotification(spent_notifications), @@ -367,10 +345,7 @@ pub fn try_send( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); - let ( - received_notification, - spent_notification - ) = try_send_impl( + let (received_notification, spent_notification) = try_send_impl( &mut deps, rng, &mut messages, @@ -391,17 +366,20 @@ pub fn try_send( .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let received_notification = received_notification.to_txhash_notification(deps.api, &env, secret, None)?; - let spent_notification = spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ); + let received_notification = + received_notification.to_txhash_notification(deps.api, &env, secret, None)?; + let spent_notification = + spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ); } #[cfg(feature = "gas_tracking")] @@ -420,8 +398,8 @@ pub fn try_batch_send( ) -> StdResult { let num_actions = actions.len(); if num_actions == 0 { - return Ok(Response::new() - .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) + return Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?) ); } @@ -450,10 +428,7 @@ pub fn try_batch_send( total_memo_len += action.memo.as_ref().map(|s| s.len()).unwrap_or_default(); - let ( - received_notification, - spent_notification - ) = try_send_impl( + let (received_notification, spent_notification) = try_send_impl( &mut deps, rng, &mut messages, @@ -495,7 +470,7 @@ pub fn try_batch_send( .iter() .fold(0u128, |acc, notification| acc + notification.data.amount); - let spent_notification = Notification::new ( + let spent_notification = Notification::new( info.sender, SpentNotification { amount: total_amount_spent, @@ -503,7 +478,7 @@ pub fn try_batch_send( recipient: spent_notifications[0].data.recipient.clone(), balance: spent_notifications.last().unwrap().data.balance, memo_len: total_memo_len, - } + }, ) .to_txhash_notification(deps.api, &env, secret, None)?; @@ -535,10 +510,7 @@ pub fn try_send_from( let owner = deps.api.addr_validate(owner.as_str())?; let recipient = deps.api.addr_validate(recipient.as_str())?; let mut messages = vec![]; - let ( - received_notification, - spent_notification - ) = try_send_from_impl( + let (received_notification, spent_notification) = try_send_from_impl( &mut deps, env.clone(), info, @@ -557,17 +529,20 @@ pub fn try_send_from( .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { - let received_notification = received_notification.to_txhash_notification(deps.api, &env, secret, None,)?; - let spent_notification = spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; - - resp = resp.add_attribute_plaintext( - received_notification.id_plaintext(), - received_notification.data_plaintext(), - ) - .add_attribute_plaintext( - spent_notification.id_plaintext(), - spent_notification.data_plaintext(), - ) + let received_notification = + received_notification.to_txhash_notification(deps.api, &env, secret, None)?; + let spent_notification = + spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + + resp = resp + .add_attribute_plaintext( + received_notification.id_plaintext(), + received_notification.data_plaintext(), + ) + .add_attribute_plaintext( + spent_notification.id_plaintext(), + spent_notification.data_plaintext(), + ) } Ok(resp) @@ -589,10 +564,7 @@ pub fn try_batch_send_from( for action in actions { let owner = deps.api.addr_validate(action.owner.as_str())?; let recipient = deps.api.addr_validate(action.recipient.as_str())?; - let ( - received_notification, - spent_notification - ) = try_send_from_impl( + let (received_notification, spent_notification) = try_send_from_impl( &mut deps, env.clone(), info, @@ -608,11 +580,9 @@ pub fn try_batch_send_from( notifications.push((received_notification, spent_notification)); } - let mut resp = Response::new() - .add_messages(messages) - .set_data(to_binary(&ExecuteAnswer::BatchSendFrom { - status: Success, - })?); + let mut resp = Response::new().add_messages(messages).set_data(to_binary( + &ExecuteAnswer::BatchSendFrom { status: Success }, + )?); if NOTIFICATIONS_ENABLED.load(deps.storage)? { let (received_notifications, spent_notifications): ( @@ -657,7 +627,10 @@ fn try_transfer_impl( memo: Option, block: &cosmwasm_std::BlockInfo, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<(Notification, Notification)> { +) -> StdResult<( + Notification, + Notification, +)> { // canonicalize owner and recipient addresses let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?; @@ -673,7 +646,7 @@ fn try_transfer_impl( sender: Some(owner.clone()), memo_len, sender_is_owner: true, - } + }, ); // perform the transfer from owner to recipient @@ -693,7 +666,7 @@ fn try_transfer_impl( )?; // create the tokens spent notification for owner - let spent_notification = Notification::new ( + let spent_notification = Notification::new( owner.clone(), SpentNotification { amount: amount.u128(), @@ -701,7 +674,7 @@ fn try_transfer_impl( recipient: Some(recipient.clone()), balance: owner_balance, memo_len, - } + }, ); Ok((received_notification, spent_notification)) @@ -718,7 +691,10 @@ fn try_transfer_from_impl( amount: Uint128, denom: String, memo: Option, -) -> StdResult<(Notification, Notification)> { +) -> StdResult<( + Notification, + Notification, +)> { let raw_amount = amount.u128(); let raw_spender = deps.api.addr_canonicalize(spender.as_str())?; let raw_owner = deps.api.addr_canonicalize(owner.as_str())?; @@ -744,9 +720,9 @@ fn try_transfer_from_impl( sender: Some(owner.clone()), memo_len, sender_is_owner: spender == owner, - } + }, ); - + // perform the transfer from owner to recipient let owner_balance = perform_transfer( deps.storage, @@ -764,7 +740,7 @@ fn try_transfer_from_impl( )?; // create tokens spent notification for owner - let spent_notification = Notification::new ( + let spent_notification = Notification::new( owner.clone(), SpentNotification { amount: amount.u128(), @@ -772,7 +748,7 @@ fn try_transfer_from_impl( recipient: Some(recipient.clone()), balance: owner_balance, memo_len, - } + }, ); Ok((received_notification, spent_notification)) @@ -792,11 +768,11 @@ fn try_send_impl( msg: Option, block: &cosmwasm_std::BlockInfo, #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker, -) -> StdResult<(Notification, Notification)> { - let ( - received_notification, - spent_notification - ) = try_transfer_impl( +) -> StdResult<( + Notification, + Notification, +)> { + let (received_notification, spent_notification) = try_transfer_impl( deps, rng, &sender, @@ -837,13 +813,13 @@ fn try_send_from_impl( amount: Uint128, memo: Option, msg: Option, -) -> StdResult<(Notification, Notification)> { +) -> StdResult<( + Notification, + Notification, +)> { let spender = info.sender.clone(); let symbol = CONFIG.load(deps.storage)?.symbol; - let ( - received_notification, - spent_notification - ) = try_transfer_from_impl( + let (received_notification, spent_notification) = try_transfer_from_impl( deps, rng, &env, @@ -977,4 +953,4 @@ fn try_add_receiver_api_callback( messages.push(callback_msg); } Ok(()) -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index bbe941b2..7b04ab05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,11 +11,11 @@ pub mod execute_admin; pub mod execute_deposit_redeem; pub mod execute_mint_burn; pub mod execute_transfer_send; -pub mod query; mod gas_tracker; pub mod msg; +mod notifications; +pub mod query; pub mod receiver; pub mod state; mod strings; mod transaction_history; -mod notifications; \ No newline at end of file diff --git a/src/msg.rs b/src/msg.rs index fc6e1f73..ed47e5fd 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -4,10 +4,13 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{batch, transaction_history::Tx}; -use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint64,}; #[cfg(feature = "gas_evaporation")] use cosmwasm_std::Uint64; -use secret_toolkit::{notification::ChannelInfoData, permit::{AllRevocation, AllRevokedInterval, Permit}}; +use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint64}; +use secret_toolkit::{ + notification::ChannelInfoData, + permit::{AllRevocation, AllRevokedInterval, Permit}, +}; #[cfg_attr(test, derive(Eq, PartialEq))] #[derive(Serialize, Deserialize, Clone, JsonSchema)] @@ -299,12 +302,11 @@ pub enum ExecuteMsg { }, // SNIP 24.1 Blanket Permits - /// Revokes all permits. Client can supply a datetime for created_after, created_before, both, or neither. /// * created_before – makes it so any permits using a created value less than this datetime will be rejected /// * created_after – makes it so any permits using a created value greater than this datetime will be rejected /// * both created_before and created_after – makes it so any permits using a created value between these two datetimes will be rejected - /// * neither – makes it so ANY permit will be rejected. + /// * neither – makes it so ANY permit will be rejected. /// in this case, the contract MUST return a revocation ID of "REVOKED_ALL". this action is idempotent RevokeAllPermits { interval: AllRevokedInterval, @@ -435,7 +437,6 @@ pub enum ExecuteAnswer { DeletePermitRevocation { status: ResponseStatus, }, - } #[cfg(feature = "gas_evaporation")] @@ -483,7 +484,7 @@ impl Evaporator for ExecuteMsg { if gas_used < gas_target.u64() { let evaporate_amount = gas_target.u64() - gas_used; api.gas_evaporate(evaporate_amount as u32)?; - return Ok(evaporate_amount) + return Ok(evaporate_amount); } Ok(0) } @@ -655,7 +656,7 @@ pub enum QueryWithPermit { txhash: Option, }, // SNIP 24.1 - ListPermitRevocations { + ListPermitRevocations { // `page` and `page_size` do nothing here because max revocations is only 10 but included // to satisfy the SNIP24.1 spec page: Option, @@ -716,7 +717,7 @@ pub enum QueryAnswer { Minters { minters: Vec, }, - + // SNIP-52 Private Push Notifications ListChannels { channels: Vec, diff --git a/src/notifications.rs b/src/notifications.rs index 2714ea35..ec6de700 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -1,16 +1,15 @@ use std::collections::HashMap; use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, Response, StdResult}; +use minicbor::Encoder; use primitive_types::{U256, U512}; use secret_toolkit::notification::{ - get_seed, notification_id, xor_bytes, EncoderExt, CBL_ADDRESS, CBL_ARRAY_SHORT, CBL_BIGNUM_U64, CBL_TIMESTAMP, CBL_U8, Notification, - DirectChannel, GroupChannel, + get_seed, notification_id, xor_bytes, DirectChannel, EncoderExt, GroupChannel, Notification, + CBL_ADDRESS, CBL_ARRAY_SHORT, CBL_BIGNUM_U64, CBL_TIMESTAMP, CBL_U8, }; -use minicbor::Encoder; use secret_toolkit_crypto::{hkdf_sha_512, sha_256}; use serde::{Deserialize, Serialize}; - const ZERO_ADDR: [u8; 20] = [0u8; 20]; // maximum value that can be stored in 62 bits @@ -19,7 +18,6 @@ const U62_MAX: u128 = (1 << 62) - 1; // maximum value that can be stored in 63 bits const U63_MAX: u128 = (1 << 63) - 1; - #[derive(Serialize, Debug, Deserialize, Clone)] #[cfg_attr(test, derive(Eq, PartialEq))] pub struct RecvdNotification { @@ -39,9 +37,11 @@ pub struct RecvdNotification { impl DirectChannel for RecvdNotification { const CHANNEL_ID: &'static str = "recvd"; #[cfg(test)] - const CDDL_SCHEMA: &'static str = "recvd=[amount:biguint .size 8,sender:bstr .size 54,memo_len:uint .size 1]"; + const CDDL_SCHEMA: &'static str = + "recvd=[amount:biguint .size 8,sender:bstr .size 54,memo_len:uint .size 1]"; #[cfg(not(test))] - const CDDL_SCHEMA: &'static str = "recvd=[amount:biguint .size 8,sender:bstr .size 20,memo_len:uint .size 1]"; + const CDDL_SCHEMA: &'static str = + "recvd=[amount:biguint .size 8,sender:bstr .size 20,memo_len:uint .size 1]"; const ELEMENTS: u64 = 3; #[cfg(test)] const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + 55 + CBL_U8; @@ -85,7 +85,6 @@ pub struct SpentNotification { pub memo_len: usize, } - impl DirectChannel for SpentNotification { const CHANNEL_ID: &'static str = "spent"; #[cfg(test)] @@ -96,7 +95,8 @@ impl DirectChannel for SpentNotification { #[cfg(test)] const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_U8 + 55 + CBL_BIGNUM_U64; #[cfg(not(test))] - const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_U8 + CBL_ADDRESS + CBL_BIGNUM_U64; + const PAYLOAD_SIZE: usize = + CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + CBL_U8 + CBL_ADDRESS + CBL_BIGNUM_U64; fn encode_cbor(&self, api: &dyn Api, encoder: &mut Encoder<&mut [u8]>) -> StdResult<()> { // amount:biguint (8-byte uint), actions:uint (1-byte uint) @@ -114,12 +114,11 @@ impl DirectChannel for SpentNotification { // balance:biguint (8-byte uint) spent_data.ext_u64_from_u128(self.balance)?; - + Ok(()) } } - ///```cddl /// allowance = [ /// amount: biguint .size 8, ; allowance amount in base denomination @@ -138,9 +137,11 @@ pub struct AllowanceNotification { impl DirectChannel for AllowanceNotification { const CHANNEL_ID: &'static str = "allowance"; #[cfg(test)] - const CDDL_SCHEMA: &'static str = "allowance=[amount:biguint .size 8,allower:bstr .size 54,expiration:uint .size 8]"; + const CDDL_SCHEMA: &'static str = + "allowance=[amount:biguint .size 8,allower:bstr .size 54,expiration:uint .size 8]"; #[cfg(not(test))] - const CDDL_SCHEMA: &'static str = "allowance=[amount:biguint .size 8,allower:bstr .size 20,expiration:uint .size 8]"; + const CDDL_SCHEMA: &'static str = + "allowance=[amount:biguint .size 8,allower:bstr .size 20,expiration:uint .size 8]"; const ELEMENTS: u64 = 3; #[cfg(test)] const PAYLOAD_SIZE: usize = CBL_ARRAY_SHORT + CBL_BIGNUM_U64 + 55 + CBL_TIMESTAMP; @@ -184,8 +185,8 @@ impl GroupChannel for MultiRecvdNotification { // encode flags and amount into 8 bytes (leftmost 2 bits reserved) let amount_bytes = &(data.amount.clamp(0, U62_MAX) | (((data.memo_len != 0) as u128) << 63) - | ((data.sender_is_owner as u128) << 62) - ).to_be_bytes()[8..]; + | ((data.sender_is_owner as u128) << 62)) + .to_be_bytes()[8..]; // packet flag bits and amount bytes (u64 == 8 bytes) packet_plaintext[0..8].copy_from_slice(amount_bytes); @@ -212,7 +213,9 @@ impl GroupChannel for MultiRecvdNotification { const_assert!(MultiRecvdNotification::BLOOM_M <= 512); // ensure m is a power of 2 -const_assert!(MultiRecvdNotification::BLOOM_M.trailing_zeros() == MultiRecvdNotification::BLOOM_M_LOG2); +const_assert!( + MultiRecvdNotification::BLOOM_M.trailing_zeros() == MultiRecvdNotification::BLOOM_M_LOG2 +); // ensure there are enough bits in the 32-byte source hash to provide entropy for the hashes const_assert!(MultiRecvdNotification::BLOOM_K * MultiRecvdNotification::BLOOM_M_LOG2 <= 256); @@ -220,10 +223,8 @@ const_assert!(MultiRecvdNotification::BLOOM_K * MultiRecvdNotification::BLOOM_M_ // this implementation is optimized to not check for packet sizes larger than 24 bytes const_assert!(MultiRecvdNotification::PACKET_SIZE <= 24); - pub struct MultiSpentNotification(pub Vec>); - impl GroupChannel for MultiSpentNotification { const CHANNEL_ID: &str = "multispent"; @@ -245,8 +246,8 @@ impl GroupChannel for MultiSpentNotification { // encode flags and amount into 8 bytes (leftmost 2 bits reserved) let amount_bytes = &(data.amount.clamp(0, U62_MAX) - | (((data.memo_len != 0) as u128) << 63) - ).to_be_bytes()[8..]; + | (((data.memo_len != 0) as u128) << 63)) + .to_be_bytes()[8..]; // packet flags and amount bytes (u64 == 8 bytes) packet_plaintext[0..8].copy_from_slice(amount_bytes); @@ -265,11 +266,8 @@ impl GroupChannel for MultiSpentNotification { packet_plaintext[8..16].copy_from_slice(&recipient_bytes[12..]); // balance bytes (u64 == 8 bytes) - packet_plaintext[16..24].copy_from_slice( - &data.balance - .clamp(0, u64::MAX.into()) - .to_be_bytes()[8..] - ); + packet_plaintext[16..24] + .copy_from_slice(&data.balance.clamp(0, u64::MAX.into()).to_be_bytes()[8..]); // 24 bytes total Ok(packet_plaintext.to_vec()) @@ -280,7 +278,9 @@ impl GroupChannel for MultiSpentNotification { const_assert!(MultiSpentNotification::BLOOM_M <= 512); // ensure m is a power of 2 -const_assert!(MultiSpentNotification::BLOOM_M.trailing_zeros() == MultiSpentNotification::BLOOM_M_LOG2); +const_assert!( + MultiSpentNotification::BLOOM_M.trailing_zeros() == MultiSpentNotification::BLOOM_M_LOG2 +); // ensure there are enough bits in the 32-byte source hash to provide entropy for the hashes const_assert!(MultiSpentNotification::BLOOM_K * MultiSpentNotification::BLOOM_M_LOG2 <= 256); @@ -288,7 +288,6 @@ const_assert!(MultiSpentNotification::BLOOM_K * MultiSpentNotification::BLOOM_M_ // this implementation is optimized to not check for packet sizes larger than 24 bytes const_assert!(MultiSpentNotification::PACKET_SIZE <= 24); - struct BloomFilter { filter: U512, tx_hash: String, @@ -309,10 +308,12 @@ impl BloomFilter { // each hash section for up to k times for i in 0..G::BLOOM_K { - let bit_index = ((hash_bytes >> (256 - G::BLOOM_M_LOG2 - (i * G::BLOOM_M_LOG2))) & bloom_mask).as_usize(); + let bit_index = ((hash_bytes >> (256 - G::BLOOM_M_LOG2 - (i * G::BLOOM_M_LOG2))) + & bloom_mask) + .as_usize(); self.filter |= U512::from(1) << bit_index; } - + // use top 64 bits of notification ID for packet ID let packet_id = &id.0.as_slice()[0..8]; @@ -320,22 +321,15 @@ impl BloomFilter { let packet_ikm = &id.0.as_slice()[8..32]; // create ciphertext by XOR'ing the plaintext with the notification ID - let packet_ciphertext = xor_bytes( - &packet_plaintext[..], - &packet_ikm[0..G::PACKET_SIZE] - ); + let packet_ciphertext = xor_bytes(&packet_plaintext[..], &packet_ikm[0..G::PACKET_SIZE]); // construct the packet bytes - let packet_bytes: Vec = [ - packet_id.to_vec(), - packet_ciphertext, - ].concat(); + let packet_bytes: Vec = [packet_id.to_vec(), packet_ciphertext].concat(); Ok(packet_bytes) } } - pub fn render_group_notification>( api: &dyn Api, group: G, @@ -366,9 +360,7 @@ pub fn render_group_notification>( // increment count of recipient occurrence recipient_counts.insert( notification_for, - recipient_counts - .get(¬ifyee) - .unwrap_or(&0u16) + 1, + recipient_counts.get(¬ifyee).unwrap_or(&0u16) + 1, ); // skip adding this packet if recipient was already seen @@ -380,10 +372,7 @@ pub fn render_group_notification>( let packet_plaintext = &group.build_packet(api, ¬ification.data)?; // add to bloom filter - let packet_bytes = bloom_filter.add::( - ¬ifyee, - packet_plaintext, - )?; + let packet_bytes = bloom_filter.add::(¬ifyee, packet_plaintext)?; // add to packets data packets.push((notifyee, packet_bytes)); @@ -409,14 +398,14 @@ pub fn render_group_notification>( &Some(vec![0u8; 64]), &env_random, format!("{}:decoys", G::CHANNEL_ID).as_bytes(), - padding_size * 20, // 20 bytes per random addr + padding_size * 20, // 20 bytes per random addr )?; // handle each padding package for i in 0..padding_size { // generate address let address = CanonicalAddr::from(&decoy_addresses[i * 20..(i + 1) * 20]); - + // nil plaintext let packet_plaintext = vec![0u8; G::PACKET_SIZE]; @@ -433,7 +422,8 @@ pub fn render_group_notification>( // append bloom filter (taking m bottom bits of 512-bit filter) output_bytes.extend_from_slice( - &bloom_filter.filter.to_big_endian()[((512 - G::BLOOM_M as usize) >> 3)..]); + &bloom_filter.filter.to_big_endian()[((512 - G::BLOOM_M as usize) >> 3)..], + ); // append packets for packet in packets { @@ -443,5 +433,6 @@ pub fn render_group_notification>( // Ok(output_bytes) Ok(resp.add_attribute_plaintext( format!("snip52:#{}", G::CHANNEL_ID), - Binary::from(output_bytes).to_base64())) + Binary::from(output_bytes).to_base64(), + )) } diff --git a/src/query.rs b/src/query.rs index b311bb5e..b5044dbb 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,17 +1,25 @@ -use cosmwasm_std::{to_binary, Addr, Binary, CanonicalAddr, Deps, Env, StdError, StdResult, Storage, Uint128, Uint64}; +use cosmwasm_std::{ + to_binary, Addr, Binary, CanonicalAddr, Deps, Env, StdError, StdResult, Storage, Uint128, + Uint64, +}; use rand_chacha::ChaChaRng; use rand_core::{RngCore, SeedableRng}; use secret_toolkit::notification::{ - get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, DirectChannel, FlatDescriptor, GroupChannel, StructDescriptor, + get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, DirectChannel, + FlatDescriptor, GroupChannel, StructDescriptor, }; use secret_toolkit::permit::{RevokedPermits, RevokedPermitsStore}; use crate::btbe::{find_start_bundle, stored_balance, stored_entry, stored_tx_count}; use crate::dwb::{DWB, TX_NODES}; use crate::msg::{AllowanceGivenResult, AllowanceReceivedResult, QueryAnswer}; -use crate::notifications::{AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, SpentNotification}; +use crate::notifications::{ + AllowanceNotification, MultiRecvdNotification, MultiSpentNotification, RecvdNotification, + SpentNotification, +}; use crate::state::{ - AllowancesStore, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, INTERNAL_SECRET_SENSITIVE, TOTAL_SUPPLY + AllowancesStore, MintersStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET_RELAXED, + INTERNAL_SECRET_SENSITIVE, TOTAL_SUPPLY, }; use crate::transaction_history::Tx; @@ -383,10 +391,7 @@ pub fn query_allowances_received( // ***************** pub fn query_list_permit_revocations(deps: Deps, account: &str) -> StdResult { - let revocations = RevokedPermits::list_revocations( - deps.storage, - account - )?; + let revocations = RevokedPermits::list_revocations(deps.storage, account)?; to_binary(&QueryAnswer::ListPermitRevocations { revocations }) } diff --git a/src/state.rs b/src/state.rs index 5dcea935..1a74ab80 100644 --- a/src/state.rs +++ b/src/state.rs @@ -232,4 +232,4 @@ pub static INTERNAL_SECRET_RELAXED: Item> = Item::new(b"internal-secret- pub static CHANNELS: Keyset = Keyset::new(b"channel-ids"); /// SNIP-52 status -pub static NOTIFICATIONS_ENABLED: Item = Item::new(b"notify-status"); \ No newline at end of file +pub static NOTIFICATIONS_ENABLED: Item = Item::new(b"notify-status"); diff --git a/src/strings.rs b/src/strings.rs index a456db18..437eb8a7 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1,4 +1,3 @@ pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str = "`transfer_history` query is UNSUPPORTED. Use `transaction_history` instead."; -pub const SEND_TO_CONTRACT_ERR_MSG: &str = - "Tokens cannot be sent to token contract."; \ No newline at end of file +pub const SEND_TO_CONTRACT_ERR_MSG: &str = "Tokens cannot be sent to token contract."; From 50f0001b042e4cf26e6bf0a6338548cddf64958a Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Dec 2024 13:19:00 +1300 Subject: [PATCH 77/87] fix linter error --- build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.rs b/build.rs index 60de26dd..b45041dc 100644 --- a/build.rs +++ b/build.rs @@ -14,8 +14,8 @@ fn main() { // write constants let mut file = File::create(&dest_path).expect("Failed to write to config.rs"); - write!(file, "pub const DWB_CAPACITY: u16 = {};\n", dwb_capacity).unwrap(); - write!(file, "pub const BTBE_CAPACITY: u16 = {};\n", btbe_capacity).unwrap(); + writeln!(file, "pub const DWB_CAPACITY: u16 = {};", dwb_capacity).unwrap(); + writeln!(file, "pub const BTBE_CAPACITY: u16 = {};", btbe_capacity).unwrap(); // monitor println!("cargo:rerun-if-env-changed=DWB_CAPACITY"); From c55e7ba11eee4c106924a3928dec71aa8534d6a4 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Dec 2024 14:10:56 +1300 Subject: [PATCH 78/87] fix linting --- src/btbe.rs | 26 +++++++++++--------------- src/contract.rs | 9 +++------ src/dwb.rs | 16 ++++++++-------- src/execute_admin.rs | 6 +++--- src/execute_mint_burn.rs | 3 ++- src/execute_transfer_send.rs | 5 +++-- src/gas_tracker.rs | 2 +- src/notifications.rs | 6 +++--- src/query.rs | 22 +++++++++++----------- 9 files changed, 45 insertions(+), 50 deletions(-) diff --git a/src/btbe.rs b/src/btbe.rs index 21e4717e..8b2b8f59 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -59,7 +59,7 @@ impl StoredEntry { let mut result = [0u8; BTBE_BUCKET_ENTRY_BYTES]; result[..BTBE_BUCKET_ADDRESS_BYTES].copy_from_slice(address); - Ok(Self { 0: result }) + Ok(Self(result)) } fn from( @@ -159,15 +159,13 @@ impl StoredEntry { pub fn routes_to_right_node(&self, bit_pos: usize, secret: &[u8]) -> StdResult { // target byte value - let byte; - // bit pos is cached - if bit_pos < (BTBE_BUCKET_CACHE_BYTES << 3) { + let byte = if bit_pos < (BTBE_BUCKET_CACHE_BYTES << 3) { // select the byte from cache corresponding to this bit position - byte = self.0[BTBE_BUCKET_ADDRESS_BYTES + self.0[BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES - + (bit_pos >> 3)]; + + (bit_pos >> 3)] } // not cached; calculate on the fly else { @@ -180,11 +178,11 @@ impl StoredEntry { )?; // select the byte containing the target bit - byte = key_bytes[bit_pos >> 3]; - } + key_bytes[bit_pos >> 3] + }; // extract value at bit position and turn into bool - return Ok(((byte >> (7 - (bit_pos % 8))) & 1) != 0); + Ok(((byte >> (7 - (bit_pos % 8))) & 1) != 0) } pub fn merge_dwb_entry( @@ -229,9 +227,7 @@ impl StoredEntry { // peek at the last tx bundle added (read the dummy one if its void) let last_tx_bundle_result = self.get_tx_bundle_at_unchecked(storage, bundle_pos); if last_tx_bundle_result.is_err() { - return Err(StdError::generic_err(format!( - "missing tx bundle while merging dwb entry!", - ))); + return Err(StdError::generic_err("missing tx bundle while merging dwb entry!")); } // unwrap @@ -469,7 +465,7 @@ pub fn locate_btbe_node( // while the node has children while node.bucket == 0 { // calculate bit value at current bit position - let bit_value = (hash[(bit_pos / 8) as usize] >> (7 - (bit_pos % 8))) & 1; + let bit_value = (hash[bit_pos / 8] >> (7 - (bit_pos % 8))) & 1; // increment bit position bit_pos += 1; @@ -584,7 +580,7 @@ pub fn settle_dwb_entry( if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(address) { // found existing entry // merge amount and history from dwb entry - found_entry.merge_dwb_entry(storage, &dwb_entry, amount_spent)?; + found_entry.merge_dwb_entry(storage, dwb_entry, amount_spent)?; bucket.entries[idx] = found_entry; #[cfg(feature = "gas_tracking")] @@ -600,7 +596,7 @@ pub fn settle_dwb_entry( else { // need to insert new entry // create new stored balance entry - let mut btbe_entry = StoredEntry::from(storage, &dwb_entry, amount_spent)?; + let mut btbe_entry = StoredEntry::from(storage, dwb_entry, amount_spent)?; // cache the address btbe_entry.save_hash_cache(storage)?; diff --git a/src/contract.rs b/src/contract.rs index f0f95957..9068539b 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -153,10 +153,7 @@ pub fn instantiate( } } - let supported_denoms = match msg.supported_denoms { - None => vec![], - Some(x) => x, - }; + let supported_denoms = msg.supported_denoms.unwrap_or_default(); CONFIG.save( deps.storage, @@ -468,7 +465,7 @@ fn permit_queries( query::query_balance(deps, account) } QueryWithPermit::TransferHistory { .. } => { - return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)); + Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)) } QueryWithPermit::TransactionHistory { page, page_size } => { if !permit.check_permission(&TokenPermissions::History) { @@ -1048,7 +1045,7 @@ mod tests { .add_suffix(&alice_entry.head_node().unwrap().to_be_bytes()) .load(&deps.storage) .unwrap() - .to_vec(&deps.storage, &deps.api) + .as_vec(&deps.storage, &deps.api) .unwrap(); let expected_alice_nodes: Vec = vec![ diff --git a/src/dwb.rs b/src/dwb.rs index 4f3585d6..908de7a4 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -129,7 +129,7 @@ impl DelayedWriteBuffer { settle_dwb_entry( store, - &mut dwb_entry, + &dwb_entry, Some(amount_spent), #[cfg(feature = "gas_tracking")] tracker, @@ -178,7 +178,7 @@ impl DelayedWriteBuffer { matched_index } - pub fn add_recipient<'a>( + pub fn add_recipient( &mut self, store: &mut dyn Storage, rng: &mut ContractPrng, @@ -196,7 +196,7 @@ impl DelayedWriteBuffer { group1.log("recipient_match"); // the new entry will either derive from a prior entry for the recipient or the dummy entry - let mut new_entry = self.entries[recipient_index].clone(); + let mut new_entry = self.entries[recipient_index]; new_entry.set_recipient(recipient)?; #[cfg(feature = "gas_tracking")] @@ -273,10 +273,10 @@ impl DelayedWriteBuffer { group1.logf(format!("@write_index: {}", write_index)); // settle the entry - let mut dwb_entry = self.entries[actual_settle_index]; + let dwb_entry = self.entries[actual_settle_index]; settle_dwb_entry( store, - &mut dwb_entry, + &dwb_entry, None, #[cfg(feature = "gas_tracking")] tracker, @@ -355,7 +355,7 @@ impl DelayedWriteBufferEntry { } let mut result = [0u8; DWB_ENTRY_BYTES]; result[..DWB_RECIPIENT_BYTES].copy_from_slice(recipient); - Ok(Self { 0: result }) + Ok(Self(result)) } pub fn recipient_slice(&self) -> &[u8] { @@ -466,7 +466,7 @@ pub fn amount_u64(amount_spent: Option) -> StdResult { let amount_spent = amount_spent.unwrap_or_default(); let amount_spent_u64 = amount_spent .try_into() - .or_else(|_| return Err(StdError::generic_err("se: spent overflow")))?; + .map_err(|_| StdError::generic_err("se: spent overflow"))?; Ok(amount_spent_u64) } @@ -481,7 +481,7 @@ pub struct TxNode { impl TxNode { // converts this and following elements in list to a vec of Tx - pub fn to_vec(&self, store: &dyn Storage, api: &dyn Api) -> StdResult> { + pub fn as_vec(&self, store: &dyn Storage, api: &dyn Api) -> StdResult> { let mut result = vec![]; let mut cur_node = Some(self.to_owned()); while cur_node.is_some() { diff --git a/src/execute_admin.rs b/src/execute_admin.rs index 057d1a5e..c2bc7e2c 100644 --- a/src/execute_admin.rs +++ b/src/execute_admin.rs @@ -10,7 +10,7 @@ pub fn change_admin(deps: DepsMut, constants: &mut Config, address: String) -> S let address = deps.api.addr_validate(address.as_str())?; constants.admin = address; - CONFIG.save(deps.storage, &constants)?; + CONFIG.save(deps.storage, constants)?; Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ChangeAdmin { status: Success })?)) } @@ -32,7 +32,7 @@ pub fn add_supported_denoms( } } - CONFIG.save(deps.storage, &config)?; + CONFIG.save(deps.storage, config)?; Ok( Response::new().set_data(to_binary(&ExecuteAnswer::AddSupportedDenoms { @@ -56,7 +56,7 @@ pub fn remove_supported_denoms( config.supported_denoms.retain(|x| x != denom); } - CONFIG.save(deps.storage, &config)?; + CONFIG.save(deps.storage, config)?; Ok( Response::new().set_data(to_binary(&ExecuteAnswer::RemoveSupportedDenoms { diff --git a/src/execute_mint_burn.rs b/src/execute_mint_burn.rs index 381b25bd..7858597c 100644 --- a/src/execute_mint_burn.rs +++ b/src/execute_mint_burn.rs @@ -212,6 +212,7 @@ fn try_mint_impl( Ok(()) } +#[allow(clippy::too_many_arguments)] pub fn perform_mint( store: &mut dyn Storage, rng: &mut ContractPrng, @@ -453,7 +454,7 @@ pub fn try_burn_from( memo_len, }, ) - .to_txhash_notification(deps.api, &env, secret, None)?; + .to_txhash_notification(deps.api, env, secret, None)?; resp = resp.add_attribute_plaintext( spent_notification.id_plaintext(), diff --git a/src/execute_transfer_send.rs b/src/execute_transfer_send.rs index ac61687e..bf52af63 100644 --- a/src/execute_transfer_send.rs +++ b/src/execute_transfer_send.rs @@ -230,10 +230,10 @@ pub fn try_transfer_from( if NOTIFICATIONS_ENABLED.load(deps.storage)? { let received_notification = - received_notification.to_txhash_notification(deps.api, &env, secret, None)?; + received_notification.to_txhash_notification(deps.api, env, secret, None)?; let spent_notification = - spent_notification.to_txhash_notification(deps.api, &env, secret, None)?; + spent_notification.to_txhash_notification(deps.api, env, secret, None)?; resp = resp .add_attribute_plaintext( @@ -846,6 +846,7 @@ fn try_send_from_impl( Ok((received_notification, spent_notification)) } +#[allow(clippy::too_many_arguments)] fn perform_transfer( store: &mut dyn Storage, rng: &mut ContractPrng, diff --git a/src/gas_tracker.rs b/src/gas_tracker.rs index 8783e630..1864bf4a 100644 --- a/src/gas_tracker.rs +++ b/src/gas_tracker.rs @@ -76,7 +76,7 @@ impl<'a, 'b> GasGroup<'a, 'b> { format!( "{}:{}:{}", self.index, - gas.unwrap_or(0u64).to_string(), + gas.unwrap_or(0u64), comment ), ); diff --git a/src/notifications.rs b/src/notifications.rs index ec6de700..8c2a321b 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -196,7 +196,7 @@ impl GroupChannel for MultiRecvdNotification { let owner_bytes: &[u8]; if let Some(owner) = &data.sender { owner_addr = api.addr_canonicalize(owner.as_str())?; - owner_bytes = &owner_addr.as_slice() + owner_bytes = owner_addr.as_slice() } else { owner_bytes = &ZERO_ADDR; } @@ -298,10 +298,10 @@ impl BloomFilter { fn add>( &mut self, recipient: &CanonicalAddr, - packet_plaintext: &Vec, + packet_plaintext: &[u8], ) -> StdResult> { // contribute to received bloom filter - let seed = get_seed(&recipient, &self.secret)?; + let seed = get_seed(recipient, &self.secret)?; let id = notification_id(&seed, G::CHANNEL_ID, &self.tx_hash)?; let hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice())); let bloom_mask: U256 = U256::from(G::BLOOM_M - 1); diff --git a/src/query.rs b/src/query.rs index b5044dbb..15f6c05e 100644 --- a/src/query.rs +++ b/src/query.rs @@ -118,7 +118,7 @@ pub fn query_transactions( let head_node = TX_NODES .add_suffix(&head_node_index.to_be_bytes()) .load(deps.storage)?; - txs_in_dwb = head_node.to_vec(deps.storage, deps.api)?; + txs_in_dwb = head_node.as_vec(deps.storage, deps.api)?; } } @@ -148,7 +148,7 @@ pub fn query_transactions( if tx_bundles_idx_len > 0 { let mut bundle_idx = tx_bundles_idx_len - 1; loop { - let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; + let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx)?; // only look if head node is not null if tx_bundle.head_node > 0 { @@ -159,11 +159,11 @@ pub fn query_transactions( let list_len = tx_bundle.list_len as u32; if txs_left <= list_len { txs.extend_from_slice( - &head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize], + &head_node.as_vec(deps.storage, deps.api)?[0..txs_left as usize], ); break; } - txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs.extend(head_node.as_vec(deps.storage, deps.api)?); txs_left = txs_left.saturating_sub(list_len); } if bundle_idx > 0 { @@ -197,7 +197,7 @@ pub fn query_transactions( .add_suffix(&tx_bundle.head_node.to_be_bytes()) .load(deps.storage)?; // this first bundle has all the txs we need - txs = head_node.to_vec(deps.storage, deps.api)? + txs = head_node.as_vec(deps.storage, deps.api)? [start_at as usize..(start_at + txs_left) as usize] .to_vec(); } @@ -208,7 +208,7 @@ pub fn query_transactions( .add_suffix(&tx_bundle.head_node.to_be_bytes()) .load(deps.storage)?; // get the rest of the txs in this bundle and then go back through history - txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); + txs = head_node.as_vec(deps.storage, deps.api)?[start_at as usize..].to_vec(); txs_left = txs_left.saturating_sub(list_len - start_at); } @@ -218,7 +218,7 @@ pub fn query_transactions( if let Some(entry) = account_stored_entry { loop { let tx_bundle = - entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?; + entry.get_tx_bundle_at(deps.storage, bundle_idx)?; // only look if head node is not null if tx_bundle.head_node > 0 { let head_node = TX_NODES @@ -227,12 +227,12 @@ pub fn query_transactions( let list_len = tx_bundle.list_len as u32; if txs_left <= list_len { txs.extend_from_slice( - &head_node.to_vec(deps.storage, deps.api)? + &head_node.as_vec(deps.storage, deps.api)? [0..txs_left as usize], ); break; } - txs.extend(head_node.to_vec(deps.storage, deps.api)?); + txs.extend(head_node.as_vec(deps.storage, deps.api)?); txs_left = txs_left.saturating_sub(list_len); } if bundle_idx > 0 { @@ -335,7 +335,7 @@ pub fn query_allowances_given( let owner = Addr::unchecked(owner); let all_allowances = - AllowancesStore::all_allowances(deps.storage, &owner, page, page_size).unwrap_or(vec![]); + AllowancesStore::all_allowances(deps.storage, &owner, page, page_size).unwrap_or_default(); let allowances_result = all_allowances .into_iter() @@ -367,7 +367,7 @@ pub fn query_allowances_received( let spender = Addr::unchecked(spender); let all_allowed = - AllowancesStore::all_allowed(deps.storage, &spender, page, page_size).unwrap_or(vec![]); + AllowancesStore::all_allowed(deps.storage, &spender, page, page_size).unwrap_or_default(); let allowances = all_allowed .into_iter() From 8cba12772a6bf5bd6f31b0c966e26276cc5eedff Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Dec 2024 14:16:21 +1300 Subject: [PATCH 79/87] cargo fmt --- src/btbe.rs | 4 +++- src/gas_tracker.rs | 7 +------ src/query.rs | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/btbe.rs b/src/btbe.rs index 8b2b8f59..63e2e069 100644 --- a/src/btbe.rs +++ b/src/btbe.rs @@ -227,7 +227,9 @@ impl StoredEntry { // peek at the last tx bundle added (read the dummy one if its void) let last_tx_bundle_result = self.get_tx_bundle_at_unchecked(storage, bundle_pos); if last_tx_bundle_result.is_err() { - return Err(StdError::generic_err("missing tx bundle while merging dwb entry!")); + return Err(StdError::generic_err( + "missing tx bundle while merging dwb entry!", + )); } // unwrap diff --git a/src/gas_tracker.rs b/src/gas_tracker.rs index 1864bf4a..0a74317b 100644 --- a/src/gas_tracker.rs +++ b/src/gas_tracker.rs @@ -73,12 +73,7 @@ impl<'a, 'b> GasGroup<'a, 'b> { let gas = self.tracker.api.check_gas(); let log_entry = ( format!("gas.{}", self.name,), - format!( - "{}:{}:{}", - self.index, - gas.unwrap_or(0u64), - comment - ), + format!("{}:{}:{}", self.index, gas.unwrap_or(0u64), comment), ); self.tracker.logs.push(log_entry); self.index += 1; diff --git a/src/query.rs b/src/query.rs index 15f6c05e..777a584e 100644 --- a/src/query.rs +++ b/src/query.rs @@ -217,8 +217,7 @@ pub fn query_transactions( let mut bundle_idx = bundle_idx - 1; if let Some(entry) = account_stored_entry { loop { - let tx_bundle = - entry.get_tx_bundle_at(deps.storage, bundle_idx)?; + let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx)?; // only look if head node is not null if tx_bundle.head_node > 0 { let head_node = TX_NODES From 6b88eca9a6f4707c4856c91a1e27890f5d76a0a1 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 4 Jan 2025 17:32:34 +1300 Subject: [PATCH 80/87] fix wasm-bindgen error --- Cargo.lock | 317 ++++++++++++----------------------------------------- Cargo.toml | 11 +- Makefile | 4 +- src/dwb.rs | 2 +- 4 files changed, 77 insertions(+), 257 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cdbaa77..a52506f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,21 +23,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -102,12 +87,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "byteorder" version = "1.5.0" @@ -156,12 +135,7 @@ version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", "num-traits", - "wasm-bindgen", - "windows-targets", ] [[package]] @@ -183,30 +157,24 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cosmwasm-derive" -version = "1.5.5" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" +checksum = "d67457e4acb04e738788d3489e343957455df2c4643f2b53050eb052ca631d19" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.5.5" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" +checksum = "3e9a7b56d154870ec4b57b224509854f706c9744449548d8a3bf91ac75c59192" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -217,20 +185,20 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.5" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6" +checksum = "edd3d80310cd7b86b09dbe886f4f2ca235a5ddb8d478493c6e50e720a3b38a42" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.94", ] [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -461,29 +429,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "inout" version = "0.1.3" @@ -495,19 +440,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "js-sys" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" -dependencies = [ - "once_cell", - "wasm-bindgen", -] +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "k256" @@ -523,15 +458,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] -name = "log" -version = "0.4.22" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minicbor" @@ -550,9 +485,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -583,9 +518,12 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "primitive-types" @@ -609,31 +547,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -667,7 +596,7 @@ checksum = "46aef80f842736de545ada6ec65b81ee91504efd6853f4b96de7414c42ae7443" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.94", ] [[package]] @@ -717,7 +646,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.66", + "syn 2.0.94", ] [[package]] @@ -797,7 +726,7 @@ dependencies = [ [[package]] name = "secret-toolkit" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=df89b582bc207f4a2f697c31b9a7c64faac10195#df89b582bc207f4a2f697c31b9a7c64faac10195" dependencies = [ "secret-toolkit-crypto", "secret-toolkit-notification", @@ -811,7 +740,7 @@ dependencies = [ [[package]] name = "secret-toolkit-crypto" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=df89b582bc207f4a2f697c31b9a7c64faac10195#df89b582bc207f4a2f697c31b9a7c64faac10195" dependencies = [ "cc", "hkdf", @@ -825,7 +754,7 @@ dependencies = [ [[package]] name = "secret-toolkit-notification" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=df89b582bc207f4a2f697c31b9a7c64faac10195#df89b582bc207f4a2f697c31b9a7c64faac10195" dependencies = [ "chacha20poly1305", "generic-array", @@ -844,7 +773,7 @@ dependencies = [ [[package]] name = "secret-toolkit-permit" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=df89b582bc207f4a2f697c31b9a7c64faac10195#df89b582bc207f4a2f697c31b9a7c64faac10195" dependencies = [ "bech32", "remain", @@ -860,7 +789,7 @@ dependencies = [ [[package]] name = "secret-toolkit-serialization" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=df89b582bc207f4a2f697c31b9a7c64faac10195#df89b582bc207f4a2f697c31b9a7c64faac10195" dependencies = [ "bincode2", "schemars", @@ -871,7 +800,7 @@ dependencies = [ [[package]] name = "secret-toolkit-storage" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=df89b582bc207f4a2f697c31b9a7c64faac10195#df89b582bc207f4a2f697c31b9a7c64faac10195" dependencies = [ "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -882,7 +811,7 @@ dependencies = [ [[package]] name = "secret-toolkit-utils" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=df89b582bc207f4a2f697c31b9a7c64faac10195#df89b582bc207f4a2f697c31b9a7c64faac10195" dependencies = [ "chrono", "schemars", @@ -894,7 +823,7 @@ dependencies = [ [[package]] name = "secret-toolkit-viewing-key" version = "0.10.3" -source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4#4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" +source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=df89b582bc207f4a2f697c31b9a7c64faac10195#df89b582bc207f4a2f697c31b9a7c64faac10195" dependencies = [ "base64 0.21.7", "schemars", @@ -908,9 +837,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -935,13 +864,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.94", ] [[package]] @@ -952,16 +881,17 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.94", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1010,7 +940,6 @@ dependencies = [ "hex", "minicbor", "primitive-types 0.13.1", - "rand", "rand_chacha", "rand_core 0.6.4", "schemars", @@ -1041,9 +970,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -1058,9 +987,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" dependencies = [ "proc-macro2", "quote", @@ -1069,22 +998,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.94", ] [[package]] @@ -1119,9 +1048,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "universal-hash" @@ -1135,9 +1064,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1146,132 +1075,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.66", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "byteorder", + "zerocopy-derive", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.99" +name = "zerocopy-derive" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "syn 2.0.94", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index d9395396..6ce4ab6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ overflow-checks = true [features] # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces -#default = ["debug-print"] +default = [] backtraces = ["cosmwasm-std/backtraces"] gas_tracking = [] gas_evaporation = [] @@ -36,11 +36,11 @@ gas_evaporation = [] [dependencies] cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" } cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11" } -rand = { version = "0.8.5", default-features = false } +cosmwasm-schema = "2.1.5" # secret-toolkit = { version = "0.10.2", default-features = false, features = ["permit", "storage", "viewing-key", "notification"] } -secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"], rev = "4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" } +secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"], rev = "df89b582bc207f4a2f697c31b9a7c64faac10195" } # secret-toolkit-crypto = { version = "0.10.2", default-features = false, features = ["hash", "hkdf", "rand"] } -secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash", "hkdf", "rand"], rev = "4a3d119d24bcf00cc30baf3f20a5b8f6bed65ca4" } +secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash", "hkdf", "rand"], rev = "df89b582bc207f4a2f697c31b9a7c64faac10195" } static_assertions = "1.1.0" rand_core = { version = "0.6.4", default-features = false } @@ -53,6 +53,3 @@ constant_time_eq = "0.3.0" primitive-types = { version = "0.13.1", default-features = false } minicbor = "0.25.1" hex = "0.4.3" - -[dev-dependencies] -cosmwasm-schema = { version = "1.1.8" } diff --git a/Makefile b/Makefile index 9c425118..44dcde21 100644 --- a/Makefile +++ b/Makefile @@ -56,14 +56,14 @@ compile-integration: _compile-integration contract.wasm.gz _compile-integration: DWB_CAPACITY=64 BTBE_CAPACITY=64 RUSTFLAGS='-C link-arg=-s' cargo build --features "gas_tracking" --release --target wasm32-unknown-unknown @# The following line is not necessary, may work only on linux (extra size optimization) - wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm + wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm .PHONY: compile-optimized _compile-optimized compile-optimized: _compile-optimized contract.wasm.gz _compile-optimized: RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown @# The following line is not necessary, may work only on linux (extra size optimization) - wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm + wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm .PHONY: compile-optimized-reproducible compile-optimized-reproducible: diff --git a/src/dwb.rs b/src/dwb.rs index 908de7a4..493a3c48 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -1,6 +1,6 @@ use constant_time_eq::constant_time_eq; use cosmwasm_std::{Api, CanonicalAddr, StdError, StdResult, Storage}; -use rand::RngCore; +use rand_core::RngCore; use secret_toolkit::storage::Item; use secret_toolkit_crypto::ContractPrng; use serde::{Deserialize, Serialize}; From 4efe9ddb874c73c22abad412f0e1dd77bf00f2b7 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 4 Jan 2025 00:04:37 -0800 Subject: [PATCH 81/87] chore: upgrade deps --- tests/dwb/.eslintrc.cjs | 14 ----- tests/dwb/bun.lockb | Bin 105725 -> 113609 bytes tests/dwb/eslint.config.mjs | 20 +++++++ tests/dwb/package.json | 26 ++++----- tests/dwb/src/constants.ts | 2 +- tests/dwb/src/contract.ts | 103 +++--------------------------------- tests/dwb/src/dwb-entry.ts | 2 +- tests/dwb/src/dwb.ts | 8 +-- tests/dwb/src/helper.ts | 7 ++- tests/dwb/src/main.ts | 46 ++++++++-------- tests/dwb/src/snip.ts | 22 +++----- 11 files changed, 79 insertions(+), 171 deletions(-) delete mode 100644 tests/dwb/.eslintrc.cjs create mode 100644 tests/dwb/eslint.config.mjs diff --git a/tests/dwb/.eslintrc.cjs b/tests/dwb/.eslintrc.cjs deleted file mode 100644 index b045f013..00000000 --- a/tests/dwb/.eslintrc.cjs +++ /dev/null @@ -1,14 +0,0 @@ - -module.exports = { - extends: '@blake.regalia/eslint-config-elite', - parserOptions: { - ecmaVersion: 2022, - sourceType: 'module', - tsconfigRootDir: __dirname, - project: 'tsconfig.json', - }, - rules: { - 'no-console': 'off', - '@typescript-eslint/naming-convention': 'off', - }, -}; diff --git a/tests/dwb/bun.lockb b/tests/dwb/bun.lockb index 48417527d80933b514bd493b29e9f0231a69aadb..b81be4e3c423f2aefe7506714333e875153979a4 100755 GIT binary patch delta 31415 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 delta 26822 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 G&;LL9y*D2K diff --git a/tests/dwb/eslint.config.mjs b/tests/dwb/eslint.config.mjs new file mode 100644 index 00000000..52d869ce --- /dev/null +++ b/tests/dwb/eslint.config.mjs @@ -0,0 +1,20 @@ +import elite from '@blake.regalia/eslint-config-elite'; + +export default [ + ...elite, + { + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + + parserOptions: { + tsconfigRootDir: import.meta.dirname, + project: 'tsconfig.json', + }, + }, + rules: { + 'no-console': 'off', + '@typescript-eslint/naming-convention': 'off', + }, + }, +]; diff --git a/tests/dwb/package.json b/tests/dwb/package.json index 2dc0e942..afee1db1 100644 --- a/tests/dwb/package.json +++ b/tests/dwb/package.json @@ -7,23 +7,23 @@ "test": "bun run make && NODE_ENV=development bun run src/main.ts" }, "devDependencies": { - "@blake.regalia/belt": "^0.38.1", - "@blake.regalia/eslint-config-elite": "^0.4.4", + "@blake.regalia/belt": "^0.52.1", + "@blake.regalia/eslint-config-elite": "^0.5.11", "@blake.regalia/tsconfig": "^0.2.0", - "@solar-republic/types": "^0.2.12", - "@types/chai": "^4.3.17", - "@types/node": "^22.1.0", - "chai": "^5.1.1", + "@solar-republic/types": "^0.3.7", + "@types/chai": "^5.0.1", + "@types/node": "^22.10.5", + "chai": "^5.1.2", "chai-bites": "^0.2.0", - "eslint": " 8", - "tsc-esm-fix": "^3.0.1", - "typescript": "^5.5.4" + "eslint": "9", + "tsc-esm-fix": "^3.1.2", + "typescript": "^5.7.2" }, "dependencies": { - "@solar-republic/contractor": "^0.8.17", - "@solar-republic/cosmos-grpc": "^0.17.1", - "@solar-republic/crypto": "^0.2.14", - "@solar-republic/neutrino": "^1.5.3", + "@solar-republic/contractor": "^0.10.3", + "@solar-republic/cosmos-grpc": "^0.17.2", + "@solar-republic/crypto": "^0.3.2", + "@solar-republic/neutrino": "^1.8.5", "bignumber.js": "^9.1.2" } } diff --git a/tests/dwb/src/constants.ts b/tests/dwb/src/constants.ts index 120f6046..1e2bc4a2 100644 --- a/tests/dwb/src/constants.ts +++ b/tests/dwb/src/constants.ts @@ -16,7 +16,7 @@ export const [k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d] = await Promise.al 'buqil+tLeeW7VLuugvOdTmkP3+tUwlCoScPZxeteBPE=', 'UFrCdmofR9iChp6Eg7kE5O3wT+jsOXwJPWwB6kSeuhE=', 'MM/1ZSbT5RF1BnaY6ui/i7yEN0mukGzvXUv+jOyjD0E=', -].map(sb64_sk => Wallet(base64_to_bytes(sb64_sk), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, 'secret'))); +].map(sb64_sk => Wallet(base64_to_bytes(sb64_sk), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, [X_GAS_PRICE, 'uscrt'], 'secret'))); export const H_ADDRS = { [k_wallet_a.addr]: 'Alice', diff --git a/tests/dwb/src/contract.ts b/tests/dwb/src/contract.ts index a9aa3e74..834e3936 100644 --- a/tests/dwb/src/contract.ts +++ b/tests/dwb/src/contract.ts @@ -1,109 +1,18 @@ -import type {JsonObject} from '@blake.regalia/belt'; import type {EncodedGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any'; -import type {TxResultTuple, Wallet, WeakSecretAccAddr} from '@solar-republic/neutrino'; -import type {CwHexLower, WeakUintStr} from '@solar-republic/types'; +import type {TxResponseTuple, Wallet} from '@solar-republic/neutrino'; -import {promisify} from 'node:util'; -import {gunzip} from 'node:zlib'; +import {TendermintEventFilter, broadcast_result, create_and_sign_tx_direct} from '@solar-republic/neutrino'; -import {base64_to_bytes, bytes_to_hex, bytes_to_text, cast, sha256} from '@blake.regalia/belt'; -import {queryCosmosBankBalance} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/query'; -import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any'; -import {SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_STORE_CODE, SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_INSTANTIATE_CONTRACT, encodeSecretComputeMsgStoreCode, encodeSecretComputeMsgInstantiateContract} from '@solar-republic/cosmos-grpc/secret/compute/v1beta1/msg'; -import {querySecretComputeCodeHashByCodeId, querySecretComputeCodes} from '@solar-republic/cosmos-grpc/secret/compute/v1beta1/query'; -import {destructSecretRegistrationKey} from '@solar-republic/cosmos-grpc/secret/registration/v1beta1/msg'; -import {querySecretRegistrationTxKey} from '@solar-republic/cosmos-grpc/secret/registration/v1beta1/query'; -import {SecretWasm, TendermintEventFilter, TendermintWs, broadcast_result, create_and_sign_tx_direct, exec_fees} from '@solar-republic/neutrino'; - -import {X_GAS_PRICE, P_SECRET_LCD, P_SECRET_RPC} from './constants'; +import {P_SECRET_RPC} from './constants'; const k_tef = await TendermintEventFilter(P_SECRET_RPC); -export async function exec(k_wallet: Wallet, atu8_msg: EncodedGoogleProtobufAny, xg_gas_limit: bigint): Promise { - const [atu8_raw, atu8_signdoc, si_txn] = await create_and_sign_tx_direct( +export async function exec(k_wallet: Wallet, atu8_msg: EncodedGoogleProtobufAny, xg_gas_limit: bigint): Promise { + const [atu8_raw, sb16_txn, atu8_signdoc] = await create_and_sign_tx_direct( k_wallet, [atu8_msg], - exec_fees(xg_gas_limit, X_GAS_PRICE, 'uscrt'), xg_gas_limit ); - return await broadcast_result(k_wallet, atu8_raw, si_txn, k_tef); -} - -export async function upload_code(k_wallet: Wallet, atu8_wasm: Uint8Array): Promise { - let atu8_bytecode = atu8_wasm; - - // gzip-encoded; decompress - if(0x1f === atu8_wasm[0] && 0x8b === atu8_wasm[1]) { - atu8_bytecode = await promisify(gunzip)(atu8_wasm); - } - - // hash - const atu8_hash = await sha256(atu8_bytecode); - const sb16_hash = cast(bytes_to_hex(atu8_hash)); - - // fetch all uploaded codes - const [,, g_codes] = await querySecretComputeCodes(P_SECRET_LCD); - - // already uploaded - const g_existing = g_codes?.code_infos?.find(g => g.code_hash! === sb16_hash); - if(g_existing) { - console.info(`Found code ID ${g_existing.code_id} already uploaded to network`); - - return g_existing.code_id as WeakUintStr; - } - - // upload - const [xc_code, sx_res, g_meta, atu8_data, h_events] = await exec(k_wallet, encodeGoogleProtobufAny( - SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_STORE_CODE, - encodeSecretComputeMsgStoreCode( - k_wallet.addr, - atu8_bytecode - ) - ), 30_000000n); - - if(xc_code) throw Error(sx_res); - - return h_events!['message.code_id'][0] as WeakUintStr; -} - -export async function instantiate_contract(k_wallet: Wallet, sg_code_id: WeakUintStr, h_init_msg: JsonObject): Promise { - const [,, g_reg] = await querySecretRegistrationTxKey(P_SECRET_LCD); - const [atu8_cons_pk] = destructSecretRegistrationKey(g_reg!); - const k_wasm = SecretWasm(atu8_cons_pk!); - const [,, g_hash] = await querySecretComputeCodeHashByCodeId(P_SECRET_LCD, sg_code_id); - - // @ts-expect-error imported types versioning - const atu8_body = await k_wasm.encodeMsg(g_hash!.code_hash, h_init_msg); - - const [xc_code, sx_res, g_meta, atu8_data, h_events] = await exec(k_wallet, encodeGoogleProtobufAny( - SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_INSTANTIATE_CONTRACT, - encodeSecretComputeMsgInstantiateContract( - k_wallet.addr, - null, - sg_code_id, - h_init_msg['name'] as string, - atu8_body - ) - ), 10_000_000n); - - if(xc_code) { - const s_error = g_meta?.log ?? sx_res; - - // encrypted error message - const m_response = /(\d+):(?: \w+:)*? encrypted: (.+?): (.+?) contract/.exec(s_error); - if(m_response) { - // destructure match - const [, s_index, sb64_encrypted, si_action] = m_response; - - // decrypt ciphertext - const atu8_plaintext = await k_wasm.decrypt(base64_to_bytes(sb64_encrypted), atu8_body.slice(0, 32)); - - throw Error(bytes_to_text(atu8_plaintext)); - } - - throw Error(sx_res); - } - - return h_events!['message.contract_address'][0] as WeakSecretAccAddr; + return await broadcast_result(k_wallet, atu8_raw, sb16_txn, k_tef); } diff --git a/tests/dwb/src/dwb-entry.ts b/tests/dwb/src/dwb-entry.ts index 32372535..564b806a 100644 --- a/tests/dwb/src/dwb-entry.ts +++ b/tests/dwb/src/dwb-entry.ts @@ -1,5 +1,5 @@ import type {Nilable} from '@blake.regalia/belt'; -import type {CwSecretAccAddr} from '@solar-republic/neutrino'; +import type {CwSecretAccAddr} from '@solar-republic/types'; import {bytes_to_biguint_be, bytes_to_hex} from '@blake.regalia/belt'; import {bech32_encode} from '@solar-republic/crypto'; diff --git a/tests/dwb/src/dwb.ts b/tests/dwb/src/dwb.ts index bd1e112c..e77359d5 100644 --- a/tests/dwb/src/dwb.ts +++ b/tests/dwb/src/dwb.ts @@ -1,13 +1,12 @@ -import type {SecretApp, WeakSecretAccAddr} from '@solar-republic/neutrino'; +import type {SecretApp} from '@solar-republic/neutrino'; +import type {WeakSecretAccAddr} from '@solar-republic/types'; import {bytes, parse_json} from '@blake.regalia/belt'; -import * as chai from 'chai'; -const {expect} = chai; - import {DwbEntry} from './dwb-entry'; import {SX_ANSI_DIM_ON, SX_ANSI_RESET, fail} from './helper'; + export type DwbRequirements = { showDelta?: boolean; shouldNotContainEntriesFor?: WeakSecretAccAddr[]; @@ -75,6 +74,7 @@ export class DwbValidator { return this._a_entries; } + // eslint-disable-next-line @typescript-eslint/require-await async check(gc_check?: DwbRequirements) { const a_prev = this._a_entries_prev; const a_entries = this._a_entries; diff --git a/tests/dwb/src/helper.ts b/tests/dwb/src/helper.ts index 60836095..64de20a0 100644 --- a/tests/dwb/src/helper.ts +++ b/tests/dwb/src/helper.ts @@ -2,7 +2,7 @@ import type {Promisable} from '@blake.regalia/belt'; import {is_string, map_entries} from '@blake.regalia/belt'; -/* eslint-disable @typescript-eslint/naming-convention */ + export const SX_ANSI_RESET = '\x1b[0m'; export const SX_ANSI_DIM_ON = '\x1b[2m'; export const SX_ANSI_UNDERLINE = '\x1b[4m'; @@ -15,13 +15,12 @@ export const SX_ANSI_MAGENTA = '\x1b[35m'; export const SX_ANSI_CYAN = '\x1b[36m'; export const SX_ANSI_WHITE = '\x1b[37m'; export const SX_ANSI_GRAY_BG = '\x1b[100m'; -/* eslint-enable */ + // polyfill crypto global for node.js env -globalThis.crypto ||= (await import('crypto')).webcrypto; +(globalThis as any).crypto ||= (await import('crypto')).webcrypto; export function pass(s_test: string): void { - // eslint-disable-next-line no-console console.log(`${SX_ANSI_GREEN}✓${SX_ANSI_RESET} ${s_test}`); } diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index e3e8b446..ac02b8fe 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -1,7 +1,8 @@ -import type {Dict, JsonObject} from '@blake.regalia/belt'; +import type {JsonObject} from '@blake.regalia/belt'; -import type {SecretContractInterface, FungibleTransferCall, SecretAccAddr, Snip24} from '@solar-republic/contractor'; +import type {SecretContractInterface, FungibleTransferCall, Snip24} from '@solar-republic/contractor'; +import type {TxResponseTuple} from '@solar-republic/neutrino'; import type {CwUint128, WeakUint128Str} from '@solar-republic/types'; import {readFileSync} from 'node:fs'; @@ -9,11 +10,10 @@ import {readFileSync} from 'node:fs'; import {bytes, bytes_to_base64, entries, sha256, text_to_bytes, bigint_greater, bigint_abs} from '@blake.regalia/belt'; import {encodeCosmosBankMsgSend, SI_MESSAGE_TYPE_COSMOS_BANK_MSG_SEND} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/tx'; import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any'; -import {SecretApp, SecretContract, Wallet, broadcast_result, create_and_sign_tx_direct, random_32, type TxMeta, type WeakSecretAccAddr} from '@solar-republic/neutrino'; +import {SecretApp, SecretContract, Wallet, broadcast_result, create_and_sign_tx_direct, random_32, secret_contract_instantiate, secret_contract_upload_code} from '@solar-republic/neutrino'; import {BigNumber} from 'bignumber.js'; import {B_TEST_EVAPORATION, N_DECIMALS, P_SECRET_LCD, P_SECRET_RPC, SI_SECRET_CHAIN, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants'; -import {upload_code, instantiate_contract} from './contract'; import {DwbValidator} from './dwb'; import {GasChecker} from './gas-checker'; import {transfer, type TransferResult} from './snip'; @@ -25,11 +25,11 @@ const atu8_wasm = readFileSync('../../contract.wasm'); console.log(k_wallet_a.addr); console.debug(`Uploading code...`); -const sg_code_id = await upload_code(k_wallet_a, atu8_wasm); +const [sg_code_id] = await secret_contract_upload_code(k_wallet_a, atu8_wasm, 30_000000n); console.debug(`Instantiating contract...`); -const sa_snip = await instantiate_contract(k_wallet_a, sg_code_id, { +const [[sa_snip]=[]] = await secret_contract_instantiate(k_wallet_a, sg_code_id!, { name: S_CONTRACT_LABEL, symbol: 'TKN', decimals: 6, @@ -48,7 +48,7 @@ const sa_snip = await instantiate_contract(k_wallet_a, sg_code_id, { enable_mint: true, enable_burn: true, }, -}); +}, 10_000_000n); console.debug(`Running tests against ${sa_snip}...`); @@ -60,12 +60,12 @@ const k_contract = await SecretContract>(P_SECRET_LCD, sa_snip); +}>>(P_SECRET_LCD, sa_snip!); -const k_app_a = SecretApp(k_wallet_a, k_contract, X_GAS_PRICE); -const k_app_b = SecretApp(k_wallet_b, k_contract, X_GAS_PRICE); -const k_app_c = SecretApp(k_wallet_c, k_contract, X_GAS_PRICE); -const k_app_d = SecretApp(k_wallet_d, k_contract, X_GAS_PRICE); +const k_app_a = SecretApp(k_wallet_a, k_contract); +const k_app_b = SecretApp(k_wallet_b, k_contract); +const k_app_c = SecretApp(k_wallet_c, k_contract); +const k_app_d = SecretApp(k_wallet_d, k_contract); const H_APPS = { a: k_app_a, @@ -104,7 +104,7 @@ if(B_TEST_EVAPORATION) { const xg_gas_wanted = 150_000n; const xg_gas_target = xg_gas_wanted - xg_post_evaporate_buffer; - const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_a.exec('transfer', { + const [g_exec,, [xc_code, sx_res,, g_meta, h_events]] = await k_app_a.exec('transfer', { amount: `${500000n}` as CwUint128, recipient: k_wallet_b.addr, gas_target: `${xg_gas_target}`, @@ -155,7 +155,7 @@ if(B_TEST_EVAPORATION) { let k_checker: GasChecker | null = null; // grant action from previous simultion - let f_grant: undefined | (() => Promise<[w_result: JsonObject | undefined, xc_code: number, s_response: string, g_meta: TxMeta | undefined, h_events: Dict | undefined, si_txn: string | undefined]>); + let f_grant: undefined | (() => Promise<[w_result: JsonObject | undefined, w_resp: any, a6_response: TxResponseTuple]>); // number of simulations to perform const N_SIMULATIONS = 300; @@ -167,20 +167,20 @@ if(B_TEST_EVAPORATION) { for(let i_sim=0; i_sim ${si_receiver}`); // transfer some gas to sim account - const [atu8_raw,, si_txn] = await create_and_sign_tx_direct(k_wallet_b, [ + const [atu8_raw, si_txn] = await create_and_sign_tx_direct(k_wallet_b, [ encodeGoogleProtobufAny( SI_MESSAGE_TYPE_COSMOS_BANK_MSG_SEND, encodeCosmosBankMsgSend(k_wallet_b.addr, k_wallet.addr, [[`${1_000000n}`, 'uscrt']]) ), - ], [[`${5000n}`, 'uscrt']], 50_000n); + ], 50_000n); // submit all in parallel const [ @@ -215,7 +215,7 @@ if(B_TEST_EVAPORATION) { k_checker = new GasChecker((g_result_transfer as TransferResult).tracking, (g_result_transfer as TransferResult).gasUsed); } - xg_max_gas_used_transfer = bigint_greater(xg_max_gas_used_transfer, g_result_transfer.gasUsed); + xg_max_gas_used_transfer = bigint_greater(xg_max_gas_used_transfer, g_result_transfer.gasUsed as bigint); } // reset checker @@ -229,11 +229,11 @@ if(B_TEST_EVAPORATION) { const si_owner = i_sim+''; const si_recipient = (i_sim - 1)+''; - const k_wallet_owner = await Wallet(await sha256(text_to_bytes(si_owner)), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, 'secret'); - const k_wallet_recipient = await Wallet(await sha256(text_to_bytes(si_recipient)), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, 'secret'); + const k_wallet_owner = await Wallet(await sha256(text_to_bytes(si_owner)), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, [X_GAS_PRICE, 'uscrt'], 'secret'); + const k_wallet_recipient = await Wallet(await sha256(text_to_bytes(si_recipient)), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, [X_GAS_PRICE, 'uscrt'], 'secret'); - const k_app_owner = SecretApp(k_wallet_owner, k_contract, X_GAS_PRICE); - const k_app_recipient = SecretApp(k_wallet_recipient, k_contract, X_GAS_PRICE); + const k_app_owner = SecretApp(k_wallet_owner, k_contract); + const k_app_recipient = SecretApp(k_wallet_recipient, k_contract); console.log(`${si_owner} --> ${si_recipient}`); diff --git a/tests/dwb/src/snip.ts b/tests/dwb/src/snip.ts index 4c3b3a62..39236105 100644 --- a/tests/dwb/src/snip.ts +++ b/tests/dwb/src/snip.ts @@ -2,12 +2,12 @@ import type {DwbValidator} from './dwb'; import type {GasChecker} from './gas-checker'; import type {Dict, Nilable} from '@blake.regalia/belt'; import type {SecretContractInterface} from '@solar-republic/contractor'; -import type {SecretApp, WeakSecretAccAddr} from '@solar-republic/neutrino'; -import type {CwUint128, SecretQueryPermit, WeakUintStr} from '@solar-republic/types'; +import type {SecretApp} from '@solar-republic/neutrino'; +import type {CwSecretAccAddr, CwUint128, Snip24QueryPermitMsg, WeakSecretAccAddr} from '@solar-republic/types'; -import {entries, is_bigint, stringify_json} from '@blake.regalia/belt'; +import {entries, stringify_json} from '@blake.regalia/belt'; import {queryCosmosBankBalance} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/query'; -import {sign_secret_query_permit} from '@solar-republic/neutrino'; +import {snip24_amino_sign} from '@solar-republic/neutrino'; import BigNumber from 'bignumber.js'; import {H_ADDRS, N_DECIMALS, P_SECRET_LCD} from './constants'; @@ -41,7 +41,7 @@ type TokenBalance = SecretContractInterface<{ query: { balance: {}; }; - permit: SecretQueryPermit; + permit: Snip24QueryPermitMsg; }; response: { balance: { @@ -60,7 +60,7 @@ export async function scrt_balance(sa_owner: WeakSecretAccAddr): Promise } export async function snip_balance(k_app: SecretApp) { - const g_permit = await sign_secret_query_permit(k_app.wallet, 'snip-balance', [k_app.contract.addr], ['balance']); + const g_permit = await snip24_amino_sign(k_app.wallet, 'snip-balance', [k_app.contract.addr], ['balance']); return await k_app.query('balance', {}, g_permit as unknown as null); } @@ -72,11 +72,10 @@ export async function transfer( k_checker?: Nilable, k_app_sender?: SecretApp ): Promise { - const sa_owner = k_app_owner.wallet.addr; + const sa_owner = k_app_owner.wallet.addr as CwSecretAccAddr; const sa_recipient = k_app_recipient.wallet.addr; // scrt balance of owner before transfer - // @ts-expect-error canonical addr const xg_scrt_balance_owner_before = await scrt_balance(sa_owner); // query balance of owner and recipient @@ -84,14 +83,12 @@ export async function transfer( [g_balance_owner_before], [g_balance_recipient_before], ] = await Promise.all([ - // @ts-expect-error secret app snip_balance(k_app_owner), - // @ts-expect-error secret app snip_balance(k_app_recipient), ]); // execute transfer - const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = k_app_sender + const [g_exec,, [xc_code, sx_res,, g_meta, h_events]] = k_app_sender ? await k_app_sender.exec('transfer_from', { owner: k_app_owner.wallet.addr, amount: `${xg_amount}` as CwUint128, @@ -110,16 +107,13 @@ export async function transfer( [g_balance_owner_after], [g_balance_recipient_after], ] = await Promise.all([ - // @ts-expect-error secret app snip_balance(k_app_owner), - // @ts-expect-error secret app snip_balance(k_app_recipient), ]); if(xc_code) { console.warn('Diagnostics', { scrt_balance_before: xg_scrt_balance_owner_before, - // @ts-expect-error canonical addr scrt_balance_after: await scrt_balance(sa_owner), snip_balance_before: g_balance_owner_before?.amount, snip_balance_after: g_balance_owner_after?.amount, From 8a408d15a5d547a73b21ab02c5996b7890a1c9f6 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 4 Jan 2025 00:47:20 -0800 Subject: [PATCH 82/87] fix: code upload and env --- tests/dwb/.env.example | 8 +++++++- tests/dwb/package.json | 3 ++- tests/dwb/src/dwb.ts | 7 ++++++- tests/dwb/src/main.ts | 9 +++++---- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/dwb/.env.example b/tests/dwb/.env.example index 0a6b74d9..61c5c87c 100644 --- a/tests/dwb/.env.example +++ b/tests/dwb/.env.example @@ -1,4 +1,10 @@ +# enabling DEV will print every query/execution to the chain +# DEV=1 +NODE_ENV=development SECRET_LCD=http://localhost:1317 SECRET_RPC=http://localhost:26657 SECRET_CHAIN=secretdev-1 -ENABLE_EVAPORATION_TESTS=1 +CONTRACT_PATH=../../contract.wasm.gz + +# Secret's evaporation API is currently broken +# ENABLE_EVAPORATION_TESTS=1 diff --git a/tests/dwb/package.json b/tests/dwb/package.json index afee1db1..b57b9868 100644 --- a/tests/dwb/package.json +++ b/tests/dwb/package.json @@ -4,7 +4,8 @@ "scripts": { "build": "tsc && tsc-esm-fix --tsconfig tsconfig.tsc-esm-fix.json --target=dist", "make": "pushd ../../ && make compile-integration && popd", - "test": "bun run make && NODE_ENV=development bun run src/main.ts" + "simulate": "bun run --env-file=.env src/main.ts", + "test": "bun run make && bun run simulate" }, "devDependencies": { "@blake.regalia/belt": "^0.52.1", diff --git a/tests/dwb/src/dwb.ts b/tests/dwb/src/dwb.ts index e77359d5..b30f7947 100644 --- a/tests/dwb/src/dwb.ts +++ b/tests/dwb/src/dwb.ts @@ -56,7 +56,12 @@ export class DwbValidator { this._a_entries_prev = this._a_entries.slice(); // dump dwb contents - const [g_dwb_res] = await this._k_app.query('dwb', {}); + const [g_dwb_res, xc_code, s_err] = await this._k_app.query('dwb', {}); + + // error + if(xc_code) { + throw Error(s_err); + } // parse const { diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts index ac02b8fe..166c2ffb 100644 --- a/tests/dwb/src/main.ts +++ b/tests/dwb/src/main.ts @@ -20,10 +20,11 @@ import {transfer, type TransferResult} from './snip'; const S_CONTRACT_LABEL = 'snip2x-test_'+bytes_to_base64(crypto.getRandomValues(bytes(6))); -const atu8_wasm = readFileSync('../../contract.wasm'); +const atu8_wasm = readFileSync(process.env['CONTRACT_PATH'] ?? '../../contract.wasm.gz'); console.log(k_wallet_a.addr); + console.debug(`Uploading code...`); const [sg_code_id] = await secret_contract_upload_code(k_wallet_a, atu8_wasm, 30_000000n); @@ -187,7 +188,7 @@ if(B_TEST_EVAPORATION) { // @ts-expect-error totally stupid g_result_transfer, [xc_send_gas, s_err_send_gas], - a_res_increase, + [g_res_increase,, [xc_code, s_err]=[]]=[], ] = await Promise.all([ // #ts-expect-error secret app transfer(k_dwbv, i_sim % 2? 1_000000n: 2_000000n, k_app_a, k_app_sim, k_checker), @@ -201,8 +202,8 @@ if(B_TEST_EVAPORATION) { } // increase allowance error - if(f_grant && a_res_increase?.[1]) { - throw Error(`Failed to increase allowance: ${a_res_increase[2]}`); + if(f_grant && xc_code) { + throw Error(`Failed to increase allowance: ${s_err}`); } // approve Alice as spender for future txs From a33e9f46b133b01964e45c949ae97973c3e27ce2 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 4 Jan 2025 08:48:29 +0000 Subject: [PATCH 83/87] fix: gas tracking and makefile --- Makefile | 16 +++++++++++++--- src/contract.rs | 2 +- src/dwb.rs | 2 +- src/execute_deposit_redeem.rs | 4 +++- src/execute_mint_burn.rs | 4 +++- src/execute_transfer_send.rs | 8 +++++--- 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 44dcde21..687bc1f0 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,10 @@ SECRETCLI = docker exec -it secretdev /usr/bin/secretcli +SECRET_GRPC_PORT ?= 9090 +SECRET_LCD_PORT ?= 1317 +SECRET_RPC_PORT ?= 26657 +LOCALSECRET_VERSION ?= v1.15.0 + .PHONY: all all: clippy test @@ -70,7 +75,7 @@ compile-optimized-reproducible: docker run --rm -v "$$(pwd)":/contract \ --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - enigmampc/secret-contract-optimizer:1.0.10 + ghcr.io/scrtlabs/secret-contract-optimizer:1.0.11 contract.wasm.gz: contract.wasm cat ./contract.wasm | gzip -9 > ./contract.wasm.gz @@ -81,9 +86,14 @@ contract.wasm: .PHONY: start-server start-server: # CTRL+C to stop docker run -it --rm \ - -p 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ + -e FAST_BLOCKS=true \ + -p $(SECRET_RPC_PORT):26657 \ + -p $(SECRET_LCD_PORT):1317 \ + -p $(SECRET_GRPC_PORT):9090 \ + -p 5000:5000 \ -v $$(pwd):/root/code \ - --name secretdev docker pull ghcr.io/scrtlabs/localsecret:v1.13.1 + --name secretdev \ + ghcr.io/scrtlabs/localsecret:$(LOCALSECRET_VERSION) .PHONY: schema schema: diff --git a/src/contract.rs b/src/contract.rs index 9068539b..ba5e1ee1 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -22,7 +22,7 @@ use crate::dwb::{DelayedWriteBuffer, DWB}; use crate::btbe::initialize_btbe; #[cfg(feature = "gas_tracking")] -use crate::gas_tracker::{GasTracker, LoggingExt}; +use crate::gas_tracker::GasTracker; #[cfg(feature = "gas_evaporation")] use crate::msg::Evaporator; use crate::msg::{ diff --git a/src/dwb.rs b/src/dwb.rs index 493a3c48..524f8c97 100644 --- a/src/dwb.rs +++ b/src/dwb.rs @@ -178,7 +178,7 @@ impl DelayedWriteBuffer { matched_index } - pub fn add_recipient( + pub fn add_recipient<'a>( &mut self, store: &mut dyn Storage, rng: &mut ContractPrng, diff --git a/src/execute_deposit_redeem.rs b/src/execute_deposit_redeem.rs index c5659f84..0e7e106d 100644 --- a/src/execute_deposit_redeem.rs +++ b/src/execute_deposit_redeem.rs @@ -8,6 +8,8 @@ use crate::dwb::DWB; use crate::msg::{ExecuteAnswer, ResponseStatus::Success}; use crate::state::{safe_add, CONFIG, TOTAL_SUPPLY}; use crate::transaction_history::{store_deposit_action, store_redeem_action}; +#[cfg(feature = "gas_tracking")] +use crate::gas_tracker::GasTracker; // deposit functions @@ -67,7 +69,7 @@ pub fn try_deposit( let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?); #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); + return Ok(tracker.add_to_response(resp)); #[cfg(not(feature = "gas_tracking"))] Ok(resp) diff --git a/src/execute_mint_burn.rs b/src/execute_mint_burn.rs index 7858597c..1afd90f8 100644 --- a/src/execute_mint_burn.rs +++ b/src/execute_mint_burn.rs @@ -17,6 +17,8 @@ use crate::state::{ safe_add, MintersStore, CONFIG, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED, TOTAL_SUPPLY, }; use crate::transaction_history::{store_burn_action, store_mint_action}; +#[cfg(feature = "gas_tracking")] +use crate::gas_tracker::GasTracker; // mint functions @@ -94,7 +96,7 @@ pub fn try_mint( } #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); + return Ok(tracker.add_to_response(resp)); #[cfg(not(feature = "gas_tracking"))] Ok(resp) diff --git a/src/execute_transfer_send.rs b/src/execute_transfer_send.rs index bf52af63..77110476 100644 --- a/src/execute_transfer_send.rs +++ b/src/execute_transfer_send.rs @@ -17,6 +17,8 @@ use crate::receiver::Snip20ReceiveMsg; use crate::state::{ReceiverHashStore, CONFIG, INTERNAL_SECRET_SENSITIVE, NOTIFICATIONS_ENABLED}; use crate::strings::SEND_TO_CONTRACT_ERR_MSG; use crate::transaction_history::store_transfer_action; +#[cfg(feature = "gas_tracking")] +use crate::gas_tracker::GasTracker; // transfer functions @@ -89,7 +91,7 @@ pub fn try_transfer( group1.log("rest"); #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); + return Ok(tracker.add_to_response(resp)); #[cfg(not(feature = "gas_tracking"))] Ok(resp) @@ -190,7 +192,7 @@ pub fn try_batch_transfer( } #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); + return Ok(tracker.add_to_response(resp)); #[cfg(not(feature = "gas_tracking"))] Ok(resp) @@ -383,7 +385,7 @@ pub fn try_send( } #[cfg(feature = "gas_tracking")] - return Ok(resp.add_gas_tracker(tracker)); + return Ok(tracker.add_to_response(resp)); #[cfg(not(feature = "gas_tracking"))] Ok(resp) From e95ec5e0cf6a8f2e4c78d089e2e1f77f1b84301d Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sat, 4 Jan 2025 08:49:12 +0000 Subject: [PATCH 84/87] fix: cleanup --- src/gas_tracker.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/gas_tracker.rs b/src/gas_tracker.rs index 0a74317b..392e4e9b 100644 --- a/src/gas_tracker.rs +++ b/src/gas_tracker.rs @@ -40,16 +40,6 @@ impl<'a> GasTracker<'a> { } } -pub trait LoggingExt { - fn add_gas_tracker(&self, tracker: GasTracker) -> Response; -} - -impl LoggingExt for Response { - fn add_gas_tracker(&self, tracker: GasTracker) -> Response { - tracker.add_to_response(self.to_owned()) - } -} - pub struct GasGroup<'a, 'b> { pub tracker: &'b mut GasTracker<'a>, pub name: String, From 5839af71fe79707bcc1e45c52a8f52b056f22b30 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 7 Jan 2025 20:34:05 +1300 Subject: [PATCH 85/87] fix legacy deposit denom bug --- src/execute_deposit_redeem.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/execute_deposit_redeem.rs b/src/execute_deposit_redeem.rs index 0e7e106d..100c3bef 100644 --- a/src/execute_deposit_redeem.rs +++ b/src/execute_deposit_redeem.rs @@ -55,12 +55,15 @@ pub fn try_deposit( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); + // use first denom given for tx record + let denom = &info.funds[0].denom; + perform_deposit( deps.storage, rng, &sender_address, raw_amount, - "uscrt".to_string(), + denom.clone(), &env.block, #[cfg(feature = "gas_tracking")] &mut tracker, From 5ed9ecfa73b31a20e0c62d09e1d674adf9709ea0 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 8 Jan 2025 11:07:58 +1300 Subject: [PATCH 86/87] clippy fix --- src/execute_deposit_redeem.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/execute_deposit_redeem.rs b/src/execute_deposit_redeem.rs index 100c3bef..4784f25d 100644 --- a/src/execute_deposit_redeem.rs +++ b/src/execute_deposit_redeem.rs @@ -55,15 +55,16 @@ pub fn try_deposit( #[cfg(feature = "gas_tracking")] let mut tracker: GasTracker = GasTracker::new(deps.api); - // use first denom given for tx record - let denom = &info.funds[0].denom; + // we know that funds.len() > 0, because amount > 0 + // use the first denom given for tx record + let denom = info.funds.first().unwrap().denom.clone(); perform_deposit( deps.storage, rng, &sender_address, raw_amount, - denom.clone(), + denom, &env.block, #[cfg(feature = "gas_tracking")] &mut tracker, From ca1602ae2f09ae3f6d1847e92cfcc7b0a62c57f3 Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Sun, 19 Jan 2025 09:50:44 -0800 Subject: [PATCH 87/87] fix: owner permission --- src/contract.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/contract.rs b/src/contract.rs index ba5e1ee1..dcac79e0 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -455,7 +455,8 @@ fn permit_queries( // Permit validated! We can now execute the query. match query { QueryWithPermit::Balance {} => { - if !permit.check_permission(&TokenPermissions::Balance) { + if !permit.check_permission(&TokenPermissions::Balance) + && !permit.check_permission(&TokenPermissions::Owner) { return Err(StdError::generic_err(format!( "No permission to query balance, got permissions {:?}", permit.params.permissions @@ -468,7 +469,8 @@ fn permit_queries( Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG)) } QueryWithPermit::TransactionHistory { page, page_size } => { - if !permit.check_permission(&TokenPermissions::History) { + if !permit.check_permission(&TokenPermissions::History) + && !permit.check_permission(&TokenPermissions::Owner) { return Err(StdError::generic_err(format!( "No permission to query history, got permissions {:?}", permit.params.permissions @@ -478,7 +480,8 @@ fn permit_queries( query::query_transactions(deps, account, page.unwrap_or(0), page_size) } QueryWithPermit::Allowance { owner, spender } => { - if !permit.check_permission(&TokenPermissions::Allowance) { + if !permit.check_permission(&TokenPermissions::Allowance) + && !permit.check_permission(&TokenPermissions::Owner) { return Err(StdError::generic_err(format!( "No permission to query allowance, got permissions {:?}", permit.params.permissions