Skip to content

Commit b1df77c

Browse files
committed
Selectively charge for MASP IBC shielded actions
1 parent 8489c60 commit b1df77c

File tree

1 file changed

+183
-41
lines changed

1 file changed

+183
-41
lines changed

crates/ibc/src/context/token_transfer.rs

Lines changed: 183 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};
@@ -58,7 +58,7 @@ where
5858
}
5959

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

@@ -194,40 +194,138 @@ where
194194
)
195195
}
196196

197-
fn validate_masp_withdraw(
198-
&self,
199-
from_account: &IbcAccountId,
200-
) -> Result<(), HostError> {
201-
if from_account.is_shielded() && !self.has_masp_tx {
202-
return Err(HostError::Other {
203-
description: format!(
204-
"Set refund address {from_account} without including an \
205-
IBC unshielding MASP transaction"
206-
),
207-
});
208-
}
209-
Ok(())
210-
}
211-
212197
#[inline]
213-
fn maybe_store_masp_note_commitments(
198+
fn maybe_handle_masp_memoless_shielding<F>(
214199
&self,
215200
to_account: &IbcAccountId,
216201
token: &Address,
217202
amount: &Amount,
218-
) -> Result<(), HostError>
203+
transfer: F,
204+
) -> Result<Amount, HostError>
219205
where
206+
F: FnOnce(Amount) -> Result<(), HostError>,
220207
Params:
221208
namada_systems::parameters::Read<<C as IbcStorageContext>::Storage>,
222209
Token: namada_systems::trans_token::Read<<C as IbcStorageContext>::Storage>,
223210
ShieldedToken: namada_systems::shielded_token::Write<
224211
<C as IbcStorageContext>::Storage,
225212
>,
226213
{
214+
let mut amount = *amount;
215+
227216
if let IbcAccountId::Shielded(owner_pa) = to_account {
228-
self.store_masp_note_commitments(owner_pa, token, amount)?;
217+
if let Some(fee) = self.get_masp_shielding_fee(token, &amount)? {
218+
amount = amount.checked_sub(fee).ok_or_else(|| {
219+
HostError::Other {
220+
description: "Shielding fee greater than deposited \
221+
amount"
222+
.to_string(),
223+
}
224+
})?;
225+
226+
transfer(fee)?;
227+
}
228+
229+
self.store_masp_note_commitments(owner_pa, token, &amount)?;
229230
}
230-
Ok(())
231+
232+
Ok(amount)
233+
}
234+
235+
#[inline]
236+
fn maybe_handle_masp_unshielding<F>(
237+
&self,
238+
from_account: &IbcAccountId,
239+
token: &Address,
240+
amount: &Amount,
241+
transfer: F,
242+
) -> Result<Amount, HostError>
243+
where
244+
F: FnOnce(Amount) -> Result<(), HostError>,
245+
Params:
246+
namada_systems::parameters::Read<<C as IbcStorageContext>::Storage>,
247+
{
248+
let mut amount = *amount;
249+
250+
if !self.has_masp_tx {
251+
return if from_account.is_transparent() {
252+
Ok(amount)
253+
} else {
254+
Err(HostError::Other {
255+
description: format!(
256+
"Set refund address {from_account} without including \
257+
an IBC unshielding MASP transaction"
258+
),
259+
})
260+
};
261+
}
262+
263+
if let Some(fee) = self.get_masp_unshielding_fee(token, &amount)? {
264+
amount =
265+
amount.checked_sub(fee).ok_or_else(|| HostError::Other {
266+
description: "Unshielding fee greater than withdrawn \
267+
amount"
268+
.to_string(),
269+
})?;
270+
271+
transfer(fee)?;
272+
}
273+
274+
Ok(amount)
275+
}
276+
277+
fn get_masp_shielding_fee(
278+
&self,
279+
token: &Address,
280+
amount: &Amount,
281+
) -> Result<Option<Amount>, HostError>
282+
where
283+
Params:
284+
namada_systems::parameters::Read<<C as IbcStorageContext>::Storage>,
285+
{
286+
if self.is_refund {
287+
return Ok(None);
288+
}
289+
290+
let Some(fee_percentage) = Params::ibc_shielding_fee_percentage(
291+
self.inner.borrow().storage(),
292+
token,
293+
)?
294+
else {
295+
return Ok(None);
296+
};
297+
298+
Ok(Some(amount.checked_mul_dec(fee_percentage).ok_or_else(
299+
|| HostError::Other {
300+
description:
301+
"Overflow in MASP shielding fee computation".to_string(),
302+
},
303+
)?))
304+
}
305+
306+
fn get_masp_unshielding_fee(
307+
&self,
308+
token: &Address,
309+
amount: &Amount,
310+
) -> Result<Option<Amount>, HostError>
311+
where
312+
Params:
313+
namada_systems::parameters::Read<<C as IbcStorageContext>::Storage>,
314+
{
315+
let Some(fee_percentage) = Params::ibc_unshielding_fee_percentage(
316+
self.inner.borrow().storage(),
317+
token,
318+
)?
319+
else {
320+
return Ok(None);
321+
};
322+
323+
Ok(Some(amount.checked_mul_dec(fee_percentage).ok_or_else(
324+
|| HostError::Other {
325+
description:
326+
"Overflow in MASP unshielding fee computation".to_string(),
327+
},
328+
)?))
231329
}
232330

233331
fn store_masp_note_commitments(
@@ -522,7 +620,24 @@ where
522620
) -> Result<(), HostError> {
523621
let (ibc_token, amount) = self.get_token_amount(coin)?;
524622

525-
self.validate_masp_withdraw(from_account)?;
623+
let from_trans_account = if self.has_masp_tx {
624+
Cow::Owned(MASP)
625+
} else {
626+
from_account.to_address()
627+
};
628+
let amount = self.maybe_handle_masp_unshielding(
629+
from_account,
630+
&ibc_token,
631+
&amount,
632+
|fee| {
633+
self.insert_verifier(&PGF);
634+
self.inner
635+
.borrow_mut()
636+
.transfer_token(&from_trans_account, &PGF, &ibc_token, fee)
637+
.map_err(HostError::from)
638+
},
639+
)?;
640+
526641
self.increment_per_epoch_withdraw_limits(&ibc_token, amount)?;
527642

528643
// A transfer of NUT tokens must be verified by their VP
@@ -532,16 +647,10 @@ where
532647
self.insert_verifier(&ibc_token);
533648
}
534649

535-
let from_account = if self.has_masp_tx {
536-
Cow::Owned(MASP)
537-
} else {
538-
from_account.to_address()
539-
};
540-
541650
self.inner
542651
.borrow_mut()
543652
.transfer_token(
544-
&from_account,
653+
&from_trans_account,
545654
&IBC_ESCROW_ADDRESS,
546655
&ibc_token,
547656
amount,
@@ -558,11 +667,21 @@ where
558667
) -> Result<(), HostError> {
559668
let (ibc_token, amount) = self.get_token_amount(coin)?;
560669

561-
self.increment_per_epoch_deposit_limits(&ibc_token, amount)?;
562-
self.maybe_store_masp_note_commitments(
563-
to_account, &ibc_token, &amount,
670+
let amount = self.maybe_handle_masp_memoless_shielding(
671+
to_account,
672+
&ibc_token,
673+
&amount,
674+
|fee| {
675+
self.insert_verifier(&PGF);
676+
self.inner
677+
.borrow_mut()
678+
.transfer_token(&IBC_ESCROW_ADDRESS, &PGF, &ibc_token, fee)
679+
.map_err(HostError::from)
680+
},
564681
)?;
565682

683+
self.increment_per_epoch_deposit_limits(&ibc_token, amount)?;
684+
566685
self.inner
567686
.borrow_mut()
568687
.transfer_token(
@@ -582,9 +701,21 @@ where
582701
// The trace path of the denom is already updated if receiving the token
583702
let (ibc_token, amount) = self.get_token_amount(coin)?;
584703

704+
let amount = self.maybe_handle_masp_memoless_shielding(
705+
account,
706+
&ibc_token,
707+
&amount,
708+
|fee| {
709+
self.insert_verifier(&PGF);
710+
self.inner
711+
.borrow_mut()
712+
.mint_token(&PGF, &ibc_token, fee)
713+
.map_err(HostError::from)
714+
},
715+
)?;
716+
585717
self.update_mint_amount(&ibc_token, amount, true)?;
586718
self.increment_per_epoch_deposit_limits(&ibc_token, amount)?;
587-
self.maybe_store_masp_note_commitments(account, &ibc_token, &amount)?;
588719

589720
// A transfer of NUT tokens must be verified by their VP
590721
if ibc_token.is_internal()
@@ -613,7 +744,24 @@ where
613744
) -> Result<(), HostError> {
614745
let (ibc_token, amount) = self.get_token_amount(coin)?;
615746

616-
self.validate_masp_withdraw(account)?;
747+
let trans_account = if self.has_masp_tx {
748+
Cow::Owned(MASP)
749+
} else {
750+
account.to_address()
751+
};
752+
let amount = self.maybe_handle_masp_unshielding(
753+
account,
754+
&ibc_token,
755+
&amount,
756+
|fee| {
757+
self.insert_verifier(&PGF);
758+
self.inner
759+
.borrow_mut()
760+
.transfer_token(&trans_account, &PGF, &ibc_token, fee)
761+
.map_err(HostError::from)
762+
},
763+
)?;
764+
617765
self.update_mint_amount(&ibc_token, amount, false)?;
618766
self.increment_per_epoch_withdraw_limits(&ibc_token, amount)?;
619767

@@ -624,16 +772,10 @@ where
624772
self.insert_verifier(&ibc_token);
625773
}
626774

627-
let account = if self.has_masp_tx {
628-
Cow::Owned(MASP)
629-
} else {
630-
account.to_address()
631-
};
632-
633775
// The burn is "unminting" from the minted balance
634776
self.inner
635777
.borrow_mut()
636-
.burn_token(&account, &ibc_token, amount)
778+
.burn_token(&trans_account, &ibc_token, amount)
637779
.map_err(HostError::from)
638780
}
639781
}

0 commit comments

Comments
 (0)