From 35f6cf56409d95b6e4f6cd194a7c2175244cca13 Mon Sep 17 00:00:00 2001 From: shaorongqiang Date: Fri, 22 Dec 2023 19:00:33 +0800 Subject: [PATCH] wasm add memo --- src/components/finutils/src/bins/fn.rs | 3 +- .../finutils/src/bins/stt/init/i_testing.rs | 12 +- .../finutils/src/bins/stt/init/mod.rs | 16 +- src/components/finutils/src/bins/stt/stt.rs | 32 +- .../finutils/src/common/ddev/init.rs | 12 +- src/components/finutils/src/common/evm.rs | 2 +- src/components/finutils/src/common/mod.rs | 44 +- src/components/finutils/src/common/utils.rs | 54 +- src/components/finutils/src/lib.rs | 3 + src/components/finutils/src/transaction.rs | 768 ++++++++++++++++++ .../finutils/src/txn_builder/mod.rs | 100 ++- src/components/wasm/src/wasm.rs | 21 +- src/ledger/src/data_model/mod.rs | 2 +- 13 files changed, 952 insertions(+), 117 deletions(-) create mode 100644 src/components/finutils/src/transaction.rs diff --git a/src/components/finutils/src/bins/fn.rs b/src/components/finutils/src/bins/fn.rs index 1002fda1d..561c69b9e 100644 --- a/src/components/finutils/src/bins/fn.rs +++ b/src/components/finutils/src/bins/fn.rs @@ -338,6 +338,7 @@ fn run() -> Result<()> { common::transfer_asset( f.as_deref(), t, + None, token_code, am.unwrap(), m.is_present("confidential-amount"), @@ -381,7 +382,7 @@ fn run() -> Result<()> { } else { common::transfer_asset_batch( f.as_deref(), - &t, + &t.iter().map(|v| (v.clone(), None)).collect::>(), None, am.unwrap(), m.is_present("confidential-amount"), diff --git a/src/components/finutils/src/bins/stt/init/i_testing.rs b/src/components/finutils/src/bins/stt/init/i_testing.rs index 31a1d69d6..d0dc743ed 100644 --- a/src/components/finutils/src/bins/stt/init/i_testing.rs +++ b/src/components/finutils/src/bins/stt/init/i_testing.rs @@ -129,7 +129,10 @@ pub fn run_all() -> Result<()> { .map(|v| &v.keypair) .filter(|kp| kp.get_pk() != v0_kp.get_pk()) .collect::>(); - let targets = target_kps.iter().map(|kp| kp.get_pk()).collect::>(); + let targets = target_kps + .iter() + .map(|kp| (kp.get_pk(), None)) + .collect::>(); transfer_asset_batch_x(v0_kp, &targets, Some(code), 1, true, true).c(d!())?; sleep_n_block!(1.2); transfer_asset_batch_x(v0_kp, &targets, Some(code), 1, false, true).c(d!())?; @@ -257,7 +260,7 @@ pub fn run_all() -> Result<()> { balances += n; transfer_asset_batch_x( &v.keypair, - &[v_set[0].pubkey], + &[(v_set[0].pubkey, None)], None, n, false, @@ -283,7 +286,10 @@ pub fn run_all() -> Result<()> { // 17. println!(">>> Transfer to 10 random addresses ..."); let rkps = (0..10).map(|_| gen_random_keypair()).collect::>(); - let targets = rkps.iter().map(|kp| kp.get_pk()).collect::>(); + let targets = rkps + .iter() + .map(|kp| (kp.get_pk(), None)) + .collect::>(); transfer_asset_batch_x( &v_set[0].keypair, &targets, diff --git a/src/components/finutils/src/bins/stt/init/mod.rs b/src/components/finutils/src/bins/stt/init/mod.rs index a5a372eb4..bee180fe7 100644 --- a/src/components/finutils/src/bins/stt/init/mod.rs +++ b/src/components/finutils/src/bins/stt/init/mod.rs @@ -30,7 +30,7 @@ pub fn init(mut interval: u64, is_mainnet: bool, skip_validator: bool) -> Result println!(">>> Block interval: {interval} seconds"); println!(">>> Define and issue FRA ..."); - common::utils::send_tx(&fra_gen_initial_tx(&root_kp)).c(d!())?; + common::utils::send_tx(&fra_gen_initial_tx(&root_kp).into()).c(d!())?; println!(">>> Wait 1.2 block ..."); sleep_n_block!(1.2, interval); @@ -44,7 +44,7 @@ pub fn init(mut interval: u64, is_mainnet: bool, skip_validator: bool) -> Result .values() .map(|u| &u.pubkey) .chain(VALIDATOR_LIST.values().map(|v| &v.pubkey)) - .map(|pk| (pk, FRA_PRE_ISSUE_AMOUNT / 2_000)) + .map(|pk| (pk, FRA_PRE_ISSUE_AMOUNT / 2_000, None)) .collect::>(); // Wallet Address: fra18xkez3fum44jq0zhvwq380rfme7u624cccn3z56fjeex6uuhpq6qv9e4g5 @@ -56,7 +56,7 @@ pub fn init(mut interval: u64, is_mainnet: bool, skip_validator: bool) -> Result let bank = pnk!(wallet::public_key_from_base64( "Oa2RRTzdayA8V2OBE7xp3n3NKrjGJxFTSZZybXOXCDQ=" )); - target_list.push((&bank, FRA_PRE_ISSUE_AMOUNT / 100 * 98)); + target_list.push((&bank, FRA_PRE_ISSUE_AMOUNT / 100 * 98, None)); println!(">>> Transfer FRAs to validators ..."); common::utils::transfer_batch(&root_kp, target_list, None, true, true) @@ -73,7 +73,7 @@ pub fn init(mut interval: u64, is_mainnet: bool, skip_validator: bool) -> Result for (i, v) in VALIDATOR_LIST.values().enumerate() { delegate::gen_tx(&v.name, (400_0000 + i as u64 * 1_0000) * FRA, &v.name) .c(d!()) - .and_then(|tx| common::utils::send_tx(&tx).c(d!()))?; + .and_then(|tx| common::utils::send_tx(&tx.into()).c(d!()))?; } println!(">>> Wait 5 block ..."); @@ -102,7 +102,7 @@ fn re_distribution() -> Result<()> { if TX_FEE_MIN < n { transfer_asset_batch_x( &v.keypair, - &[v_set[0].pubkey], + &[(v_set[0].pubkey, None)], None, n - TX_FEE_MIN, true, @@ -128,7 +128,11 @@ fn re_distribution() -> Result<()> { let expected = (400_0000 + 1_0000 * (v_set.len() as u64 - 1) + 1) * FRA; transfer_asset_batch_x( &v_set[0].keypair, - &v_set.iter().skip(1).map(|v| v.pubkey).collect::>(), + &v_set + .iter() + .skip(1) + .map(|v| (v.pubkey, None)) + .collect::>(), None, expected, true, diff --git a/src/components/finutils/src/bins/stt/stt.rs b/src/components/finutils/src/bins/stt/stt.rs index 84eb29643..61c63ec6f 100644 --- a/src/components/finutils/src/bins/stt/stt.rs +++ b/src/components/finutils/src/bins/stt/stt.rs @@ -127,7 +127,7 @@ fn run() -> Result<()> { let amount = amount.unwrap().parse::().c(d!())?; delegate::gen_tx(user.unwrap(), amount, validator.unwrap()) .c(d!()) - .and_then(|tx| common::utils::send_tx(&tx).c(d!()))?; + .and_then(|tx| common::utils::send_tx(&tx.into()).c(d!()))?; } } else if let Some(m) = matches.subcommand_matches("undelegate") { let user = m.value_of("user"); @@ -143,7 +143,7 @@ fn run() -> Result<()> { let amount = amount.and_then(|am| am.parse::().ok()); undelegate::gen_tx(user.unwrap(), amount, validator) .c(d!()) - .and_then(|tx| common::utils::send_tx(&tx).c(d!()))?; + .and_then(|tx| common::utils::send_tx(&tx.into()).c(d!()))?; } } else if let Some(m) = matches.subcommand_matches("claim") { let user = m.value_of("user"); @@ -158,7 +158,7 @@ fn run() -> Result<()> { }; claim::gen_tx(user.unwrap(), amount) .c(d!()) - .and_then(|tx| common::utils::send_tx(&tx).c(d!()))?; + .and_then(|tx| common::utils::send_tx(&tx.into()).c(d!()))?; } } else if let Some(m) = matches.subcommand_matches("transfer") { let from = m.value_of("from-user"); @@ -173,8 +173,10 @@ fn run() -> Result<()> { .c(d!()) .map(|kp| kp.get_pk()) .or_else(|e| wallet::public_key_from_base64(receiver).c(d!(e)))?; - common::utils::transfer(owner_kp, &target_pk, am, None, false, false) - .c(d!())?; + common::utils::transfer( + owner_kp, &target_pk, am, None, false, false, None, + ) + .c(d!())?; } _ => { println!("{}", m.usage()); @@ -199,12 +201,14 @@ fn run() -> Result<()> { } mod issue { + use { super::*, + finutils::transaction::BuildOperation, ledger::{ data_model::{ - AssetTypeCode, IssueAsset, IssueAssetBody, IssuerKeyPair, Operation, - TxOutput, ASSET_TYPE_FRA, + AssetTypeCode, IssueAsset, IssueAssetBody, IssuerKeyPair, TxOutput, + ASSET_TYPE_FRA, }, staking::FRA_PRE_ISSUE_AMOUNT, }, @@ -220,7 +224,7 @@ mod issue { pub fn issue() -> Result<()> { gen_issue_tx() .c(d!()) - .and_then(|tx| common::utils::send_tx(&tx).c(d!())) + .and_then(|tx| common::utils::send_tx(&tx.into()).c(d!())) } fn gen_issue_tx() -> Result { @@ -265,8 +269,8 @@ mod issue { let asset_issuance_operation = IssueAsset::new(aib, &IssuerKeyPair { keypair: &root_kp }).c(d!())?; - builder.add_operation(Operation::IssueAsset(asset_issuance_operation)); - Ok(builder.take_transaction()) + builder.add_operation(BuildOperation::IssueAsset(asset_issuance_operation)); + Ok(builder.take_transaction().into()) } } @@ -291,7 +295,7 @@ mod delegate { common::utils::gen_transfer_op( owner_kp, - vec![(&BLACK_HOLE_PUBKEY_STAKING, amount)], + vec![(&BLACK_HOLE_PUBKEY_STAKING, amount, None)], None, false, false, @@ -305,7 +309,7 @@ mod delegate { let mut tx = builder.take_transaction(); tx.sign(owner_kp); - Ok(tx) + Ok(tx.into()) } } @@ -341,7 +345,7 @@ mod undelegate { } })?; - Ok(builder.take_transaction()) + Ok(builder.take_transaction().into()) } } @@ -358,7 +362,7 @@ mod claim { builder.add_operation_claim(None, owner_kp, amount); })?; - Ok(builder.take_transaction()) + Ok(builder.take_transaction().into()) } } diff --git a/src/components/finutils/src/common/ddev/init.rs b/src/components/finutils/src/common/ddev/init.rs index 3db94fca6..43a38d610 100644 --- a/src/components/finutils/src/common/ddev/init.rs +++ b/src/components/finutils/src/common/ddev/init.rs @@ -102,7 +102,7 @@ pub(super) fn init(env: &mut Env) -> Result<()> { .custom_data .initial_validators .iter() - .map(|v| (v.xfr_keypair.get_pk_ref(), 500_0000 * FRA)) + .map(|v| (v.xfr_keypair.get_pk_ref(), 500_0000 * FRA, None)) .collect::>(); println!("[ {} ] >>> Transfer FRAs to validators ...", &env.name); @@ -118,7 +118,7 @@ pub(super) fn init(env: &mut Env) -> Result<()> { gen_transfer_op_xx( Some(&gen_8668_endpoint(env)), &v.xfr_keypair, - vec![(&BLACK_HOLE_PUBKEY_STAKING, am)], + vec![(&BLACK_HOLE_PUBKEY_STAKING, am, None)], None, true, false, @@ -136,7 +136,7 @@ pub(super) fn init(env: &mut Env) -> Result<()> { })?; let mut tx = builder.take_transaction(); tx.sign(&v.xfr_keypair); - send_tx(env, &tx).c(d!())?; + send_tx(env, &tx.into()).c(d!())?; } println!("[ {} ] >>> Init work done !", &env.name); @@ -154,7 +154,7 @@ fn setup_initial_validators(env: &Env) -> Result<()> { .collect::>(); builder.add_operation_update_validator(&[], 1, vs).c(d!())?; - send_tx(env, &builder.take_transaction()).c(d!()) + send_tx(env, &builder.take_transaction().into()).c(d!()) } fn send_tx(env: &Env, tx: &Transaction) -> Result<()> { @@ -174,7 +174,7 @@ fn send_tx(env: &Env, tx: &Transaction) -> Result<()> { fn transfer_batch( env: &Env, owner_kp: &XfrKeyPair, - target_list: Vec<(&XfrPublicKey, u64)>, + target_list: Vec<(&XfrPublicKey, u64, Option)>, token_code: Option, confidential_am: bool, confidential_ty: bool, @@ -196,7 +196,7 @@ fn transfer_batch( let mut tx = builder.take_transaction(); tx.sign(owner_kp); - send_tx(env, &tx).c(d!()) + send_tx(env, &tx.into()).c(d!()) } fn new_tx_builder(env: &Env) -> Result { diff --git a/src/components/finutils/src/common/evm.rs b/src/components/finutils/src/common/evm.rs index 14dfde63e..3ea5a4ae4 100644 --- a/src/components/finutils/src/common/evm.rs +++ b/src/components/finutils/src/common/evm.rs @@ -51,7 +51,7 @@ pub fn transfer_to_account( let transfer_op = utils::gen_transfer_op( &kp, - vec![(&BLACK_HOLE_PUBKEY_STAKING, amount)], + vec![(&BLACK_HOLE_PUBKEY_STAKING, amount, None)], asset, false, false, diff --git a/src/components/finutils/src/common/mod.rs b/src/components/finutils/src/common/mod.rs index baa6373aa..5dc6236b3 100644 --- a/src/components/finutils/src/common/mod.rs +++ b/src/components/finutils/src/common/mod.rs @@ -6,10 +6,6 @@ //! This module is the library part of FN. //! -use sha3::{Digest, Keccak256}; -use std::str::FromStr; -use zei::serialization::ZeiFromToBytes; - #[cfg(not(target_arch = "wasm32"))] pub mod dev; @@ -20,14 +16,18 @@ pub mod evm; pub mod utils; use { - self::utils::{get_evm_staking_address, get_validator_memo_and_rate}, - crate::api::DelegationInfo, - crate::common::utils::mapping_address, + crate::{ + api::DelegationInfo, + common::utils::{ + get_evm_staking_address, get_validator_memo_and_rate, mapping_address, + }, + transaction::BuildTransaction, + }, globutils::wallet, lazy_static::lazy_static, ledger::{ data_model::{ - gen_random_keypair, AssetRules, AssetTypeCode, AssetTypePrefix, Transaction, + gen_random_keypair, AssetRules, AssetTypeCode, AssetTypePrefix, BLACK_HOLE_PUBKEY_STAKING, }, staking::{ @@ -37,10 +37,13 @@ use { }, }, ruc::*, + sha3::{Digest, Keccak256}, + std::str::FromStr, std::{env, fs}, tendermint::PrivateKey, utils::{get_block_height, get_local_block_height, parse_td_validator_keys}, web3::types::H160, + zei::serialization::ZeiFromToBytes, zei::{ setup::PublicParams, xfr::{ @@ -156,7 +159,7 @@ pub fn stake( .c(d!())?; utils::gen_transfer_op( &kp, - vec![(&BLACK_HOLE_PUBKEY_STAKING, am)], + vec![(&BLACK_HOLE_PUBKEY_STAKING, am, None)], None, false, false, @@ -221,7 +224,7 @@ pub fn stake_append( builder.add_operation_delegation(&kp, am, td_addr); utils::gen_transfer_op( &kp, - vec![(&BLACK_HOLE_PUBKEY_STAKING, am)], + vec![(&BLACK_HOLE_PUBKEY_STAKING, am, None)], None, false, false, @@ -423,6 +426,7 @@ pub fn setup( pub fn transfer_asset( owner_sk: Option<&str>, target_addr: XfrPublicKey, + memo: Option, token_code: Option, am: &str, confidential_am: bool, @@ -430,7 +434,7 @@ pub fn transfer_asset( ) -> Result<()> { transfer_asset_batch( owner_sk, - &[target_addr], + &[(target_addr, memo)], token_code, am, confidential_am, @@ -443,6 +447,7 @@ pub fn transfer_asset( pub fn transfer_asset_x( kp: &XfrKeyPair, target_addr: XfrPublicKey, + memo: Option, token_code: Option, am: u64, confidential_am: bool, @@ -450,7 +455,7 @@ pub fn transfer_asset_x( ) -> Result<()> { transfer_asset_batch_x( kp, - &[target_addr], + &[(target_addr, memo)], token_code, am, confidential_am, @@ -462,7 +467,7 @@ pub fn transfer_asset_x( #[allow(missing_docs)] pub fn transfer_asset_batch( owner_sk: Option<&str>, - target_addr: &[XfrPublicKey], + target_addr: &[(XfrPublicKey, Option)], token_code: Option, am: &str, confidential_am: bool, @@ -485,7 +490,7 @@ pub fn transfer_asset_batch( #[allow(missing_docs)] pub fn transfer_asset_batch_x( kp: &XfrKeyPair, - target_addr: &[XfrPublicKey], + target_addr: &[(XfrPublicKey, Option)], token_code: Option, am: u64, confidential_am: bool, @@ -493,7 +498,10 @@ pub fn transfer_asset_batch_x( ) -> Result<()> { utils::transfer_batch( kp, - target_addr.iter().map(|addr| (addr, am)).collect(), + target_addr + .iter() + .map(|(addr, memo)| (addr, am, memo.clone())) + .collect(), token_code, confidential_am, confidential_ty, @@ -677,7 +685,7 @@ pub fn show_delegations(sk_str: Option<&str>) -> Result<()> { fn gen_undelegate_tx( owner_kp: &XfrKeyPair, param: Option<(u64, &str)>, -) -> Result { +) -> Result { let mut builder = utils::new_tx_builder().c(d!())?; utils::gen_fee_op(owner_kp).c(d!()).map(|op| { builder.add_operation(op); @@ -706,12 +714,12 @@ fn gen_delegate_tx( owner_kp: &XfrKeyPair, amount: u64, validator: &str, -) -> Result { +) -> Result { let mut builder = utils::new_tx_builder().c(d!())?; utils::gen_transfer_op( owner_kp, - vec![(&BLACK_HOLE_PUBKEY_STAKING, amount)], + vec![(&BLACK_HOLE_PUBKEY_STAKING, amount, None)], None, false, false, diff --git a/src/components/finutils/src/common/utils.rs b/src/components/finutils/src/common/utils.rs index 390b3b64b..ee434fee1 100644 --- a/src/components/finutils/src/common/utils.rs +++ b/src/components/finutils/src/common/utils.rs @@ -2,20 +2,18 @@ //! Some handful function and data structure for findora cli tools //! -use std::ops::Div; - use { crate::{ api::{DelegationInfo, ValidatorDetail}, common::get_serv_addr, + transaction::{BuildOperation, BuildTransaction}, txn_builder::{TransactionBuilder, TransferOperationBuilder}, }, globutils::{wallet, HashOf, SignatureOf}, ledger::{ data_model::{ - AssetType, AssetTypeCode, DefineAsset, Operation, StateCommitmentData, - Transaction, TransferType, TxoRef, TxoSID, Utxo, ASSET_TYPE_FRA, - BLACK_HOLE_PUBKEY, TX_FEE_MIN, + AssetType, AssetTypeCode, DefineAsset, StateCommitmentData, TransferType, + TxoRef, TxoSID, Utxo, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY, TX_FEE_MIN, }, staking::{ init::get_inital_validators, StakerMemo, TendermintAddrRef, FRA_TOTAL_AMOUNT, @@ -28,6 +26,7 @@ use { sha3::Keccak256, std::{ collections::{BTreeMap, HashMap}, + ops::Div, str::FromStr, }, tendermint::{PrivateKey, PublicKey}, @@ -57,7 +56,7 @@ pub fn new_tx_builder() -> Result { #[inline(always)] #[allow(missing_docs)] -pub fn send_tx(tx: &Transaction) -> Result<()> { +pub fn send_tx(tx: &BuildTransaction) -> Result<()> { let url = format!("{}:8669/submit_transaction", get_serv_addr().c(d!())?); let tx_bytes = serde_json::to_vec(tx).c(d!())?; @@ -96,6 +95,7 @@ pub fn transfer( token_code: Option, confidential_am: bool, confidential_ty: bool, + memo: Option, ) -> Result<()> { // FRA asset is the default case if token_code.is_none() && FRA_TOTAL_AMOUNT < am { @@ -105,7 +105,7 @@ pub fn transfer( } transfer_batch( owner_kp, - vec![(target_pk, am)], + vec![(target_pk, am, memo)], token_code, confidential_am, confidential_ty, @@ -117,7 +117,7 @@ pub fn transfer( #[allow(missing_docs)] pub fn transfer_batch( owner_kp: &XfrKeyPair, - target_list: Vec<(&XfrPublicKey, u64)>, + target_list: Vec<(&XfrPublicKey, u64, Option)>, token_code: Option, confidential_am: bool, confidential_ty: bool, @@ -145,12 +145,12 @@ pub fn transfer_batch( #[inline(always)] pub fn gen_transfer_op( owner_kp: &XfrKeyPair, - target_list: Vec<(&XfrPublicKey, u64)>, + target_list: Vec<(&XfrPublicKey, u64, Option)>, token_code: Option, confidential_am: bool, confidential_ty: bool, balance_type: Option, -) -> Result { +) -> Result { gen_transfer_op_x( owner_kp, target_list, @@ -166,13 +166,13 @@ pub fn gen_transfer_op( #[allow(missing_docs)] pub fn gen_transfer_op_x( owner_kp: &XfrKeyPair, - target_list: Vec<(&XfrPublicKey, u64)>, + target_list: Vec<(&XfrPublicKey, u64, Option)>, token_code: Option, auto_fee: bool, confidential_am: bool, confidential_ty: bool, balance_type: Option, -) -> Result { +) -> Result { gen_transfer_op_xx( None, owner_kp, @@ -191,23 +191,23 @@ pub fn gen_transfer_op_x( pub fn gen_transfer_op_xx( rpc_endpoint: Option<&str>, owner_kp: &XfrKeyPair, - mut target_list: Vec<(&XfrPublicKey, u64)>, + mut target_list: Vec<(&XfrPublicKey, u64, Option)>, token_code: Option, auto_fee: bool, confidential_am: bool, confidential_ty: bool, balance_type: Option, -) -> Result { +) -> Result { let mut op_fee: u64 = 0; if auto_fee { - target_list.push((&*BLACK_HOLE_PUBKEY, TX_FEE_MIN)); + target_list.push((&*BLACK_HOLE_PUBKEY, TX_FEE_MIN, None)); op_fee += TX_FEE_MIN; } let asset_type = token_code.map(|code| code.val).unwrap_or(ASSET_TYPE_FRA); let mut trans_builder = TransferOperationBuilder::new(); - let mut am = target_list.iter().map(|(_, am)| *am).sum(); + let mut am = target_list.iter().map(|(_, am, _)| *am).sum(); if asset_type != ASSET_TYPE_FRA { am -= op_fee; } else { @@ -265,6 +265,7 @@ pub fn gen_transfer_op_xx( None, None, None, + None, ) .c(d!())?; } @@ -276,18 +277,21 @@ pub fn gen_transfer_op_xx( _ => AssetRecordType::NonConfidentialAmount_NonConfidentialAssetType, }; - let outputs = target_list.into_iter().map(|(pk, n)| { - AssetRecordTemplate::with_no_asset_tracing( - n, - token_code.map(|code| code.val).unwrap_or(ASSET_TYPE_FRA), - art, - *pk, + let outputs = target_list.into_iter().map(|(pk, n, memo)| { + ( + AssetRecordTemplate::with_no_asset_tracing( + n, + token_code.map(|code| code.val).unwrap_or(ASSET_TYPE_FRA), + art, + *pk, + ), + memo, ) }); - for output in outputs { + for (output, memo) in outputs { trans_builder - .add_output(&output, None, None, None) + .add_output(&output, None, None, None, memo) .c(d!())?; } @@ -305,7 +309,7 @@ pub fn gen_transfer_op_xx( /// for scenes that need to pay a standalone fee without other transfers #[inline(always)] #[allow(missing_docs)] -pub fn gen_fee_op(owner_kp: &XfrKeyPair) -> Result { +pub fn gen_fee_op(owner_kp: &XfrKeyPair) -> Result { gen_transfer_op(owner_kp, vec![], None, false, false, None).c(d!()) } diff --git a/src/components/finutils/src/lib.rs b/src/components/finutils/src/lib.rs index 238827abf..36c76adc8 100644 --- a/src/components/finutils/src/lib.rs +++ b/src/components/finutils/src/lib.rs @@ -10,3 +10,6 @@ pub mod api; #[cfg(feature = "std")] pub mod common; pub mod txn_builder; + +#[allow(missing_docs)] +pub mod transaction; diff --git a/src/components/finutils/src/transaction.rs b/src/components/finutils/src/transaction.rs new file mode 100644 index 000000000..5d45ef018 --- /dev/null +++ b/src/components/finutils/src/transaction.rs @@ -0,0 +1,768 @@ +use std::collections::HashMap; + +use globutils::{HashOf, SignatureOf}; +use ledger::{ + converter::ConvertAccount, + data_model::{ + CredentialProof, DefineAsset, IndexedSignature, IssueAsset, Memo, NoReplayToken, + Operation, Transaction, TransactionBody, TransferAsset, TransferAssetBody, + TransferType, TxOutput, TxnEffect, TxnSID, TxoRef, TxoSID, UpdateMemo, + XfrAddress, __trash__::TxnPolicyData, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY, + TX_FEE_MIN, + }, + staking::ops::{ + claim::ClaimOps, delegation::DelegationOps, + fra_distribution::FraDistributionOps, governance::GovernanceOps, + mint_fra::MintFraOps, replace_staker::ReplaceStakerOps, + undelegation::UnDelegationOps, update_staker::UpdateStakerOps, + update_validator::UpdateValidatorOps, + }, +}; +use rand_chacha::ChaChaRng; +use rand_core::{CryptoRng, RngCore, SeedableRng}; +use ruc::*; +use serde::{Deserialize, Serialize}; +use zei::xfr::{ + lib::{gen_xfr_body, XfrNotePolicies}, + sig::{XfrKeyPair, XfrPublicKey}, + structs::{ + AssetRecord, BlindAssetRecord, OwnerMemo, TracingPolicies, XfrAmount, + XfrAssetType, XfrBody, + }, +}; + +#[inline(always)] +fn is_default(x: &T) -> bool { + x == &T::default() +} + +#[allow(missing_docs)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct BuildTxOutput { + pub id: Option, + pub record: BlindAssetRecord, + #[serde(default)] + #[serde(skip_serializing_if = "is_default")] + pub lien: Option>>, + #[serde(skip_serializing_if = "is_default")] + pub memo: Option, +} +impl From for BuildTxOutput { + fn from(value: TxOutput) -> Self { + Self { + id: value.id, + record: value.record, + lien: value.lien, + memo: None, + } + } +} +impl From for TxOutput { + fn from(value: BuildTxOutput) -> Self { + Self { + id: value.id, + record: value.record, + lien: value.lien, + } + } +} + +/// The inner data of Transfer Operation +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct BuildTransferAssetBody { + /// Ledger address of inputs + pub inputs: Vec, + #[serde(default)] + #[serde(skip_serializing_if = "is_default")] + /// Transfer policies + pub policies: XfrNotePolicies, + /// A array of transaction outputs + pub outputs: Vec, + #[serde(default)] + #[serde(skip_serializing_if = "is_default")] + /// (inp_idx,out_idx,hash) triples signifying that the lien `hash` on + /// the input `inp_idx` gets assigned to the output `out_idx` + pub lien_assignments: Vec<(usize, usize, HashOf>)>, + /// TODO(joe): we probably don't need the whole XfrNote with input records + /// once it's on the chain + /// Encrypted transfer note + pub transfer: Box, + + /// Only Standard type supported + pub transfer_type: TransferType, +} +impl From for BuildTransferAssetBody { + fn from(value: TransferAssetBody) -> Self { + Self { + inputs: value.inputs, + policies: value.policies, + outputs: value.outputs.iter().map(|v| v.clone().into()).collect(), + lien_assignments: value.lien_assignments, + transfer: value.transfer, + transfer_type: value.transfer_type, + } + } +} +impl From for TransferAssetBody { + fn from(value: BuildTransferAssetBody) -> Self { + Self { + inputs: value.inputs, + policies: value.policies, + outputs: value.outputs.iter().map(|v| v.clone().into()).collect(), + lien_assignments: value.lien_assignments, + transfer: value.transfer, + transfer_type: value.transfer_type, + } + } +} + +impl BuildTransferAssetBody { + #[allow(missing_docs)] + #[allow(clippy::too_many_arguments)] + pub fn new( + prng: &mut R, + input_refs: Vec, + input_records: &[AssetRecord], + output_records: &[AssetRecord], + output_memos: &[Option], + policies: Option, + lien_assignments: Vec<(usize, usize, HashOf>)>, + transfer_type: TransferType, + ) -> Result { + let num_inputs = input_records.len(); + let num_outputs = output_records.len(); + + if num_inputs == 0 { + return Err(eg!()); + } + + // If no policies specified, construct set of empty policies + let policies = policies.unwrap_or_else(|| { + let no_policies = TracingPolicies::new(); + XfrNotePolicies::new( + vec![no_policies.clone(); num_inputs], + vec![None; num_inputs], + vec![no_policies; num_outputs], + vec![None; num_outputs], + ) + }); + + // Verify that for each input and output, there is a corresponding policy and credential commitment + if num_inputs != policies.inputs_tracing_policies.len() + || num_inputs != policies.inputs_sig_commitments.len() + || num_outputs != policies.outputs_tracing_policies.len() + || num_outputs != policies.outputs_sig_commitments.len() + { + return Err(eg!()); + } + + let transfer = + Box::new(gen_xfr_body(prng, input_records, output_records).c(d!())?); + let outputs = transfer + .outputs + .iter() + .zip(output_memos.iter()) + .map(|(rec, memo)| BuildTxOutput { + id: None, + record: rec.clone(), + lien: None, + memo: memo.clone(), + }) + .collect(); + Ok(Self { + inputs: input_refs, + outputs, + policies, + lien_assignments, + transfer, + transfer_type, + }) + } + /// Computes a body signature. A body signature represents consent to some part of the asset transfer. If an + /// input_idx is specified, the signature is a co-signature. + #[inline(always)] + pub fn compute_body_signature( + &self, + keypair: &XfrKeyPair, + input_idx: Option, + ) -> IndexedSignature { + let public_key = keypair.get_pk_ref(); + IndexedSignature { + signature: SignatureOf::new(keypair, &(self.clone().into(), input_idx)), + address: XfrAddress { key: *public_key }, + input_idx, + } + } +} /* + /// Verifies a body signature + #[inline(always)] + pub fn verify_body_signature( + &self, + signature: &IndexedSignature, + ) -> bool { + signature.verify(&self.into()) + } + } + */ +#[allow(missing_docs)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct BuildTransferAsset { + pub body: BuildTransferAssetBody, + pub body_signatures: Vec>, +} +impl From for BuildTransferAsset { + fn from(value: TransferAsset) -> Self { + Self { + body: value.body.into(), + body_signatures: value.body_signatures, + } + } +} +impl From for TransferAsset { + fn from(value: BuildTransferAsset) -> Self { + Self { + body: value.body.into(), + body_signatures: value.body_signatures, + } + } +} +impl BuildTransferAsset { + #[inline(always)] + #[allow(missing_docs)] + pub fn new(transfer_body: BuildTransferAssetBody) -> Result { + Ok(Self { + body: transfer_body, + body_signatures: Vec::new(), + }) + } + #[inline(always)] + #[allow(missing_docs)] + pub fn sign(&mut self, keypair: &XfrKeyPair) { + let sig = self.create_input_signature(keypair); + self.attach_signature(sig).unwrap() + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn attach_signature( + &mut self, + sig: IndexedSignature, + ) -> Result<()> { + if !sig.verify(&self.body.clone().into()) { + return Err(eg!()); + } + self.body_signatures.push(sig); + Ok(()) + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn create_input_signature( + &self, + keypair: &XfrKeyPair, + ) -> IndexedSignature { + self.body.compute_body_signature(keypair, None) + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn get_owner_memos_ref(&self) -> Vec> { + self.body + .transfer + .owners_memos + .iter() + .map(|mem| mem.as_ref()) + .collect() + } +} /* + #[inline(always)] + #[allow(missing_docs)] + pub fn get_owner_addresses(&self) -> Vec { + self.body + .transfer + .inputs + .iter() + .map(|record| record.public_key) + .collect() + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn get_outputs_ref(&self) -> Vec<&BuildTxOutput> { + self.body.outputs.iter().collect() + } + } + */ +/// Operation list supported in findora network +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum BuildOperation { + /// Transfer a findora asset, FRA or custom asset + TransferAsset(BuildTransferAsset), + /// Issue a custom asset in findora network + IssueAsset(IssueAsset), + /// Create a new asset for a findora account + DefineAsset(DefineAsset), + /// Update memo for a findora custom asset + UpdateMemo(UpdateMemo), + /// Add or remove validator from findora network + UpdateStaker(UpdateStakerOps), + /// Delegate FRA token to existed validator or self-delegation + Delegation(DelegationOps), + /// Withdraw FRA token from findora network + UnDelegation(Box), + /// Claim rewards + Claim(ClaimOps), + /// Update initial validator list + UpdateValidator(UpdateValidatorOps), + /// Findora network goverance operation + Governance(GovernanceOps), + /// Update FRA distribution + FraDistribution(FraDistributionOps), + /// Coinbase operation + MintFra(MintFraOps), + /// Convert UTXOs to EVM Account balance + ConvertAccount(ConvertAccount), + ///replace staker. + ReplaceStaker(ReplaceStakerOps), +} +impl From<&Operation> for BuildOperation { + fn from(value: &Operation) -> Self { + match value.clone() { + Operation::TransferAsset(op) => Self::TransferAsset(op.into()), + Operation::IssueAsset(op) => Self::IssueAsset(op), + Operation::DefineAsset(op) => Self::DefineAsset(op), + Operation::UpdateMemo(op) => Self::UpdateMemo(op), + Operation::UpdateStaker(op) => Self::UpdateStaker(op), + Operation::Delegation(op) => Self::Delegation(op), + Operation::UnDelegation(op) => Self::UnDelegation(op), + Operation::Claim(op) => Self::Claim(op), + Operation::UpdateValidator(op) => Self::UpdateValidator(op), + Operation::Governance(op) => Self::Governance(op), + Operation::FraDistribution(op) => Self::FraDistribution(op), + Operation::MintFra(op) => Self::MintFra(op), + Operation::ConvertAccount(op) => Self::ConvertAccount(op), + Operation::ReplaceStaker(op) => Self::ReplaceStaker(op), + } + } +} +impl From<&BuildOperation> for Operation { + fn from(value: &BuildOperation) -> Self { + match value.clone() { + BuildOperation::TransferAsset(op) => Self::TransferAsset(op.into()), + BuildOperation::IssueAsset(op) => Self::IssueAsset(op), + BuildOperation::DefineAsset(op) => Self::DefineAsset(op), + BuildOperation::UpdateMemo(op) => Self::UpdateMemo(op), + BuildOperation::UpdateStaker(op) => Self::UpdateStaker(op), + BuildOperation::Delegation(op) => Self::Delegation(op), + BuildOperation::UnDelegation(op) => Self::UnDelegation(op), + BuildOperation::Claim(op) => Self::Claim(op), + BuildOperation::UpdateValidator(op) => Self::UpdateValidator(op), + BuildOperation::Governance(op) => Self::Governance(op), + BuildOperation::FraDistribution(op) => Self::FraDistribution(op), + BuildOperation::MintFra(op) => Self::MintFra(op), + BuildOperation::ConvertAccount(op) => Self::ConvertAccount(op), + BuildOperation::ReplaceStaker(op) => Self::ReplaceStaker(op), + } + } +} + +#[allow(missing_docs)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub struct BuildTransactionBody { + pub no_replay_token: NoReplayToken, + pub operations: Vec, + #[serde(default)] + #[serde(skip_serializing_if = "is_default")] + pub credentials: Vec, + #[serde(default)] + #[serde(skip_serializing_if = "is_default")] + pub policy_options: Option, + #[serde(default)] + #[serde(skip_serializing_if = "is_default")] + pub memos: Vec, +} + +impl From for BuildTransactionBody { + fn from(value: TransactionBody) -> Self { + Self { + no_replay_token: value.no_replay_token, + operations: value.operations.iter().map(|v| v.into()).collect(), + credentials: value.credentials, + policy_options: value.policy_options, + memos: value.memos, + } + } +} + +impl From for TransactionBody { + fn from(value: BuildTransactionBody) -> Self { + Self { + no_replay_token: value.no_replay_token, + operations: value.operations.iter().map(|v| v.into()).collect(), + credentials: value.credentials, + policy_options: value.policy_options, + memos: value.memos, + } + } +} + +impl BuildTransactionBody { + #[inline(always)] + fn from_token(no_replay_token: NoReplayToken) -> Self { + let mut result = Self::default(); + result.no_replay_token = no_replay_token; + result + } +} + +#[allow(missing_docs)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct BuildTransaction { + pub body: BuildTransactionBody, + #[serde(default)] + #[serde(skip_serializing_if = "is_default")] + pub signatures: Vec>, + #[serde(default)] + #[serde(skip_serializing_if = "HashMap::is_empty")] + pub pubkey_sign_map: HashMap>, +} + +impl From for BuildTransaction { + fn from(value: Transaction) -> Self { + Self { + body: value.body.into(), + signatures: value.signatures, + pubkey_sign_map: value.pubkey_sign_map, + } + } +} + +impl From for Transaction { + fn from(value: BuildTransaction) -> Self { + Self { + body: value.body.into(), + signatures: value.signatures, + pubkey_sign_map: value.pubkey_sign_map, + } + } +} + +impl BuildTransaction { + #[inline(always)] + #[allow(missing_docs)] + pub fn is_coinbase_tx(&self) -> bool { + self.body + .operations + .iter() + .any(|o| matches!(o, BuildOperation::MintFra(_))) + } + + // /// All-in-one checker + // #[inline(always)] + // pub fn valid_in_abci(&self) -> bool { + // self.check_fee() && !self.is_coinbase_tx() + // } + + /// A simple fee checker + /// + /// The check logic is as follows: + /// - Only `NonConfidential Operation` can be used as fee + /// - FRA code == [0; ASSET_TYPE_LENGTH] + /// - Fee destination == BLACK_HOLE_PUBKEY + /// - A transaction with an `Operation` of defining/issuing FRA need NOT fee + /// - A transaction with all addresses of inputs equal to BLACK_HOLE_PUBKEY need NOT fee + pub fn check_fee(&self) -> bool { + // This method can not completely solve the DOS risk, + // we should further limit the number of txo[s] in every operation. + // + // But it seems enough when we combine it with limiting + // the payload size of submission-server's http-requests. + self.is_coinbase_tx() + || self.body.operations.iter().any(|ops| { + if let BuildOperation::TransferAsset(ref x) = ops { + return x.body.outputs.iter().any(|o| { + if let XfrAssetType::NonConfidential(ty) = o.record.asset_type { + if ty == ASSET_TYPE_FRA + && *BLACK_HOLE_PUBKEY == o.record.public_key + { + if let XfrAmount::NonConfidential(am) = o.record.amount { + if am > (TX_FEE_MIN - 1) { + return true; + } + } + } + } + false + }); + } else if let BuildOperation::DefineAsset(ref x) = ops { + if x.body.asset.code.val == ASSET_TYPE_FRA { + return true; + } + } else if let BuildOperation::IssueAsset(ref x) = ops { + if x.body.code.val == ASSET_TYPE_FRA { + return true; + } + } else if matches!(ops, BuildOperation::UpdateValidator(_)) { + return true; + } + false + }) + } + + /// findora hash + #[inline(always)] + pub fn hash(&self, id: TxnSID) -> HashOf<(TxnSID, Transaction)> { + HashOf::new(&(id, self.clone().into())) + } + + // /// tendermint hash + // #[inline(always)] + // pub fn hash_tm(&self) -> HashOf { + // HashOf::new(self) + // } + + // #[inline(always)] + // #[allow(missing_docs)] + // pub fn hash_tm_rawbytes(&self) -> Vec { + // self.hash_tm().0.hash.as_ref().to_vec() + // } + + #[inline(always)] + #[allow(missing_docs)] + pub fn handle(&self) -> String { + let digest = self.hash(TxnSID(0)); + hex::encode(digest) + } + + /// Create a transaction from seq id + #[inline(always)] + pub fn from_seq_id(seq_id: u64) -> Self { + let mut prng = ChaChaRng::from_entropy(); + let no_replay_token = NoReplayToken::new(&mut prng, seq_id); + Self { + body: BuildTransactionBody::from_token(no_replay_token), + signatures: Vec::new(), + pubkey_sign_map: Default::default(), + } + } + + /// Create a transaction from a operation + // #[inline(always)] + // pub fn from_operation(op: Operation, seq_id: u64) -> Self { + // let mut tx = Transaction::from_seq_id(seq_id); + // tx.add_operation(op); + // tx + // } + + // /// Create a transaction from coinbase operation + // #[inline(always)] + // pub fn from_operation_coinbase_mint(op: Operation, seq_id: u64) -> Self { + // let mut tx = Transaction { + // body: TransactionBody::from_token(NoReplayToken::unsafe_new( + // seq_id.saturating_add(1357).saturating_mul(89), + // seq_id, + // )), + // signatures: Vec::new(), + // pubkey_sign_map: Default::default(), + // }; + // tx.add_operation(op); + // tx + // } + + #[inline(always)] + #[allow(missing_docs)] + pub fn add_operation(&mut self, mut op: BuildOperation) { + set_no_replay_token(&mut op, self.body.no_replay_token); + self.body.operations.push(op); + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn sign(&mut self, keypair: &XfrKeyPair) { + self.signatures + .push(SignatureOf::new(keypair, &self.body.clone().into())); + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn sign_to_map(&mut self, keypair: &XfrKeyPair) { + self.pubkey_sign_map.insert( + keypair.pub_key, + SignatureOf::new(keypair, &self.body.clone().into()), + ); + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn check_signature( + &self, + public_key: &XfrPublicKey, + sig: &SignatureOf, + ) -> Result<()> { + sig.verify(public_key, &self.body.clone().into()).c(d!()) + } + + #[inline(always)] + #[allow(missing_docs)] + pub fn get_owner_memos_ref(&self) -> Vec> { + let mut memos = Vec::new(); + for op in self.body.operations.iter() { + match op { + BuildOperation::TransferAsset(xfr_asset) => { + memos.append(&mut xfr_asset.get_owner_memos_ref()); + } + BuildOperation::MintFra(mint_asset) => { + memos.append(&mut mint_asset.get_owner_memos_ref()); + } + BuildOperation::IssueAsset(issue_asset) => { + memos.append(&mut issue_asset.get_owner_memos_ref()); + } + _ => {} + } + } + memos + } + + /// Returns the outputs of a transaction. Internally spent outputs can be optionally included. + /// This will never panic on a well formed transaction, but may panic on a malformed one. + #[inline(always)] + pub fn get_outputs_ref(&self, include_spent: bool) -> Vec { + let eff = TxnEffect::compute_effect(self.clone().into()).unwrap(); + if !include_spent { + eff.txos.into_iter().flatten().collect() + } else { + let mut spent = eff.internally_spent_txos.into_iter(); + let mut ret = Vec::new(); + for txo in eff.txos.into_iter() { + if let Some(txo) = txo { + ret.push(txo); + } else { + ret.push(spent.next().unwrap()); + } + } + ret + } + } + + // /// NOTE: this does *not* guarantee that a private key affiliated with + // /// `public_key` has signed this transaction! If `public_key` is derived + // /// from `self` somehow, then it is infeasible for someone to forge a + // /// passing signature, but it is plausible for someone to generate an + // /// unrelated `public_key` which can pass this signature check! + // #[inline(always)] + // pub fn check_has_signature(&self, public_key: &XfrPublicKey) -> Result<()> { + // let serialized = Serialized::new(&self.body); + // for sig in self.signatures.iter() { + // match sig.0.verify(public_key, &serialized) { + // Err(_) => {} + // Ok(_) => { + // return Ok(()); + // } + // } + // } + // Err(eg!()) + // } + + // #[inline(always)] + // #[allow(missing_docs)] + // pub fn check_has_signature_from_map(&self, public_key: &XfrPublicKey) -> Result<()> { + // if let Some(sign) = self.pubkey_sign_map.get(public_key) { + // sign.0.verify(public_key, &Serialized::new(&self.body)) + // } else { + // Err(eg!( + // "the pubkey not match: {}", + // public_key_to_base64(public_key) + // )) + // } + // } + + // /// NOTE: This method is used to verify the signature in the transaction, + // /// when the user constructs the transaction not only needs to sign each `operation`, + // /// but also needs to sign the whole transaction, otherwise it will not be passed here + // #[inline(always)] + // pub fn check_tx(&self) -> Result<()> { + // let select_check = |tx: &Transaction, pk: &XfrPublicKey| -> Result<()> { + // if tx.signatures.is_empty() { + // tx.check_has_signature_from_map(pk) + // } else { + // tx.check_has_signature(pk) + // } + // }; + + // for operation in self.body.operations.iter() { + // match operation { + // Operation::TransferAsset(o) => { + // for pk in o.get_owner_addresses().iter() { + // select_check(self, pk).c(d!())?; + // } + // } + // Operation::IssueAsset(o) => { + // select_check(self, &o.pubkey.key).c(d!())?; + // } + // Operation::DefineAsset(o) => { + // select_check(self, &o.pubkey.key).c(d!())?; + // } + // Operation::UpdateMemo(o) => { + // select_check(self, &o.pubkey).c(d!())?; + // } + // Operation::UpdateStaker(o) => { + // select_check(self, &o.pubkey).c(d!())?; + // } + // Operation::Delegation(o) => { + // select_check(self, &o.pubkey).c(d!())?; + // } + // Operation::UnDelegation(o) => { + // select_check(self, &o.pubkey).c(d!())?; + // } + // Operation::Claim(o) => { + // select_check(self, &o.pubkey).c(d!())?; + // } + // Operation::UpdateValidator(_) => {} + // Operation::Governance(_) => {} + // Operation::FraDistribution(_) => {} + // Operation::MintFra(_) => {} + // Operation::ConvertAccount(o) => { + // select_check(self, &o.signer).c(d!())?; + // } + // Operation::ReplaceStaker(o) => { + // if !o.get_related_pubkeys().is_empty() { + // for pk in o.get_related_pubkeys() { + // select_check(self, &pk).c(Ok!())?; + // } + // } + // } + // } + // } + + // d(()) + // } +} +#[allow(missing_docs)] +pub fn set_no_replay_token(op: &mut BuildOperation, no_replay_token: NoReplayToken) { + match op { + BuildOperation::UpdateStaker(i) => { + i.set_nonce(no_replay_token); + } + BuildOperation::Delegation(i) => { + i.set_nonce(no_replay_token); + } + BuildOperation::UnDelegation(i) => { + i.set_nonce(no_replay_token); + } + BuildOperation::Claim(i) => { + i.set_nonce(no_replay_token); + } + BuildOperation::FraDistribution(i) => { + i.set_nonce(no_replay_token); + } + BuildOperation::UpdateValidator(i) => { + i.set_nonce(no_replay_token); + } + BuildOperation::Governance(i) => { + i.set_nonce(no_replay_token); + } + BuildOperation::UpdateMemo(i) => i.body.no_replay_token = no_replay_token, + BuildOperation::ConvertAccount(i) => i.set_nonce(no_replay_token), + _ => {} + } +} diff --git a/src/components/finutils/src/txn_builder/mod.rs b/src/components/finutils/src/txn_builder/mod.rs index c4ef9a1a0..f170a8f69 100644 --- a/src/components/finutils/src/txn_builder/mod.rs +++ b/src/components/finutils/src/txn_builder/mod.rs @@ -5,6 +5,13 @@ #![deny(warnings)] #![allow(clippy::needless_borrow)] +use crate::transaction::{ + BuildOperation, BuildTransaction, BuildTransferAsset, BuildTransferAssetBody, +}; + +//#[cfg(not(target_arch = "wasm32"))] +use zei::serialization::ZeiFromToBytes; + use { credentials::CredUserSecretKey, curve25519_dalek::scalar::Scalar, @@ -15,10 +22,9 @@ use { data_model::{ AssetRules, AssetTypeCode, ConfidentialMemo, DefineAsset, DefineAssetBody, IndexedSignature, IssueAsset, IssueAssetBody, IssuerKeyPair, - IssuerPublicKey, Memo, NoReplayToken, Operation, Transaction, - TransactionBody, TransferAsset, TransferAssetBody, TransferType, TxOutput, - TxoRef, UpdateMemo, UpdateMemoBody, ASSET_TYPE_FRA, BLACK_HOLE_PUBKEY, - TX_FEE_MIN, + IssuerPublicKey, Memo, NoReplayToken, TransactionBody, TransferAssetBody, + TransferType, TxOutput, TxoRef, UpdateMemo, UpdateMemoBody, ASSET_TYPE_FRA, + BLACK_HOLE_PUBKEY, TX_FEE_MIN, }, staking::{ is_valid_tendermint_addr, @@ -50,7 +56,6 @@ use { ac_confidential_open_commitment, ACCommitment, ACCommitmentKey, ConfidentialAC, Credential, }, - serialization::ZeiFromToBytes, setup::PublicParams, xfr::{ asset_record::{ @@ -115,7 +120,7 @@ impl FeeInputs { /// An simple builder for findora transaction #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TransactionBuilder { - txn: Transaction, + txn: BuildTransaction, outputs: u64, #[allow(missing_docs)] pub no_replay_token: NoReplayToken, @@ -123,12 +128,12 @@ pub struct TransactionBuilder { impl TransactionBuilder { /// Convert builder to it's inner transaction - pub fn into_transaction(self) -> Transaction { + pub fn into_transaction(self) -> BuildTransaction { self.txn } /// Get reference of it's inner transaction - pub fn get_transaction(&self) -> &Transaction { + pub fn get_transaction(&self) -> &BuildTransaction { &self.txn } @@ -152,10 +157,10 @@ impl TransactionBuilder { .operations .iter() .flat_map(|new| match new { - Operation::TransferAsset(d) => { + BuildOperation::TransferAsset(d) => { seek!(d) } - Operation::IssueAsset(d) => d + BuildOperation::IssueAsset(d) => d .body .records .iter() @@ -172,6 +177,7 @@ impl TransactionBuilder { pub fn add_fee_relative_auto( &mut self, kp: &XfrKeyPair, + memo: Option, ) -> Result<&mut TransactionBuilder> { let mut opb = TransferOperationBuilder::default(); let outputs = self.get_relative_outputs(); @@ -202,6 +208,7 @@ impl TransactionBuilder { None, None, None, + memo, ) .c(d!()) .and_then(|o| o.balance(None).c(d!())) @@ -213,7 +220,11 @@ impl TransactionBuilder { /// As the last operation of any transaction, /// add a static fee to the transaction. - pub fn add_fee(&mut self, inputs: FeeInputs) -> Result<&mut TransactionBuilder> { + pub fn add_fee( + &mut self, + inputs: FeeInputs, + memo: Option, + ) -> Result<&mut TransactionBuilder> { let mut kps = vec![]; let mut opb = TransferOperationBuilder::default(); @@ -239,6 +250,7 @@ impl TransactionBuilder { None, None, None, + memo, ) .c(d!()) .and_then(|o| o.balance(None).c(d!())) @@ -279,7 +291,7 @@ impl TransactionBuilder { let mut prng = ChaChaRng::from_entropy(); let no_replay_token = NoReplayToken::new(&mut prng, seq_id); TransactionBuilder { - txn: Transaction::from_seq_id(seq_id), + txn: BuildTransaction::from_seq_id(seq_id), outputs: 0, no_replay_token, } @@ -329,12 +341,12 @@ impl TransactionBuilder { } #[allow(missing_docs)] - pub fn transaction(&self) -> &Transaction { + pub fn transaction(&self) -> &BuildTransaction { &self.txn } #[allow(missing_docs)] - pub fn take_transaction(self) -> Transaction { + pub fn take_transaction(self) -> BuildTransaction { self.txn } @@ -357,7 +369,7 @@ impl TransactionBuilder { None => AssetTypeCode::gen_random(), }; let iss_keypair = IssuerKeyPair { keypair: &key_pair }; - self.txn.add_operation(Operation::DefineAsset( + self.txn.add_operation(BuildOperation::DefineAsset( DefineAsset::new( DefineAssetBody::new( &token_code, @@ -387,7 +399,7 @@ impl TransactionBuilder { ) -> Result<&mut Self> { let iss_keypair = IssuerKeyPair { keypair: &key_pair }; - self.txn.add_operation(Operation::IssueAsset( + self.txn.add_operation(BuildOperation::IssueAsset( IssueAsset::new( IssueAssetBody::new(token_code, seq_num, &records_and_memos).c(d!())?, &iss_keypair, @@ -407,6 +419,7 @@ impl TransactionBuilder { input_tracing_policies: Vec>, _input_identity_commitments: Vec>, output_records: &[AssetRecord], + output_memos: &[Option], _output_identity_commitments: Vec>, ) -> Result<&mut Self> { let mut prng = ChaChaRng::from_entropy(); @@ -428,12 +441,13 @@ impl TransactionBuilder { ); } - let mut xfr = TransferAsset::new( - TransferAssetBody::new( + let mut xfr = BuildTransferAsset::new( + BuildTransferAssetBody::new( &mut prng, input_sids, &input_asset_records[..], output_records, + output_memos, None, vec![], TransferType::Standard, @@ -443,7 +457,7 @@ impl TransactionBuilder { .c(d!())?; xfr.sign(&keys); - self.txn.add_operation(Operation::TransferAsset(xfr)); + self.txn.add_operation(BuildOperation::TransferAsset(xfr)); Ok(self) } @@ -464,7 +478,7 @@ impl TransactionBuilder { auth_key_pair, ); memo_update.pubkey = auth_key_pair.get_pk(); - let op = Operation::UpdateMemo(memo_update); + let op = BuildOperation::UpdateMemo(memo_update); self.txn.add_operation(op); self } @@ -485,7 +499,7 @@ impl TransactionBuilder { None, self.txn.body.no_replay_token, ); - self.add_operation(Operation::Delegation(op)) + self.add_operation(BuildOperation::Delegation(op)) } /// Add a operation to updating staker memo and commission_rate @@ -514,7 +528,7 @@ impl TransactionBuilder { self.txn.body.no_replay_token, ); - Ok(self.add_operation(Operation::UpdateStaker(op))) + Ok(self.add_operation(BuildOperation::UpdateStaker(op))) } /// Add a staking operation to add a tendermint node as a validator @@ -551,7 +565,7 @@ impl TransactionBuilder { self.txn.body.no_replay_token, ); - Ok(self.add_operation(Operation::Delegation(op))) + Ok(self.add_operation(BuildOperation::Delegation(op))) } /// Add a operation to reduce delegation amount of a findora account. @@ -563,7 +577,7 @@ impl TransactionBuilder { pu: Option, ) -> &mut Self { let op = UnDelegationOps::new(keypair, self.txn.body.no_replay_token, pu); - self.add_operation(Operation::UnDelegation(Box::new(op))) + self.add_operation(BuildOperation::UnDelegation(Box::new(op))) } /// Add a operation to claim all the rewards @@ -574,7 +588,7 @@ impl TransactionBuilder { am: Option, ) -> &mut Self { let op = ClaimOps::new(td_addr, keypair, am, self.txn.body.no_replay_token); - self.add_operation(Operation::Claim(op)) + self.add_operation(BuildOperation::Claim(op)) } #[allow(missing_docs)] @@ -585,7 +599,7 @@ impl TransactionBuilder { ) -> Result<&mut Self> { FraDistributionOps::new(kps, alloc_table, self.txn.body.no_replay_token) .c(d!()) - .map(move |op| self.add_operation(Operation::FraDistribution(op))) + .map(move |op| self.add_operation(BuildOperation::FraDistribution(op))) } #[allow(missing_docs)] @@ -604,7 +618,7 @@ impl TransactionBuilder { self.txn.body.no_replay_token, ) .c(d!()) - .map(move |op| self.add_operation(Operation::Governance(op))) + .map(move |op| self.add_operation(BuildOperation::Governance(op))) } /// Add a operation update the validator set at specified block height. @@ -616,7 +630,7 @@ impl TransactionBuilder { ) -> Result<&mut Self> { UpdateValidatorOps::new(kps, h, v_set, self.txn.body.no_replay_token) .c(d!()) - .map(move |op| self.add_operation(Operation::UpdateValidator(op))) + .map(move |op| self.add_operation(BuildOperation::UpdateValidator(op))) } /// Add an operation to replace the staker of validator. @@ -634,7 +648,7 @@ impl TransactionBuilder { td_addr, self.txn.body.no_replay_token, ); - self.add_operation(Operation::ReplaceStaker(ops)); + self.add_operation(BuildOperation::ReplaceStaker(ops)); Ok(self) } @@ -647,7 +661,7 @@ impl TransactionBuilder { asset: Option, lowlevel_data: Option>, ) -> Result<&mut Self> { - self.add_operation(Operation::ConvertAccount(ConvertAccount { + self.add_operation(BuildOperation::ConvertAccount(ConvertAccount { signer: kp.get_pk(), nonce: self.txn.body.no_replay_token, receiver: addr, @@ -659,7 +673,7 @@ impl TransactionBuilder { } #[allow(missing_docs)] - pub fn add_operation(&mut self, op: Operation) -> &mut Self { + pub fn add_operation(&mut self, op: BuildOperation) -> &mut Self { self.txn.add_operation(op); self } @@ -784,9 +798,11 @@ pub struct TransferOperationBuilder { inputs_tracing_policies: Vec, input_identity_commitments: Vec>, output_records: Vec, + output_memos: Vec>, + outputs_tracing_policies: Vec, output_identity_commitments: Vec>, - transfer: Option, + transfer: Option, transfer_type: TransferType, auto_refund: bool, } @@ -845,6 +861,7 @@ impl TransferOperationBuilder { tracing_policies: Option, identity_commitment: Option, credential_record: Option<(&CredUserSecretKey, &Credential, &ACCommitmentKey)>, + output_memo: Option, ) -> Result<&mut Self> { let prng = &mut ChaChaRng::from_entropy(); if self.transfer.is_some() { @@ -869,6 +886,7 @@ impl TransferOperationBuilder { .c(d!())? }; self.output_records.push(ar); + self.output_memos.push(output_memo); self.outputs_tracing_policies.push(policies); self.output_identity_commitments.push(identity_commitment); Ok(self) @@ -881,6 +899,7 @@ impl TransferOperationBuilder { credential_record: Option<(&CredUserSecretKey, &Credential, &ACCommitmentKey)>, prng: &mut R, blinds: &mut ((Scalar, Scalar), Scalar), + output_memo: Option, ) -> Result<&mut Self> { if self.transfer.is_some() { return Err(eg!( @@ -936,6 +955,7 @@ impl TransferOperationBuilder { blinds.0 = amount_blinds; blinds.1 = type_blind; self.output_records.push(ar); + self.output_memos.push(output_memo); self.outputs_tracing_policies .push(asset_record_template.asset_tracing_policies.clone()); self.output_identity_commitments.push(None); @@ -975,6 +995,7 @@ impl TransferOperationBuilder { let spend_total: u64 = self.spend_amounts.iter().sum(); let mut partially_consumed_inputs = Vec::new(); + let mut output_memos = Vec::new(); for (idx, ((spend_amount, ar), policies)) in self .spend_amounts @@ -1006,6 +1027,7 @@ impl TransferOperationBuilder { ) .c(d!())?; partially_consumed_inputs.push(ar); + output_memos.push(None); self.outputs_tracing_policies.push(policies.clone()); self.output_identity_commitments.push(None); @@ -1024,6 +1046,7 @@ impl TransferOperationBuilder { return Err(eg!(format!("{spend_total} != {output_total}"))); } self.output_records.append(&mut partially_consumed_inputs); + self.output_memos.append(&mut output_memos); // for: repeated/idempotent balance amt_cache.into_iter().for_each(|(idx, am)| { @@ -1051,17 +1074,18 @@ impl TransferOperationBuilder { self.outputs_tracing_policies.clone(), vec![None; num_outputs], ); - let body = TransferAssetBody::new( + let body = BuildTransferAssetBody::new( &mut prng, self.input_sids.clone(), &self.input_records, &self.output_records, + &self.output_memos, Some(xfr_policies), vec![], transfer_type, ) .c(d!())?; - self.transfer = Some(TransferAsset::new(body).c(d!())?); + self.transfer = Some(BuildTransferAsset::new(body).c(d!())?); Ok(self) } @@ -1112,11 +1136,13 @@ impl TransferOperationBuilder { } /// Return the transaction operation - pub fn transaction(&self) -> Result { + pub fn transaction(&self) -> Result { if self.transfer.is_none() { return Err(eg!(no_transfer_err!())); } - Ok(Operation::TransferAsset(self.transfer.clone().c(d!())?)) + Ok(BuildOperation::TransferAsset( + self.transfer.clone().c(d!())?, + )) } /// Checks to see whether all necessary signatures are present and valid @@ -1128,7 +1154,7 @@ impl TransferOperationBuilder { let trn = self.transfer.as_ref().c(d!())?; let mut sig_keys = HashSet::new(); for sig in &trn.body_signatures { - if !sig.verify(&trn.body) { + if !sig.verify(&trn.body.clone().into()) { return Err(eg!(("Invalid signature"))); } sig_keys.insert(sig.address.key.zei_to_bytes()); diff --git a/src/components/wasm/src/wasm.rs b/src/components/wasm/src/wasm.rs index 4bf909c71..13b72c7a4 100644 --- a/src/components/wasm/src/wasm.rs +++ b/src/components/wasm/src/wasm.rs @@ -258,9 +258,10 @@ impl TransactionBuilder { pub fn add_fee_relative_auto( mut self, kp: XfrKeyPair, + memo: Option, ) -> Result { self.transaction_builder - .add_fee_relative_auto(&kp) + .add_fee_relative_auto(&kp, memo) .c(d!()) .map_err(error_to_jsvalue)?; Ok(self) @@ -292,9 +293,13 @@ impl TransactionBuilder { /// As the last operation of any transaction, /// add a static fee to the transaction. - pub fn add_fee(mut self, inputs: FeeInputs) -> Result { + pub fn add_fee( + mut self, + inputs: FeeInputs, + memo: Option, + ) -> Result { self.transaction_builder - .add_fee(inputs.into()) + .add_fee(inputs.into(), memo) .c(d!()) .map_err(error_to_jsvalue)?; Ok(self) @@ -555,7 +560,7 @@ impl TransactionBuilder { mut self, op: String, ) -> Result { - let op = serde_json::from_str::(&op) + let op = serde_json::from_str::(&op) .c(d!()) .map_err(error_to_jsvalue)?; self.get_builder_mut().add_operation(op); @@ -751,6 +756,7 @@ impl TransferOperationBuilder { code: String, conf_amount: bool, conf_type: bool, + memo: Option, ) -> Result { let code = AssetTypeCode::new_from_base64(&code) .c(d!()) @@ -780,6 +786,7 @@ impl TransferOperationBuilder { tracing_policies.map(|policies| policies.get_policies_ref().clone()), None, None, + memo, ) .c(d!()) .map_err(error_to_jsvalue)?; @@ -868,6 +875,7 @@ impl TransferOperationBuilder { code: String, conf_amount: bool, conf_type: bool, + memo: Option, ) -> Result { self.add_output( amount, @@ -876,6 +884,7 @@ impl TransferOperationBuilder { code, conf_amount, conf_type, + memo, ) } @@ -894,8 +903,9 @@ impl TransferOperationBuilder { code: String, conf_amount: bool, conf_type: bool, + memo: Option, ) -> Result { - self.add_output(amount, recipient, None, code, conf_amount, conf_type) + self.add_output(amount, recipient, None, code, conf_amount, conf_type, memo) } /// Wraps around TransferOperationBuilder to ensure the transfer inputs and outputs are balanced. @@ -1290,6 +1300,7 @@ pub fn trace_assets( use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead}; use aes_gcm::Aes256Gcm; +use finutils::transaction::BuildOperation; use rand::{thread_rng, Rng}; use ring::pbkdf2; use std::num::NonZeroU32; diff --git a/src/ledger/src/data_model/mod.rs b/src/ledger/src/data_model/mod.rs index d3b2a54e7..f23eee11d 100644 --- a/src/ledger/src/data_model/mod.rs +++ b/src/ledger/src/data_model/mod.rs @@ -5,7 +5,7 @@ #![allow(clippy::field_reassign_with_default)] #![allow(clippy::assertions_on_constants)] -mod __trash__; +pub mod __trash__; mod effects; mod test;