Skip to content

Commit 9e19e60

Browse files
committed
Selectively charge for MASP IBC shielded actions
1 parent d753102 commit 9e19e60

File tree

1 file changed

+179
-41
lines changed

1 file changed

+179
-41
lines changed

crates/ibc/src/context/token_transfer.rs

Lines changed: 179 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use ibc::apps::transfer::types::{Memo, PrefixedCoin, PrefixedDenom};
1313
use ibc::core::host::types::error::HostError;
1414
use ibc::core::host::types::identifiers::{ChannelId, PortId};
1515
use ibc::core::primitives::Signer;
16-
use namada_core::address::{Address, InternalAddress, MASP};
16+
use namada_core::address::{Address, InternalAddress, MASP, PGF};
1717
use namada_core::arith::{CheckedAdd, checked};
1818
use namada_core::masp::{AssetData, CompactNote, PaymentAddress};
1919
use namada_core::token::{Amount, MaspDigitPos};
@@ -56,7 +56,7 @@ where
5656
}
5757

5858
/// Insert a verifier address whose VP will verify the tx.
59-
pub(crate) fn insert_verifier(&mut self, addr: &Address) {
59+
pub(crate) fn insert_verifier(&self, addr: &Address) {
6060
self.verifiers.borrow_mut().insert(addr.clone());
6161
}
6262

@@ -184,40 +184,134 @@ where
184184
)
185185
}
186186

187-
fn validate_masp_withdraw(
188-
&self,
189-
from_account: &IbcAccountId,
190-
) -> Result<(), HostError> {
191-
if from_account.is_shielded() && !self.has_masp_tx {
192-
return Err(HostError::Other {
193-
description: format!(
194-
"Set refund address {from_account} without including an \
195-
IBC unshielding MASP transaction"
196-
),
197-
});
198-
}
199-
Ok(())
200-
}
201-
202187
#[inline]
203-
fn maybe_store_masp_note_commitments(
188+
fn maybe_handle_masp_memoless_shielding<F>(
204189
&self,
205190
to_account: &IbcAccountId,
206191
token: &Address,
207192
amount: &Amount,
208-
) -> Result<(), HostError>
193+
transfer: F,
194+
) -> Result<Amount, HostError>
209195
where
196+
F: FnOnce(Amount) -> Result<(), HostError>,
210197
Params:
211198
namada_systems::parameters::Read<<C as IbcStorageContext>::Storage>,
212199
Token: namada_systems::trans_token::Read<<C as IbcStorageContext>::Storage>,
213200
ShieldedToken: namada_systems::shielded_token::Write<
214201
<C as IbcStorageContext>::Storage,
215202
>,
216203
{
204+
let mut amount = *amount;
205+
217206
if let IbcAccountId::Shielded(owner_pa) = to_account {
218-
self.store_masp_note_commitments(owner_pa, token, amount)?;
207+
if let Some(fee) = self.get_masp_shielding_fee(token, &amount)? {
208+
amount = amount.checked_sub(fee).ok_or_else(|| {
209+
HostError::Other {
210+
description: "Shielding fee greater than deposited \
211+
amount"
212+
.to_string(),
213+
}
214+
})?;
215+
216+
transfer(fee)?;
217+
}
218+
219+
self.store_masp_note_commitments(owner_pa, token, &amount)?;
219220
}
220-
Ok(())
221+
222+
Ok(amount)
223+
}
224+
225+
#[inline]
226+
fn maybe_handle_masp_unshielding<F>(
227+
&self,
228+
from_account: &IbcAccountId,
229+
token: &Address,
230+
amount: &Amount,
231+
transfer: F,
232+
) -> Result<Amount, HostError>
233+
where
234+
F: FnOnce(Amount) -> Result<(), HostError>,
235+
Params:
236+
namada_systems::parameters::Read<<C as IbcStorageContext>::Storage>,
237+
{
238+
let mut amount = *amount;
239+
240+
if !self.has_masp_tx {
241+
return if from_account.is_transparent() {
242+
Ok(amount)
243+
} else {
244+
Err(HostError::Other {
245+
description: format!(
246+
"Set refund address {from_account} without including \
247+
an IBC unshielding MASP transaction"
248+
),
249+
})
250+
};
251+
}
252+
253+
if let Some(fee) = self.get_masp_unshielding_fee(token, &amount)? {
254+
amount =
255+
amount.checked_sub(fee).ok_or_else(|| HostError::Other {
256+
description: "Unshielding fee greater than withdrawn \
257+
amount"
258+
.to_string(),
259+
})?;
260+
261+
transfer(fee)?;
262+
}
263+
264+
Ok(amount)
265+
}
266+
267+
fn get_masp_shielding_fee(
268+
&self,
269+
token: &Address,
270+
amount: &Amount,
271+
) -> Result<Option<Amount>, HostError>
272+
where
273+
Params:
274+
namada_systems::parameters::Read<<C as IbcStorageContext>::Storage>,
275+
{
276+
let Some(fee_percentage) = Params::ibc_shielding_fee_percentage(
277+
self.inner.borrow().storage(),
278+
token,
279+
)?
280+
else {
281+
return Ok(None);
282+
};
283+
284+
Ok(Some(amount.checked_mul_dec(fee_percentage).ok_or_else(
285+
|| HostError::Other {
286+
description:
287+
"Overflow in MASP shielding fee computation".to_string(),
288+
},
289+
)?))
290+
}
291+
292+
fn get_masp_unshielding_fee(
293+
&self,
294+
token: &Address,
295+
amount: &Amount,
296+
) -> Result<Option<Amount>, HostError>
297+
where
298+
Params:
299+
namada_systems::parameters::Read<<C as IbcStorageContext>::Storage>,
300+
{
301+
let Some(fee_percentage) = Params::ibc_unshielding_fee_percentage(
302+
self.inner.borrow().storage(),
303+
token,
304+
)?
305+
else {
306+
return Ok(None);
307+
};
308+
309+
Ok(Some(amount.checked_mul_dec(fee_percentage).ok_or_else(
310+
|| HostError::Other {
311+
description:
312+
"Overflow in MASP unshielding fee computation".to_string(),
313+
},
314+
)?))
221315
}
222316

223317
fn store_masp_note_commitments(
@@ -512,7 +606,24 @@ where
512606
) -> Result<(), HostError> {
513607
let (ibc_token, amount) = self.get_token_amount(coin)?;
514608

515-
self.validate_masp_withdraw(from_account)?;
609+
let from_trans_account = if self.has_masp_tx {
610+
Cow::Owned(MASP)
611+
} else {
612+
from_account.to_address()
613+
};
614+
let amount = self.maybe_handle_masp_unshielding(
615+
from_account,
616+
&ibc_token,
617+
&amount,
618+
|fee| {
619+
self.insert_verifier(&PGF);
620+
self.inner
621+
.borrow_mut()
622+
.transfer_token(&from_trans_account, &PGF, &ibc_token, fee)
623+
.map_err(HostError::from)
624+
},
625+
)?;
626+
516627
self.increment_per_epoch_withdraw_limits(&ibc_token, amount)?;
517628

518629
// A transfer of NUT tokens must be verified by their VP
@@ -522,16 +633,10 @@ where
522633
self.insert_verifier(&ibc_token);
523634
}
524635

525-
let from_account = if self.has_masp_tx {
526-
Cow::Owned(MASP)
527-
} else {
528-
from_account.to_address()
529-
};
530-
531636
self.inner
532637
.borrow_mut()
533638
.transfer_token(
534-
&from_account,
639+
&from_trans_account,
535640
&IBC_ESCROW_ADDRESS,
536641
&ibc_token,
537642
amount,
@@ -548,11 +653,21 @@ where
548653
) -> Result<(), HostError> {
549654
let (ibc_token, amount) = self.get_token_amount(coin)?;
550655

551-
self.increment_per_epoch_deposit_limits(&ibc_token, amount)?;
552-
self.maybe_store_masp_note_commitments(
553-
to_account, &ibc_token, &amount,
656+
let amount = self.maybe_handle_masp_memoless_shielding(
657+
to_account,
658+
&ibc_token,
659+
&amount,
660+
|fee| {
661+
self.insert_verifier(&PGF);
662+
self.inner
663+
.borrow_mut()
664+
.transfer_token(&IBC_ESCROW_ADDRESS, &PGF, &ibc_token, fee)
665+
.map_err(HostError::from)
666+
},
554667
)?;
555668

669+
self.increment_per_epoch_deposit_limits(&ibc_token, amount)?;
670+
556671
self.inner
557672
.borrow_mut()
558673
.transfer_token(
@@ -572,9 +687,21 @@ where
572687
// The trace path of the denom is already updated if receiving the token
573688
let (ibc_token, amount) = self.get_token_amount(coin)?;
574689

690+
let amount = self.maybe_handle_masp_memoless_shielding(
691+
account,
692+
&ibc_token,
693+
&amount,
694+
|fee| {
695+
self.insert_verifier(&PGF);
696+
self.inner
697+
.borrow_mut()
698+
.mint_token(&PGF, &ibc_token, fee)
699+
.map_err(HostError::from)
700+
},
701+
)?;
702+
575703
self.update_mint_amount(&ibc_token, amount, true)?;
576704
self.increment_per_epoch_deposit_limits(&ibc_token, amount)?;
577-
self.maybe_store_masp_note_commitments(account, &ibc_token, &amount)?;
578705

579706
// A transfer of NUT tokens must be verified by their VP
580707
if ibc_token.is_internal()
@@ -603,7 +730,24 @@ where
603730
) -> Result<(), HostError> {
604731
let (ibc_token, amount) = self.get_token_amount(coin)?;
605732

606-
self.validate_masp_withdraw(account)?;
733+
let trans_account = if self.has_masp_tx {
734+
Cow::Owned(MASP)
735+
} else {
736+
account.to_address()
737+
};
738+
let amount = self.maybe_handle_masp_unshielding(
739+
account,
740+
&ibc_token,
741+
&amount,
742+
|fee| {
743+
self.insert_verifier(&PGF);
744+
self.inner
745+
.borrow_mut()
746+
.transfer_token(&trans_account, &PGF, &ibc_token, fee)
747+
.map_err(HostError::from)
748+
},
749+
)?;
750+
607751
self.update_mint_amount(&ibc_token, amount, false)?;
608752
self.increment_per_epoch_withdraw_limits(&ibc_token, amount)?;
609753

@@ -614,16 +758,10 @@ where
614758
self.insert_verifier(&ibc_token);
615759
}
616760

617-
let account = if self.has_masp_tx {
618-
Cow::Owned(MASP)
619-
} else {
620-
account.to_address()
621-
};
622-
623761
// The burn is "unminting" from the minted balance
624762
self.inner
625763
.borrow_mut()
626-
.burn_token(&account, &ibc_token, amount)
764+
.burn_token(&trans_account, &ibc_token, amount)
627765
.map_err(HostError::from)
628766
}
629767
}

0 commit comments

Comments
 (0)