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, PurseLookupError, Result},
Key, Phase, URef, U512,
};
const SYSTEM_ACCOUNT: AccountHash = AccountHash::new([0u8; 32]);
const PAYMENT_PURSE_KEY: &str = "pos_payment_purse";
const REFUND_PURSE_KEY: &str = "pos_refund_purse";
fn get_purse<R: RuntimeProvider>(
runtime_provider: &R,
name: &str,
) -> core::result::Result<URef, PurseLookupError> {
runtime_provider
.get_key(name)
.ok_or(PurseLookupError::KeyNotFound)
.and_then(|key| match key {
Key::URef(uref) => Ok(uref),
_ => Err(PurseLookupError::KeyUnexpectedType),
})
}
pub fn get_payment_purse<R: RuntimeProvider>(runtime_provider: &R) -> Result<URef> {
get_purse::<R>(runtime_provider, PAYMENT_PURSE_KEY).map_err(PurseLookupError::payment)
}
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 get_purse::<R>(runtime_provider, REFUND_PURSE_KEY) {
Ok(uref) => Ok(Some(uref)),
Err(PurseLookupError::KeyNotFound) => Ok(None),
Err(PurseLookupError::KeyUnexpectedType) => Err(Error::RefundPurseKeyUnexpectedType),
}
}
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),
}
}
}