Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
835fdc0
Adding a shielding fee for normal shielding txs. Still need to check IBC
batconjurer Jul 8, 2025
5b27b61
Fixing tests
batconjurer Jul 10, 2025
5187a62
Formatting
batconjurer Jul 11, 2025
9ef5631
Add changelog
batconjurer Jul 14, 2025
3ec74e1
Added shielding fee to an e2e test
batconjurer Jul 14, 2025
cd35e30
Fixing e2e tests
batconjurer Jul 14, 2025
61f34b7
Update crates/tests/src/e2e/ledger_tests.rs
batconjurer Jul 14, 2025
521ae10
Fixed benches
batconjurer Jul 15, 2025
929d320
Fixing pr comments
batconjurer Jul 15, 2025
cae58a6
Added fee shielding checks to masp vp
batconjurer Jul 15, 2025
8fdc6df
typo
batconjurer Jul 15, 2025
249b89c
Strengthened vp checks and add some test cases
batconjurer Jul 16, 2025
ff00f67
Fixing masp vp
batconjurer Jul 17, 2025
20346b9
Added new flow for shielding over ibc
batconjurer Jul 24, 2025
6754f06
Fix bug in finding authorizers masp vp must check from the actions
batconjurer Jul 25, 2025
41cda8f
Formatting
batconjurer Jul 25, 2025
6e71d14
Merge branch 'main' into bat/add-shielding-fee-section
batconjurer Jul 25, 2025
ba86de7
Formatting
batconjurer Jul 25, 2025
e331c61
Fixed an import mistake
batconjurer Jul 28, 2025
6b49a2d
Fixed test vp env
batconjurer Jul 28, 2025
528aaaf
Added balance check to shielding fee payer when call gen_ibc_transfer
batconjurer Jul 28, 2025
05f285e
Forgot a goddamn await ofc
batconjurer Jul 28, 2025
f39cea7
Formatting
batconjurer Jul 28, 2025
fa08fed
Some light renaming in anticipation of unshielding fees
batconjurer Jul 28, 2025
3bd65a3
Fixed a silly assertion error in integration test
batconjurer Jul 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Adds a shielding fee section to txs. This forces a fee to paid when shielding
to the masp ([\#4276](https://github.com/anoma/namada/issues/4276))
2 changes: 2 additions & 0 deletions Cargo.lock

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

77 changes: 75 additions & 2 deletions crates/apps_lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ pub mod cmds {
}

#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum NamadaClientWithContext {
// Ledger cmds
TxCustom(TxCustom),
Expand Down Expand Up @@ -3674,6 +3675,14 @@ pub mod args {
arg("self-bond-amount");
pub const SENDER: Arg<String> = arg("sender");
pub const SHIELDED: ArgFlag = flag("shielded");
pub const SHIELDING_FEE_PAYER: Arg<WalletPublicKey> =
arg("shielding-fee-payer");
pub const SHIELDING_FEE_PAYER_OPT: ArgOpt<WalletPublicKey> =
arg_opt("shielding-fee-payer");
pub const SHIELDING_FEE_TOKEN: Arg<WalletAddress> =
arg("shielding-fee-token");
pub const SHIELDING_FEE_TOKEN_OPT: ArgOpt<WalletAddress> =
arg_opt("shielding-fee-token");
pub const SHOW_IBC_TOKENS: ArgFlag = flag("show-ibc-tokens");
pub const SLIPPAGE: ArgOpt<Dec> = arg_opt("slippage-percentage");
pub const SIGNING_KEYS: ArgMulti<WalletPublicKey, GlobStar> =
Expand Down Expand Up @@ -4947,12 +4956,15 @@ pub mod args {
amount: transfer_data.amount,
});
}
let shielding_fee_payer = chain_ctx.get(&self.shielding_fee_payer);

Ok(TxShieldingTransfer::<SdkTypes> {
tx,
sources: data,
targets,
tx_code_path: self.tx_code_path.to_path_buf(),
shielding_fee_payer,
shielding_fee_token: chain_ctx.get(&self.shielding_fee_token),
})
}
}
Expand All @@ -4965,6 +4977,8 @@ pub mod args {
let token = TOKEN.parse(matches);
let amount = InputAmount::Unvalidated(AMOUNT.parse(matches));
let tx_code_path = PathBuf::from(TX_TRANSFER_WASM);
let shielding_fee_payer = SHIELDING_FEE_PAYER.parse(matches);
let shielding_fee_token = SHIELDING_FEE_TOKEN.parse(matches);
let data = vec![TxTransparentSource {
source,
token: token.clone(),
Expand All @@ -4981,6 +4995,8 @@ pub mod args {
sources: data,
targets,
tx_code_path,
shielding_fee_payer,
shielding_fee_token,
}
}

Expand All @@ -5001,6 +5017,13 @@ pub mod args {
.def()
.help(wrap!("The amount to transfer in decimal.")),
)
.arg(SHIELDING_FEE_PAYER.def().help(wrap!(
"The implicit account which will pay the fee for \
shielding tokens"
)))
.arg(SHIELDING_FEE_TOKEN.def().help(wrap!(
"The token which will be used to pay the shielding fee"
)))
}
}

Expand Down Expand Up @@ -5231,7 +5254,16 @@ pub mod args {
let chain_ctx = ctx.borrow_mut_chain_or_exit();
let recipient = match self.recipient {
Either::Left(r) => Either::Left(chain_ctx.get(&r)),
Either::Right(r) => Either::Right(chain_ctx.get(&r)),
Either::Right(r) => {
let shielded_recipient = ShieldedSwapRecipient {
recipient: chain_ctx.get(&r.recipient),
shielding_fee_payer: chain_ctx
.get(&r.shielding_fee_payer),
shielding_fee_token: chain_ctx
.get(&r.shielding_fee_token),
};
Either::Right(shielded_recipient)
}
};
let overflow = self.overflow.map(|r| chain_ctx.get(&r));
Ok(TxOsmosisSwap {
Expand All @@ -5257,6 +5289,10 @@ pub mod args {
let maybe_trans_recipient = TARGET_OPT.parse(matches);
let maybe_shielded_recipient =
PAYMENT_ADDRESS_TARGET_OPT.parse(matches);
let maybe_shielding_fee_payer =
SHIELDING_FEE_PAYER_OPT.parse(matches);
let maybe_shielding_fee_token =
SHIELDING_FEE_TOKEN_OPT.parse(matches);
let maybe_overflow = OVERFLOW_OPT.parse(matches);
let slippage_percent = SLIPPAGE.parse(matches);
if slippage_percent.is_some_and(|percent| {
Expand Down Expand Up @@ -5288,7 +5324,11 @@ pub mod args {
recipient: if let Some(target) = maybe_trans_recipient {
Either::Left(target)
} else {
Either::Right(maybe_shielded_recipient.unwrap())
Either::Right(ShieldedSwapRecipient {
recipient: maybe_shielded_recipient.unwrap(),
shielding_fee_payer: maybe_shielding_fee_payer.unwrap(),
shielding_fee_token: maybe_shielding_fee_token.unwrap(),
})
},
overflow: maybe_overflow,
slippage,
Expand Down Expand Up @@ -5339,12 +5379,31 @@ pub mod args {
.arg(
PAYMENT_ADDRESS_TARGET_OPT
.def()
.requires(SHIELDING_FEE_PAYER_OPT.name)
.requires(SHIELDING_FEE_TOKEN_OPT.name)
.conflicts_with(TARGET_OPT.name)
.help(wrap!(
"Namada payment address that shall receive the \
minimum amount of tokens swapped on Osmosis."
)),
)
.arg(
SHIELDING_FEE_PAYER_OPT
.def()
.conflicts_with(TARGET_OPT.name)
.help(wrap!(
"Namada address that will pay the shielding fee."
)),
)
.arg(
SHIELDING_FEE_TOKEN_OPT
.def()
.conflicts_with(TARGET_OPT.name)
.help(wrap!(
"Namada token address that the shielding fee will \
be paid in."
)),
)
.arg(OVERFLOW_OPT.def().help(wrap!(
"Transparent address that receives the amount of target \
asset exceeding the minimum trade amount. Only \
Expand Down Expand Up @@ -7231,6 +7290,8 @@ pub mod args {
IbcShieldingTransferAsset::Address(chain_ctx.get(&addr))
}
},
shielding_fee_payer: chain_ctx.get(&self.shielding_fee_payer),
shielding_fee_token: chain_ctx.get(&self.shielding_fee_token),
})
}
}
Expand All @@ -7254,6 +7315,8 @@ pub mod args {
None => TxExpiration::Default,
}
};
let shielding_fee_payer = SHIELDING_FEE_PAYER.parse(matches);
let shielding_fee_token = SHIELDING_FEE_TOKEN.parse(matches);

Self {
query,
Expand All @@ -7266,6 +7329,8 @@ pub mod args {
channel_id,
token,
},
shielding_fee_payer,
shielding_fee_token,
}
}

Expand Down Expand Up @@ -7307,6 +7372,14 @@ pub mod args {
.arg(CHANNEL_ID.def().help(wrap!(
"The channel ID via which the token is received."
)))
.arg(SHIELDING_FEE_PAYER.def().help(wrap!(
"The implicit account paying the fee for shielding tokens"
)))
.arg(
SHIELDING_FEE_TOKEN
.def()
.help(wrap!("The token used to pay the shielding fee")),
)
}
}

Expand Down
16 changes: 16 additions & 0 deletions crates/apps_lib/src/client/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,15 @@ pub async fn query_protocol_parameters(
query_storage_value(context.client(), &key)
.await
.expect("Parameter should be defined.");
let native_token = rpc::query_native_token(context.client())
.await
.expect("Native token should be defined");
let key = param_storage::masp_shielding_fee_amount(&native_token);
let masp_nam_shielding_fee =
query_storage_value::<_, DenominatedAmount>(context.client(), &key)
.await
.expect("Parameter should be defined.")
.amount();
let key = param_storage::get_gas_cost_key();
let minimum_gas_price: BTreeMap<Address, token::Amount> =
query_storage_value(context.client(), &key)
Expand Down Expand Up @@ -884,6 +893,7 @@ pub async fn query_protocol_parameters(
epochs_per_year,
masp_epoch_multiplier,
masp_fee_payment_gas_limit,
masp_nam_shielding_fee,
gas_scale,
minimum_gas_price,
is_native_token_transferable,
Expand All @@ -900,6 +910,7 @@ pub async fn query_protocol_parameters(
epochs_per_year,
masp_epoch_multiplier,
masp_fee_payment_gas_limit,
masp_nam_shielding_fee,
gas_scale,
minimum_gas_price,
is_native_token_transferable,
Expand Down Expand Up @@ -956,6 +967,11 @@ pub async fn query_protocol_parameters(
"",
masp_fee_payment_gas_limit
);
display_line!(
context.io(),
"{:4}Masp shielding fee payment (NAM)",
masp_nam_shielding_fee
);
display_line!(context.io(), "{:4}Minimum gas costs:", "");
for (token, gas_cost) in minimum_gas_price {
let denom = rpc::query_denom(context.client(), &token)
Expand Down
19 changes: 16 additions & 3 deletions crates/apps_lib/src/client/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ use namada_sdk::collections::HashMap;
use namada_sdk::governance::cli::onchain::{
DefaultProposal, PgfFundingProposal, PgfStewardProposal,
};
use namada_sdk::ibc::convert_masp_tx_to_ibc_memo;
use namada_sdk::io::{Io, display_line, edisplay_line};
use namada_sdk::key::*;
use namada_sdk::rpc::{InnerTxResult, TxBroadcastData, TxResponse};
use namada_sdk::state::EPOCH_SWITCH_BLOCKS_DELAY;
use namada_sdk::tx::data::compute_inner_tx_hash;
use namada_sdk::tx::{CompressedAuthorization, Section, Signer, Tx};
use namada_sdk::tx::{
CompressedAuthorization, Section, Signer, Tx,
convert_masp_tx_to_ibc_memo_data,
};
use namada_sdk::wallet::alias::{validator_address, validator_consensus_key};
use namada_sdk::wallet::{Wallet, WalletIo};
use namada_sdk::{ExtendedViewingKey, Namada, error, signing, tx};
Expand Down Expand Up @@ -79,6 +81,7 @@ pub async fn aux_signing_data(
owner,
default_signer,
vec![],
None,
disposable_signing_key,
signatures,
wrapper_signature,
Expand Down Expand Up @@ -2011,6 +2014,8 @@ pub async fn gen_ibc_shielding_transfer(
) -> Result<(), error::Error> {
let output_folder = args.output_folder.clone();

let shielding_fee_payer = args.shielding_fee_payer.clone();
let shielding_fee_token = args.shielding_fee_token.clone();
if let Some(masp_tx) = tx::gen_ibc_shielding_transfer(context, args).await?
{
let tx_id = masp_tx.txid().to_string();
Expand All @@ -2021,7 +2026,15 @@ pub async fn gen_ibc_shielding_transfer(
};
let mut out = File::create(&output_path)
.expect("Creating a new file for IBC MASP transaction failed.");
let bytes = convert_masp_tx_to_ibc_memo(&masp_tx);
let bytes = String::from(
convert_masp_tx_to_ibc_memo_data(
context,
&masp_tx,
shielding_fee_payer,
shielding_fee_token,
)
.await?,
);
out.write_all(bytes.as_bytes())
.expect("Writing IBC MASP transaction file failed.");
println!(
Expand Down
2 changes: 2 additions & 0 deletions crates/apps_lib/src/config/genesis/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ impl Finalized {
epochs_per_year,
masp_epoch_multiplier,
masp_fee_payment_gas_limit,
masp_nam_shielding_fee,
gas_scale,
max_block_gas,
minimum_gas_price,
Expand Down Expand Up @@ -373,6 +374,7 @@ impl Finalized {
masp_epoch_multiplier,
max_proposal_bytes,
masp_fee_payment_gas_limit,
masp_nam_shielding_fee: masp_nam_shielding_fee.amount(),
gas_scale,
max_block_gas,
minimum_gas_price: minimum_gas_price
Expand Down
14 changes: 14 additions & 0 deletions crates/apps_lib/src/config/genesis/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ pub struct ChainParams<T: TemplateValidation> {
pub max_block_gas: u64,
/// Gas limit of a masp transaction paying fees
pub masp_fee_payment_gas_limit: u64,
/// The amount of NAM the MASP shielding fee costs
pub masp_nam_shielding_fee: DenominatedAmount,
/// Gas scale
pub gas_scale: u64,
/// Map of the cost per gas unit for every token allowed for fee payment
Expand All @@ -314,6 +316,7 @@ impl ChainParams<Unvalidated> {
masp_epoch_multiplier,
max_block_gas,
masp_fee_payment_gas_limit,
masp_nam_shielding_fee,
gas_scale,
minimum_gas_price,
} = self;
Expand Down Expand Up @@ -345,6 +348,16 @@ impl ChainParams<Unvalidated> {
})?;
min_gas_prices.insert(token, amount);
}
let masp_nam_shielding_fee = masp_nam_shielding_fee
.increase_precision(NATIVE_MAX_DECIMAL_PLACES.into())
.map_err(|e| {
eprintln!(
"A MASP shielding fee (in NAM) in the parameters.toml \
file was incorrectly denominated:\n{}",
e
);
e
})?;

Ok(ChainParams {
max_tx_bytes,
Expand All @@ -359,6 +372,7 @@ impl ChainParams<Unvalidated> {
masp_epoch_multiplier,
max_block_gas,
masp_fee_payment_gas_limit,
masp_nam_shielding_fee,
gas_scale,
minimum_gas_price: min_gas_prices,
})
Expand Down
1 change: 1 addition & 0 deletions crates/apps_lib/src/config/genesis/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ impl<T> Signed<T> {
public_keys: pks.clone(),
threshold,
fee_payer: Either::Left((genesis_fee_payer_pk(), false)),
masp_sus_fee_payer: None,
shielded_hash: None,
signatures: vec![],
};
Expand Down
13 changes: 13 additions & 0 deletions crates/core/src/masp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use sha2::Sha256;
use crate::address::{Address, DecodeError, HASH_HEX_LEN, IBC, MASP};
use crate::borsh::BorshSerializeExt;
use crate::chain::Epoch;
use crate::hash::Hash;
use crate::impl_display_and_from_str_via_format;
use crate::string_encoding::{
self, MASP_EXT_FULL_VIEWING_KEY_HRP, MASP_EXT_SPENDING_KEY_HRP,
Expand Down Expand Up @@ -79,6 +80,18 @@ impl From<TxIdInner> for MaspTxId {
}
}

impl From<Hash> for MaspTxId {
fn from(hash: Hash) -> Self {
MaspTxId(TxIdInner::from_bytes(hash.0))
}
}

impl From<MaspTxId> for Hash {
fn from(tx_id: MaspTxId) -> Self {
Hash(*tx_id.0.as_ref())
}
}

impl Display for MaspTxId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
Expand Down
Loading
Loading