use super::{
mint_provider::MintProvider, runtime_provider::RuntimeProvider,
storage_provider::StorageProvider,
};
use casper_types::{
system::handle_payment::{Error, PAYMENT_PURSE_KEY, REFUND_PURSE_KEY},
Key, Phase, URef, U512,
};
use num::CheckedMul;
use num_rational::Ratio;
use num_traits::Zero;
pub fn get_payment_purse<R: RuntimeProvider>(runtime_provider: &mut R) -> Result<URef, Error> {
match runtime_provider.get_key(PAYMENT_PURSE_KEY) {
Some(Key::URef(uref)) => Ok(uref),
Some(_) => Err(Error::PaymentPurseKeyUnexpectedType),
None => Err(Error::PaymentPurseNotFound),
}
}
pub fn set_refund<R: RuntimeProvider>(runtime_provider: &mut R, purse: URef) -> Result<(), Error> {
if let Phase::Payment = runtime_provider.get_phase() {
runtime_provider.put_key(REFUND_PURSE_KEY, Key::URef(purse))?;
return Ok(());
}
Err(Error::SetRefundPurseCalledOutsidePayment)
}
pub fn get_refund_purse<R: RuntimeProvider>(
runtime_provider: &mut R,
) -> Result<Option<URef>, Error> {
match runtime_provider.get_key(REFUND_PURSE_KEY) {
Some(Key::URef(uref)) => Ok(Some(uref)),
Some(_) => Err(Error::RefundPurseKeyUnexpectedType),
None => Ok(None),
}
}
pub fn calculate_overpayment_and_fee(
limit: U512,
gas_price: u8,
cost: U512,
consumed: U512,
available_balance: U512,
refund_ratio: Ratio<U512>,
) -> Result<(U512, U512), Error> {
if available_balance < cost {
return Ok((U512::zero(), available_balance));
}
if refund_ratio.is_zero() {
return Ok((U512::zero(), cost));
}
let unspent = limit.saturating_sub(consumed);
if unspent == U512::zero() {
return Ok((U512::zero(), cost));
}
let base_refund = unspent * gas_price;
let adjusted_refund = Ratio::from(base_refund)
.checked_mul(&refund_ratio)
.ok_or(Error::ArithmeticOverflow)?
.to_integer();
let fee = cost
.checked_sub(adjusted_refund)
.ok_or(Error::ArithmeticOverflow)?;
Ok((adjusted_refund, fee))
}
pub fn payment_burn<P: MintProvider + RuntimeProvider + StorageProvider>(
provider: &mut P,
purse: URef,
amount: Option<U512>,
) -> Result<(), Error> {
let available_balance = match provider.available_balance(purse)? {
Some(balance) => balance,
None => return Err(Error::PaymentPurseBalanceNotFound),
};
let burn_amount = amount.unwrap_or(available_balance);
if burn_amount.is_zero() {
return Ok(());
}
let adjusted_balance = available_balance
.checked_sub(burn_amount)
.ok_or(Error::ArithmeticOverflow)?;
provider.write_balance(purse, adjusted_balance)?;
provider.reduce_total_supply(burn_amount)?;
Ok(())
}
pub fn distribute_accumulated_fees<P>(
provider: &mut P,
source_uref: URef,
amount: Option<U512>,
) -> Result<(), Error>
where
P: RuntimeProvider + MintProvider,
{
let fee_handling = provider.fee_handling();
if !fee_handling.is_accumulate() {
return Err(Error::IncompatiblePaymentSettings);
}
let administrative_accounts = provider.administrative_accounts();
let reward_recipients = U512::from(administrative_accounts.len());
let distribute_amount = match amount {
Some(amount) => amount,
None => provider.available_balance(source_uref)?.unwrap_or_default(),
};
if distribute_amount.is_zero() {
return Ok(());
}
let portion = distribute_amount
.checked_div(reward_recipients)
.unwrap_or_else(U512::zero);
if !portion.is_zero() {
for target in administrative_accounts {
provider.transfer_purse_to_account(source_uref, target, portion)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_calculate_expected_amounts() {
let limit = U512::from(6u64);
let gas_price = 1;
let cost = limit;
let consumed = U512::from(3u64);
let available = U512::from(10u64);
let (overpay, fee) = calculate_overpayment_and_fee(
limit,
gas_price,
cost,
consumed,
available,
Ratio::new_raw(U512::from(1), U512::from(1)),
)
.unwrap();
let unspent = limit.saturating_sub(consumed);
let expected = unspent;
assert_eq!(expected, overpay, "overpay");
let expected_fee = consumed;
assert_eq!(expected_fee, fee, "fee");
}
#[test]
fn should_handle_straight_percentages() {
let limit = U512::from(100u64);
let gas_price = 1;
let cost = limit;
let consumed = U512::from(50u64);
let available = U512::from(1000u64);
let denom = 100;
for numer in 0..=denom {
let refund_ratio = Ratio::new_raw(U512::from(numer), U512::from(denom));
let (overpay, fee) = calculate_overpayment_and_fee(
limit,
gas_price,
cost,
consumed,
available,
refund_ratio,
)
.unwrap();
let unspent = limit.saturating_sub(consumed);
let expected = Ratio::from(unspent)
.checked_mul(&refund_ratio)
.ok_or(Error::ArithmeticOverflow)
.expect("should math")
.to_integer();
assert_eq!(expected, overpay, "overpay");
let expected_fee = limit - expected;
assert_eq!(expected_fee, fee, "fee");
}
}
#[test]
fn should_roll_over_dust() {
let limit = U512::from(6u64);
let gas_price = 1;
let cost = limit;
let consumed = U512::from(3u64);
let available = U512::from(10u64);
for percentage in 0..=100 {
let refund_ratio = Ratio::new_raw(U512::from(percentage), U512::from(100));
let (overpay, fee) = calculate_overpayment_and_fee(
limit,
gas_price,
cost,
consumed,
available,
refund_ratio,
)
.expect("should have overpay and fee");
let a = Ratio::from(overpay);
let b = Ratio::from(fee);
assert_eq!(a + b, Ratio::from(cost), "{}", percentage);
}
}
#[test]
fn should_take_all_of_insufficient_balance() {
let limit = U512::from(6u64);
let gas_price = 1;
let cost = limit;
let consumed = U512::from(3u64);
let available = U512::from(5u64);
let (overpay, fee) = calculate_overpayment_and_fee(
limit,
gas_price,
cost,
consumed,
available,
Ratio::new_raw(U512::from(1), U512::from(1)),
)
.unwrap();
assert_eq!(U512::zero(), overpay, "overpay");
let expected = available;
assert_eq!(expected, fee, "fee");
}
#[test]
fn should_handle_non_1_gas_price() {
let limit = U512::from(6u64);
let gas_price = 2;
let cost = limit * gas_price;
let consumed = U512::from(3u64);
let available = U512::from(12u64);
let (overpay, fee) = calculate_overpayment_and_fee(
limit,
gas_price,
cost,
consumed,
available,
Ratio::new_raw(U512::from(1), U512::from(1)),
)
.unwrap();
let unspent = limit.saturating_sub(consumed);
let expected = unspent * gas_price;
assert_eq!(expected, overpay, "overpay");
let expected_fee = consumed * gas_price;
assert_eq!(expected_fee, fee, "fee");
}
#[test]
fn should_handle_extra_cost() {
let limit = U512::from(6u64);
let gas_price = 2;
let extra_cost = U512::from(1u64);
let cost = limit * gas_price + extra_cost;
let consumed = U512::from(3u64);
let available = U512::from(21u64);
let (overpay, fee) = calculate_overpayment_and_fee(
limit,
gas_price,
cost,
consumed,
available,
Ratio::new_raw(U512::from(1), U512::from(1)),
)
.unwrap();
let unspent = limit.saturating_sub(consumed);
let expected = unspent * gas_price;
assert_eq!(expected, overpay, "overpay");
let expected_fee = consumed * gas_price + extra_cost;
assert_eq!(expected_fee, fee, "fee");
}
}