Sender or Recipient pays fee

This commit is contained in:
Hanh 2022-12-11 06:01:10 +08:00
parent a817b05259
commit c269eafe36
9 changed files with 78 additions and 40 deletions

View File

@ -110,11 +110,15 @@ struct CResult_____c_char transfer_pools(uint8_t coin,
uint8_t from_pool,
uint8_t to_pool,
uint64_t amount,
bool fee_included,
char *memo,
uint64_t split_amount,
uint32_t confirmations);
struct CResult_____c_char shield_taddr(uint8_t coin, uint32_t account, uint32_t confirmations);
struct CResult_____c_char shield_taddr(uint8_t coin,
uint32_t account,
uint64_t amount,
uint32_t confirmations);
void scan_transparent_accounts(uint32_t gap_limit);

View File

@ -45,6 +45,7 @@ async fn save_contacts_tx(memos: &[Memo], anchor_offset: u32) -> anyhow::Result<
.map(|m| RecipientMemo {
address: address.clone(),
amount: 0,
fee_included: false,
memo: m.clone(),
max_amount_per_note: 0,
})

View File

@ -12,7 +12,6 @@ use std::path::Path;
use std::sync::Mutex;
use tokio::sync::Semaphore;
use zcash_primitives::transaction::builder::Progress;
use crate::api::payment_v2::AmountOrMax;
static mut POST_COBJ: Option<ffi::DartPostCObjectFnType> = None;
@ -424,6 +423,7 @@ pub async unsafe extern "C" fn transfer_pools(
account: u32,
from_pool: u8, to_pool: u8,
amount: u64,
fee_included: bool,
memo: *mut c_char,
split_amount: u64,
confirmations: u32,
@ -431,7 +431,7 @@ pub async unsafe extern "C" fn transfer_pools(
from_c_str!(memo);
let res = async move {
let tx_plan = crate::api::payment_v2::transfer_pools(coin, account, from_pool, to_pool,
AmountOrMax::Amount(amount),
amount, fee_included,
&memo, split_amount, confirmations).await?;
let tx_plan = serde_json::to_string(&tx_plan)?;
Ok::<_, anyhow::Error>(tx_plan)
@ -444,9 +444,10 @@ pub async unsafe extern "C" fn transfer_pools(
pub async unsafe extern "C" fn shield_taddr(
coin: u8,
account: u32,
amount: u64,
confirmations: u32,
) -> CResult<*mut c_char> {
let res = crate::api::payment_v2::shield_taddr(coin, account, confirmations).await;
let res = crate::api::payment_v2::shield_taddr(coin, account, amount, confirmations).await;
to_cresult_str(res)
}

View File

@ -28,6 +28,15 @@ pub async fn build_tx_plan(
) -> note_selection::Result<TransactionPlan> {
let c = CoinConfig::get(coin);
let network = c.chain.network();
let mut recipient_fee = false;
for r in recipients {
if r.fee_included {
if recipient_fee { return Err(TransactionBuilderError::DuplicateRecipientFee) }
recipient_fee = true;
}
}
let (fvk, checkpoint_height) = {
let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?;
@ -49,7 +58,7 @@ pub async fn build_tx_plan(
loop {
let a = min(amount, max_amount_per_note);
let memo_bytes: MemoBytes = r.memo.clone().into();
let order = Order::new(network, id_order, &r.address, a, memo_bytes);
let order = Order::new(network, id_order, &r.address, a, false, memo_bytes);
orders.push(order);
amount -= a;
id_order += 1;
@ -57,6 +66,7 @@ pub async fn build_tx_plan(
break;
} // at least one note even when amount = 0
}
orders.last_mut().unwrap().take_fee = r.fee_included;
}
let utxos = fetch_utxos(coin, account, checkpoint_height, excluded_flags).await?;
@ -144,36 +154,25 @@ pub async fn build_max_tx(
Err(TransactionBuilderError::TxTooComplex)
}
pub enum AmountOrMax {
Amount(u64),
Max
}
pub async fn transfer_pools(coin: u8, account: u32, from_pool: u8, to_pool: u8, amount: AmountOrMax,
memo: &str, split_amount: u64, confirmations: u32) -> anyhow::Result<TransactionPlan> {
pub async fn transfer_pools(coin: u8, account: u32, from_pool: u8, to_pool: u8, amount: u64,
fee_included: bool, memo: &str, split_amount: u64, confirmations: u32) -> anyhow::Result<TransactionPlan> {
let address = get_unified_address(coin, account, to_pool)?; // get our own unified address
let a = match amount {
AmountOrMax::Amount(a) => a,
AmountOrMax::Max => 0,
};
let recipient = RecipientMemo {
address,
amount: a,
amount,
fee_included,
memo: Memo::from_str(memo)?,
max_amount_per_note: split_amount,
};
let last_height = get_latest_height().await?;
let tx_plan = match amount {
AmountOrMax::Amount(_) => build_tx_plan(coin, account, last_height, slice::from_ref(&recipient),
!from_pool, confirmations).await?,
AmountOrMax::Max => build_max_tx(coin, account, last_height, &recipient, !from_pool, confirmations).await?,
};
let tx_plan = build_tx_plan(coin, account, last_height, slice::from_ref(&recipient),
!from_pool, confirmations).await?;
Ok(tx_plan)
}
/// Make a transaction that shields the transparent balance
pub async fn shield_taddr(coin: u8, account: u32, confirmations: u32) -> anyhow::Result<String> {
let tx_plan = transfer_pools(coin, account, 1, 6, AmountOrMax::Max, "Shield Transparent Balance", 0, confirmations).await?;
pub async fn shield_taddr(coin: u8, account: u32, amount: u64, confirmations: u32) -> anyhow::Result<String> {
let tx_plan = transfer_pools(coin, account, 1, 6, amount, true, "Shield Transparent Balance", 0, confirmations).await?;
let tx_id = sign_and_broadcast(coin, account, &tx_plan).await?;
log::info!("TXID: {}", tx_id);
Ok(tx_id)

View File

@ -8,6 +8,7 @@ use zcash_primitives::memo::Memo;
pub struct Recipient {
pub address: String,
pub amount: u64,
pub fee_included: bool,
pub reply_to: bool,
pub subject: String,
pub memo: String,
@ -24,6 +25,7 @@ pub struct RecipientShort {
pub struct RecipientMemo {
pub address: String,
pub amount: u64,
pub fee_included: bool,
pub memo: Memo,
pub max_amount_per_note: u64,
}
@ -38,6 +40,7 @@ impl RecipientMemo {
Ok(RecipientMemo {
address: r.address.clone(),
amount: r.amount,
fee_included: r.fee_included,
memo: Memo::from_str(&memo)?,
max_amount_per_note: r.max_amount_per_note,
})
@ -49,6 +52,7 @@ impl From<RecipientShort> for RecipientMemo {
RecipientMemo {
address: r.address,
amount: r.amount,
fee_included: false,
memo: Memo::Empty,
max_amount_per_note: 0,
}

View File

@ -1,10 +1,7 @@
use crate::coinconfig::CoinConfig;
use anyhow::anyhow;
use bip39::{Language, Mnemonic, Seed};
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, decode_extended_spending_key,
encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
};
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address, decode_transparent_address, encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address};
use zcash_client_backend::keys::UnifiedFullViewingKey;
use zcash_primitives::consensus::{Network, Parameters};
use zcash_primitives::zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey};
@ -80,8 +77,21 @@ pub fn is_valid_key(coin: u8, key: &str) -> i8 {
pub fn is_valid_address(coin: u8, address: &str) -> bool {
let c = CoinConfig::get(coin);
let network = c.chain.network();
let recipient = zcash_client_backend::address::RecipientAddress::decode(network, address);
recipient.is_some()
if decode_payment_address(network.hrp_sapling_payment_address(), address).is_ok() {
true
}
else if let Ok(Some(_)) = decode_transparent_address(
&network.b58_pubkey_address_prefix(),
&network.b58_script_address_prefix(),
address) {
true
}
else if zcash_client_backend::address::RecipientAddress::decode(network, address).is_some() {
true
}
else {
false
}
}
fn derive_secret_key(

View File

@ -19,6 +19,10 @@ use zcash_primitives::memo::Memo;
pub enum TransactionBuilderError {
#[error("Not enough funds: Missing {0} zats")]
NotEnoughFunds(u64),
#[error("Only one recipient can pay for the fees")]
DuplicateRecipientFee,
#[error("Recipient amount too low for the fees")]
RecipientCannotPayFee,
#[error("Tx too complex")]
TxTooComplex,
#[error(transparent)]
@ -47,7 +51,8 @@ pub fn recipients_to_orders(network: &Network, recipients: &[Recipient]) -> Resu
Ok::<_, TransactionBuilderError>(Order {
id: i as u32,
destinations,
amount: r.amount,
raw_amount: r.amount,
take_fee: r.fee_included, // Caller must make sure that at most one recipient pays for the fees
memo: Memo::from_str(&r.memo).unwrap().into(),
})
})

View File

@ -34,9 +34,10 @@ pub fn group_orders(orders: &[Order], fee: u64) -> Result<(Vec<OrderInfo>, Order
group_type |= 1 << i;
}
}
let amount = order.amount(fee)?;
order_info.push(OrderInfo {
group_type,
amount: order.amount,
amount,
});
}
@ -189,6 +190,7 @@ pub fn fill(
order_infos: &[OrderInfo],
amounts: &OrderGroupAmounts,
allocation: &FundAllocation,
fee: u64
) -> Result<Vec<Fill>> {
assert_eq!(orders.len(), order_infos.len());
let mut fills = vec![];
@ -202,7 +204,7 @@ pub fn fill(
let fill = Fill {
id_order: Some(order.id),
destination: order.destinations[ilog2(info.group_type)].unwrap(),
amount: order.amount,
amount: order.amount(fee)?,
memo: order.memo.clone(),
};
fills.push(fill);
@ -211,13 +213,13 @@ pub fn fill(
let fill1 = Fill {
id_order: Some(order.id),
destination: order.destinations[1].unwrap(),
amount: (order.amount as f64 * f).round() as u64,
amount: (order.amount(fee)? as f64 * f).round() as u64,
memo: order.memo.clone(),
};
let fill2 = Fill {
id_order: Some(order.id),
destination: order.destinations[2].unwrap(),
amount: order.amount - fill1.amount,
amount: order.amount(fee)? - fill1.amount,
memo: order.memo.clone(),
};
if fill1.amount != 0 {
@ -310,7 +312,7 @@ pub fn build_tx_plan<F: FeeCalculator>(
);
let net_chg = [s0 + s1 - s2, o0 + o1 - o2];
let mut fills = fill(&orders, &groups, &amounts, &allocation)?;
let mut fills = fill(&orders, &groups, &amounts, &allocation, fee)?;
let (notes, change) = select_inputs(&utxos, &allocation)?;
let change_destinations = decode(network, &config.change_address)?;

View File

@ -1,7 +1,7 @@
use super::ser::MemoBytesProxy;
use crate::note_selection::ua::decode;
use crate::unified::orchard_as_unified;
use crate::Hash;
use crate::{Hash, TransactionBuilderError};
use orchard::Address;
use serde::{Deserialize, Serialize};
use serde_hex::{SerHex, Strict};
@ -103,7 +103,8 @@ pub struct UTXO {
pub struct Order {
pub id: u32,
pub destinations: [Option<Destination>; 3],
pub amount: u64,
pub raw_amount: u64,
pub take_fee: bool,
#[serde(with = "MemoBytesProxy")]
pub memo: MemoBytes,
}
@ -200,13 +201,24 @@ impl Destination {
}
impl Order {
pub fn new(network: &Network, id: u32, address: &str, amount: u64, memo: MemoBytes) -> Self {
pub fn new(network: &Network, id: u32, address: &str, amount: u64, take_fee: bool, memo: MemoBytes) -> Self {
let destinations = decode(network, address).unwrap();
Order {
id,
destinations,
amount,
raw_amount: amount,
take_fee,
memo,
}
}
pub fn amount(&self, fee: u64) -> Result<u64, TransactionBuilderError> {
if self.take_fee {
if self.raw_amount < fee { return Err(TransactionBuilderError::RecipientCannotPayFee) }
Ok(self.raw_amount - fee)
}
else {
Ok(self.raw_amount)
}
}
}