mod constants;
mod mint_provider;
mod runtime_provider;
use core::marker::Sized;
use crate::{account::AccountHash, system_contract_errors::pos::Result, AccessRights, URef, U512};
pub use crate::proof_of_stake::{
constants::*, mint_provider::MintProvider, runtime_provider::RuntimeProvider,
};
pub trait ProofOfStake: MintProvider + RuntimeProvider + Sized {
fn get_payment_purse(&self) -> Result<URef> {
let purse = internal::get_payment_purse(self)?;
Ok(URef::new(purse.addr(), AccessRights::READ_ADD))
}
fn set_refund_purse(&mut self, purse: URef) -> Result<()> {
internal::set_refund(self, purse)
}
fn get_refund_purse(&self) -> Result<Option<URef>> {
let maybe_purse = internal::get_refund_purse(self)?;
Ok(maybe_purse.map(|p| p.remove_access_rights()))
}
fn finalize_payment(
&mut self,
amount_spent: U512,
account: AccountHash,
target: URef,
) -> Result<()> {
internal::finalize_payment(self, amount_spent, account, target)
}
}
mod internal {
use crate::{
account::AccountHash,
proof_of_stake::{MintProvider, RuntimeProvider},
system_contract_errors::pos::{Error, Result},
Key, Phase, URef, U512,
};
use super::{PAYMENT_PURSE_KEY, REFUND_PURSE_KEY};
const SYSTEM_ACCOUNT: AccountHash = AccountHash::new([0u8; 32]);
pub fn get_payment_purse<R: RuntimeProvider>(runtime_provider: &R) -> Result<URef> {
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<()> {
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: &R) -> Result<Option<URef>> {
match runtime_provider.get_key(REFUND_PURSE_KEY) {
Some(Key::URef(uref)) => Ok(Some(uref)),
Some(_) => Err(Error::RefundPurseKeyUnexpectedType),
None => Ok(None),
}
}
pub fn finalize_payment<P: MintProvider + RuntimeProvider>(
provider: &mut P,
amount_spent: U512,
account: AccountHash,
target: URef,
) -> Result<()> {
let caller = provider.get_caller();
if caller != SYSTEM_ACCOUNT {
return Err(Error::SystemFunctionCalledByUserAccount);
}
let payment_purse = get_payment_purse(provider)?;
let total = match provider.balance(payment_purse)? {
Some(balance) => balance,
None => return Err(Error::PaymentPurseBalanceNotFound),
};
if total < amount_spent {
return Err(Error::InsufficientPaymentForAmountSpent);
}
let refund_amount = total - amount_spent;
let refund_purse = get_refund_purse(provider)?;
provider.remove_key(REFUND_PURSE_KEY)?;
provider
.transfer_purse_to_purse(payment_purse, target, amount_spent)
.map_err(|_| Error::FailedTransferToRewardsPurse)?;
if refund_amount.is_zero() {
return Ok(());
}
let refund_purse = match refund_purse {
Some(uref) => uref,
None => return refund_to_account::<P>(provider, payment_purse, account, refund_amount),
};
if provider
.transfer_purse_to_purse(payment_purse, refund_purse, refund_amount)
.is_err()
{
return refund_to_account::<P>(provider, payment_purse, account, refund_amount);
}
Ok(())
}
pub fn refund_to_account<M: MintProvider>(
mint_provider: &mut M,
payment_purse: URef,
account: AccountHash,
amount: U512,
) -> Result<()> {
match mint_provider.transfer_purse_to_account(payment_purse, account, amount) {
Ok(_) => Ok(()),
Err(_) => Err(Error::FailedTransferToAccountPurse),
}
}
}