use std::ops::Neg;
use cid::Cid;
pub use error::TokenError;
use fvm_actor_utils::messaging::{MessagingError, RECEIVER_HOOK_METHOD_NUM};
use fvm_actor_utils::receiver::{ReceiverHook, ReceiverHookError};
use fvm_actor_utils::syscalls::Syscalls;
use fvm_actor_utils::util::ActorRuntime;
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::ipld_block::IpldBlock;
use fvm_ipld_encoding::RawBytes;
use fvm_shared::address::Address;
use fvm_shared::econ::TokenAmount;
use fvm_shared::error::ExitCode;
use num_traits::Zero;
use self::state::{StateError as TokenStateError, StateInvariantError, StateSummary, TokenState};
use self::types::TransferFromIntermediate;
use self::types::TransferFromReturn;
use self::types::TransferReturn;
use self::types::{BurnFromReturn, MintIntermediate};
use self::types::{BurnReturn, TransferIntermediate};
use crate::receiver::{FRC46ReceiverHook, FRC46TokenReceived};
use crate::token::types::MintReturn;
use crate::token::TokenError::InvalidGranularity;
mod error;
pub mod state;
pub mod types;
pub const TOKEN_PRECISION: u64 = 1_000_000_000_000_000_000;
type Result<T> = std::result::Result<T, TokenError>;
pub struct Token<'st, S, BS>
where
S: Syscalls,
BS: Blockstore,
{
runtime: &'st ActorRuntime<S, BS>,
state: &'st mut TokenState,
granularity: u64,
}
impl<'st, S, BS> Token<'st, S, BS>
where
S: Syscalls,
BS: Blockstore,
{
pub fn create_state(bs: &BS) -> Result<TokenState> {
Ok(TokenState::new(bs)?)
}
pub fn create_state_with_bit_width(bs: &BS, hamt_bit_width: u32) -> Result<TokenState> {
Ok(TokenState::new_with_bit_width(bs, hamt_bit_width)?)
}
pub fn wrap(
runtime: &'st ActorRuntime<S, BS>,
granularity: u64,
state: &'st mut TokenState,
) -> Self {
Self { runtime, granularity, state }
}
pub fn replace(&mut self, state: TokenState) -> TokenState {
std::mem::replace(self.state, state)
}
pub fn load_state(bs: &BS, state_cid: &Cid) -> Result<TokenState> {
Ok(TokenState::load(bs, state_cid)?)
}
pub fn load_replace(&mut self, cid: &Cid) -> Result<TokenState> {
Ok(self.replace(Self::load_state(self.runtime.bs(), cid)?))
}
pub fn flush(&mut self) -> Result<Cid> {
Ok(self.state.save(&self.runtime)?)
}
pub fn state(&self) -> &TokenState {
self.state
}
pub fn runtime(&self) -> &ActorRuntime<S, BS> {
self.runtime
}
fn transaction<F, Res>(&mut self, f: F) -> Result<Res>
where
F: FnOnce(&mut TokenState, &ActorRuntime<S, BS>) -> Result<Res>,
{
let mut mutable_state = self.state.clone();
let res = f(&mut mutable_state, self.runtime)?;
*self.state = mutable_state;
Ok(res)
}
}
impl<'st, S, BS> Token<'st, S, BS>
where
S: Syscalls,
BS: Blockstore,
{
pub fn granularity(&self) -> u64 {
self.granularity
}
pub fn mint(
&mut self,
operator: &Address,
initial_owner: &Address,
amount: &TokenAmount,
operator_data: RawBytes,
token_data: RawBytes,
) -> Result<ReceiverHook<MintIntermediate>> {
let amount = validate_amount_with_granularity(amount, "mint", self.granularity)?;
let operator_id = self.runtime.resolve_or_init(operator)?;
let owner_id = self.runtime.resolve_or_init(initial_owner)?;
let result = self.transaction(|state, bs| {
state.change_balance_by(&bs, owner_id, amount)?;
state.change_supply_by(amount)?;
Ok(MintIntermediate { recipient: *initial_owner, recipient_data: RawBytes::default() })
})?;
let params = FRC46TokenReceived {
operator: operator_id,
from: self.runtime.actor_id(),
to: owner_id,
amount: amount.clone(),
operator_data,
token_data,
};
Ok(ReceiverHook::new_frc46(*initial_owner, params, result)?)
}
pub fn mint_return(&self, intermediate: MintIntermediate) -> Result<MintReturn> {
Ok(MintReturn {
balance: self.balance_of(&intermediate.recipient)?,
supply: self.total_supply(),
recipient_data: intermediate.recipient_data,
})
}
pub fn total_supply(&self) -> TokenAmount {
self.state.supply.clone()
}
pub fn balance_of(&self, owner: &Address) -> Result<TokenAmount> {
match self.runtime.resolve_id(owner) {
Ok(owner) => Ok(self.state.get_balance(&self.runtime, owner)?),
Err(MessagingError::AddressNotResolved(_)) => {
Ok(TokenAmount::zero())
}
Err(e) => Err(e.into()),
}
}
pub fn allowance(&self, owner: &Address, operator: &Address) -> Result<TokenAmount> {
let owner = match self.runtime.resolve_id(owner) {
Ok(owner) => owner,
Err(MessagingError::AddressNotResolved(_)) => {
return Ok(TokenAmount::zero());
}
Err(e) => return Err(e.into()),
};
let operator = match self.runtime.resolve_id(operator) {
Ok(operator) => operator,
Err(MessagingError::AddressNotResolved(_)) => {
return Ok(TokenAmount::zero());
}
Err(e) => return Err(e.into()),
};
Ok(self.state.get_allowance_between(&self.runtime, owner, operator)?)
}
pub fn increase_allowance(
&mut self,
owner: &Address,
operator: &Address,
delta: &TokenAmount,
) -> Result<TokenAmount> {
let delta = validate_allowance(delta, "increase allowance delta")?;
let owner = self.runtime.resolve_or_init(owner)?;
let operator = self.runtime.resolve_or_init(operator)?;
let new_amount = self.state.change_allowance_by(&self.runtime, owner, operator, delta)?;
Ok(new_amount)
}
pub fn decrease_allowance(
&mut self,
owner: &Address,
operator: &Address,
delta: &TokenAmount,
) -> Result<TokenAmount> {
let delta = validate_allowance(delta, "decrease allowance delta")?;
let owner = self.runtime.resolve_or_init(owner)?;
let operator = self.runtime.resolve_or_init(operator)?;
let new_allowance =
self.state.change_allowance_by(&self.runtime, owner, operator, &delta.neg())?;
Ok(new_allowance)
}
pub fn revoke_allowance(&mut self, owner: &Address, operator: &Address) -> Result<TokenAmount> {
let owner = match self.runtime.resolve_id(owner) {
Ok(owner) => owner,
Err(MessagingError::AddressNotResolved(_)) => {
return Ok(TokenAmount::zero());
}
Err(e) => return Err(e.into()),
};
let operator = match self.runtime.resolve_id(operator) {
Ok(operator) => operator,
Err(MessagingError::AddressNotResolved(_)) => {
return Ok(TokenAmount::zero());
}
Err(e) => return Err(e.into()),
};
Ok(self.state.revoke_allowance(&self.runtime, owner, operator)?)
}
pub fn set_allowance(
&mut self,
owner: &Address,
operator: &Address,
amount: &TokenAmount,
) -> Result<TokenAmount> {
let amount = validate_allowance(amount, "set allowance amount")?;
if amount.is_zero() {
return self.revoke_allowance(owner, operator);
}
let owner = self.runtime.resolve_or_init(owner)?;
let operator = self.runtime.resolve_or_init(operator)?;
Ok(self.state.set_allowance(&self.runtime, owner, operator, amount)?)
}
pub fn burn(&mut self, owner: &Address, amount: &TokenAmount) -> Result<BurnReturn> {
let amount = validate_amount_with_granularity(amount, "burn", self.granularity)?;
let owner = self.runtime.resolve_or_init(owner)?;
self.transaction(|state, bs| {
let new_amount = state.change_balance_by(&bs, owner, &amount.clone().neg())?;
state.change_supply_by(&amount.neg())?;
Ok(BurnReturn { balance: new_amount })
})
}
pub fn burn_from(
&mut self,
operator: &Address,
owner: &Address,
amount: &TokenAmount,
) -> Result<BurnFromReturn> {
let amount = validate_amount_with_granularity(amount, "burn", self.granularity)?;
if self.runtime.same_address(operator, owner) {
return Err(TokenError::InvalidOperator(*operator));
}
let operator = match self.runtime.resolve_id(operator) {
Ok(operator) => operator,
Err(MessagingError::AddressNotResolved(addr)) => {
return Err(TokenStateError::InsufficientAllowance {
owner: (*owner).into(),
operator: addr.into(),
allowance: TokenAmount::zero(),
delta: amount.clone(),
}
.into());
}
Err(e) => return Err(e.into()),
};
let owner = match self.runtime.resolve_id(owner) {
Ok(owner) => owner,
Err(MessagingError::AddressNotResolved(addr)) => {
return Err(TokenStateError::InsufficientAllowance {
owner: (*owner).into(),
operator: addr.into(),
allowance: TokenAmount::zero(),
delta: amount.clone(),
}
.into());
}
Err(e) => return Err(e.into()),
};
self.transaction(|state, bs| {
let new_allowance = state.attempt_use_allowance(&bs, operator, owner, amount)?;
let new_balance = state.change_balance_by(&bs, owner, &amount.clone().neg())?;
state.change_supply_by(&amount.neg())?;
Ok(BurnFromReturn { balance: new_balance, allowance: new_allowance })
})
}
pub fn transfer(
&mut self,
from: &Address,
to: &Address,
amount: &TokenAmount,
operator_data: RawBytes,
token_data: RawBytes,
) -> Result<ReceiverHook<TransferIntermediate>> {
let amount = validate_amount_with_granularity(amount, "transfer", self.granularity)?;
let from_id = self.runtime.resolve_or_init(from)?;
let to_id = self.runtime.resolve_or_init(to)?;
self.transaction(|state, bs| {
state.make_transfer(&bs, from_id, to_id, amount)?;
Ok(())
})?;
let res =
TransferIntermediate { from: *from, to: *to, recipient_data: RawBytes::default() };
let params = FRC46TokenReceived {
operator: from_id,
from: from_id,
to: to_id,
amount: amount.clone(),
operator_data,
token_data,
};
Ok(ReceiverHook::new_frc46(*to, params, res)?)
}
pub fn transfer_return(&self, intermediate: TransferIntermediate) -> Result<TransferReturn> {
Ok(TransferReturn {
from_balance: self.balance_of(&intermediate.from)?,
to_balance: self.balance_of(&intermediate.to)?,
recipient_data: intermediate.recipient_data,
})
}
pub fn transfer_from(
&mut self,
operator: &Address,
from: &Address,
to: &Address,
amount: &TokenAmount,
operator_data: RawBytes,
token_data: RawBytes,
) -> Result<ReceiverHook<TransferFromIntermediate>> {
let amount = validate_amount_with_granularity(amount, "transfer", self.granularity)?;
if self.runtime.same_address(operator, from) {
return Err(TokenError::InvalidOperator(*operator));
}
let operator_id = match self.runtime.resolve_id(operator) {
Ok(id) => id,
Err(MessagingError::AddressNotResolved(_)) => {
return Err(TokenError::TokenState(TokenStateError::InsufficientAllowance {
operator: (*operator).into(),
owner: (*from).into(),
allowance: TokenAmount::zero(),
delta: amount.clone(),
}));
}
Err(e) => return Err(e.into()),
};
let from_id = match self.runtime.resolve_id(from) {
Ok(id) => id,
Err(MessagingError::AddressNotResolved(from)) => {
return Err(TokenError::TokenState(TokenStateError::InsufficientAllowance {
operator: (*operator).into(),
owner: from.into(),
allowance: TokenAmount::zero(),
delta: amount.clone(),
}));
}
Err(e) => return Err(e.into()),
};
let to_id = self.runtime.resolve_or_init(to)?;
self.transaction(|state, bs| {
state.attempt_use_allowance(&bs, operator_id, from_id, amount)?;
state.make_transfer(&bs, from_id, to_id, amount)?;
Ok(())
})?;
let res = TransferFromIntermediate {
operator: *operator,
from: *from,
to: *to,
recipient_data: RawBytes::default(),
};
let params = FRC46TokenReceived {
operator: operator_id,
from: from_id,
to: to_id,
amount: amount.clone(),
operator_data,
token_data,
};
Ok(ReceiverHook::new_frc46(*to, params, res)?)
}
pub fn transfer_from_return(
&self,
intermediate: TransferFromIntermediate,
) -> Result<TransferFromReturn> {
Ok(TransferFromReturn {
from_balance: self.balance_of(&intermediate.from)?,
to_balance: self.balance_of(&intermediate.to)?,
allowance: self.allowance(&intermediate.from, &intermediate.operator)?, recipient_data: intermediate.recipient_data,
})
}
pub fn set_balance(&mut self, owner: &Address, amount: &TokenAmount) -> Result<TokenAmount> {
let amount = validate_amount_with_granularity(amount, "set_balance", self.granularity)?;
let owner = self.runtime.resolve_or_init(owner)?;
let old_balance = self.transaction(|state, bs| {
let old_balance = state.set_balance(bs, owner, amount)?;
let supply_change = amount - old_balance.clone();
state.supply += supply_change;
Ok(old_balance)
})?;
Ok(old_balance)
}
}
impl<'st, S, BS> Token<'st, S, BS>
where
S: Syscalls,
BS: Blockstore,
{
pub fn call_receiver_hook(
&mut self,
token_receiver: &Address,
params: FRC46TokenReceived,
) -> Result<()> {
let receipt = self.runtime.send(
token_receiver,
RECEIVER_HOOK_METHOD_NUM,
IpldBlock::serialize_cbor(¶ms)?,
TokenAmount::zero(),
)?;
match receipt.exit_code {
ExitCode::OK => Ok(()),
abort_code => Err(ReceiverHookError::new_receiver_error(
*token_receiver,
abort_code,
receipt.return_data,
)
.into()),
}
}
pub fn assert_invariants(&self) -> std::result::Result<StateSummary, Vec<StateInvariantError>> {
let (summary, errors) = self.check_invariants();
match errors.is_empty() {
true => Ok(summary),
false => Err(errors),
}
}
pub fn check_invariants(&self) -> (StateSummary, Vec<StateInvariantError>) {
self.state.check_invariants(&self.runtime, self.granularity)
}
}
pub fn validate_amount_with_granularity<'a>(
a: &'a TokenAmount,
name: &'static str,
granularity: u64,
) -> Result<&'a TokenAmount> {
if a.is_negative() {
return Err(TokenError::InvalidNegative { name, amount: a.clone() });
}
let (_, modulus) = a.div_rem(granularity);
if !modulus.is_zero() {
return Err(InvalidGranularity { name, amount: a.clone(), granularity });
}
Ok(a)
}
pub fn validate_allowance<'a>(a: &'a TokenAmount, name: &'static str) -> Result<&'a TokenAmount> {
if a.is_negative() {
return Err(TokenError::InvalidNegative { name, amount: a.clone() });
}
Ok(a)
}
#[cfg(test)]
mod test {
use std::ops::Neg;
use fvm_actor_utils::messaging::{MessagingError, RECEIVER_HOOK_METHOD_NUM};
use fvm_actor_utils::receiver::{ReceiverHookError, UniversalReceiverParams};
use fvm_actor_utils::syscalls::fake_syscalls::FakeSyscalls;
use fvm_actor_utils::util::ActorRuntime;
use fvm_ipld_blockstore::MemoryBlockstore;
use fvm_ipld_encoding::RawBytes;
use fvm_sdk::sys::ErrorNumber;
use fvm_shared::address::{Address, BLS_PUB_LEN};
use fvm_shared::econ::TokenAmount;
use num_traits::Zero;
use crate::receiver::{FRC46TokenReceived, FRC46_TOKEN_TYPE};
use crate::token::state::StateError;
use crate::token::state::TokenState;
use crate::token::Token;
use crate::token::TokenError;
fn secp_address() -> Address {
let key = vec![0; 65];
Address::new_secp256k1(key.as_slice()).unwrap()
}
fn bls_address() -> Address {
let key = vec![0; BLS_PUB_LEN];
Address::new_bls(key.as_slice()).unwrap()
}
fn actor_address() -> Address {
Address::new_actor(Default::default())
}
const TOKEN_ACTOR: &Address = &Address::new_id(1);
const TREASURY: &Address = &Address::new_id(2);
const ALICE: &Address = &Address::new_id(3);
const BOB: &Address = &Address::new_id(4);
const CAROL: &Address = &Address::new_id(5);
fn new_token<'st>(
runtime: &'st ActorRuntime<FakeSyscalls, MemoryBlockstore>,
state: &'st mut TokenState,
) -> Token<'st, FakeSyscalls, MemoryBlockstore> {
Token::wrap(runtime, 1, state)
}
fn assert_last_hook_call_eq(
runtime: &ActorRuntime<FakeSyscalls, MemoryBlockstore>,
expected: FRC46TokenReceived,
) {
let last_message = runtime.syscalls.last_message.borrow().clone().unwrap();
assert_eq!(last_message.method, RECEIVER_HOOK_METHOD_NUM);
let last_called: UniversalReceiverParams =
last_message.params.unwrap().deserialize().unwrap();
assert_eq!(last_called.type_, FRC46_TOKEN_TYPE);
let last_called: FRC46TokenReceived = last_called.payload.deserialize().unwrap();
assert_eq!(last_called, expected);
}
#[test]
fn it_wraps_a_previously_loaded_state_tree() {
struct ActorState {
token_state: TokenState,
}
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut actor_state = ActorState {
token_state: Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs())
.unwrap(),
};
let mut token = new_token(&helper, &mut actor_state.token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
TREASURY,
&TokenAmount::from_atto(1),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let state = token.state();
assert_eq!(state.supply, TokenAmount::from_atto(1));
assert_eq!(actor_state.token_state.supply, TokenAmount::from_atto(1));
}
#[test]
fn it_instantiates_and_persists() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut state = Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut state);
assert_eq!(token.total_supply(), TokenAmount::zero());
let mut hook = token
.mint(
TOKEN_ACTOR,
TREASURY,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
let cid = token.flush().unwrap();
let mut state =
Token::<FakeSyscalls, MemoryBlockstore>::load_state(helper.bs(), &cid).unwrap();
let token2 = Token::wrap(token.runtime, 1, &mut state);
assert_eq!(token2.total_supply(), TokenAmount::from_atto(100));
}
#[test]
fn it_instantiates_with_variable_bit_width() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state_with_bit_width(helper.bs(), 2)
.unwrap();
state.set_balance(&helper, ALICE.id().unwrap(), &TokenAmount::from_atto(100)).unwrap();
let state_cid = state.save(&helper).unwrap();
let token =
Token::<FakeSyscalls, MemoryBlockstore>::load_state(helper.bs(), &state_cid).unwrap();
assert_eq!(
token.get_balance(&helper, ALICE.id().unwrap()).unwrap(),
TokenAmount::from_atto(100)
);
}
#[test]
fn it_mutates_externally_loaded_state() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut state = TokenState::new(&helper).unwrap();
let mut token = Token::<FakeSyscalls, MemoryBlockstore>::wrap(&helper, 1, &mut state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
assert_eq!(state.supply, TokenAmount::from_atto(100));
assert_eq!(
state.get_balance(&helper, ALICE.id().unwrap()).unwrap(),
TokenAmount::from_atto(100)
);
}
#[test]
fn it_provides_atomic_transactions() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
token
.transaction(|state, _bs| {
state.change_supply_by(&TokenAmount::from_atto(100))?;
state.change_supply_by(&TokenAmount::from_atto(100))?;
Ok(())
})
.unwrap();
assert_eq!(token.total_supply(), TokenAmount::from_atto(200));
token
.transaction(|state, _bs| {
state.change_supply_by(&TokenAmount::from_atto(-100))?;
state.change_supply_by(&TokenAmount::from_atto(-100))?;
state.change_supply_by(&TokenAmount::from_atto(-100))?;
Ok(())
})
.unwrap_err();
assert_eq!(token.total_supply(), TokenAmount::from_atto(200));
}
#[test]
fn it_mints() {
let mut helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
helper.syscalls.actor_id = TOKEN_ACTOR.id().unwrap(); let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::zero());
let mut hook = token
.mint(
TOKEN_ACTOR,
TREASURY,
&TokenAmount::from_atto(1_000_000),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
let hook_ret = hook.call(token.runtime).unwrap();
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: TOKEN_ACTOR.id().unwrap(),
from: TOKEN_ACTOR.id().unwrap(),
to: TREASURY.id().unwrap(),
amount: TokenAmount::from_atto(1_000_000),
operator_data: Default::default(),
token_data: Default::default(),
},
);
let result = token.mint_return(hook_ret).unwrap();
assert_eq!(TokenAmount::from_atto(1_000_000), result.balance);
assert_eq!(TokenAmount::from_atto(1_000_000), result.supply);
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.total_supply(), TokenAmount::from_atto(1_000_000));
token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(-1),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::from_atto(1_000_000));
let mut hook = token
.mint(TOKEN_ACTOR, ALICE, &TokenAmount::zero(), Default::default(), Default::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: TOKEN_ACTOR.id().unwrap(),
from: TOKEN_ACTOR.id().unwrap(),
to: ALICE.id().unwrap(),
amount: TokenAmount::zero(),
operator_data: Default::default(),
token_data: Default::default(),
},
);
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::from_atto(1_000_000));
let mut hook = token
.mint(
TOKEN_ACTOR,
TREASURY,
&TokenAmount::from_atto(1_000_000),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
let hook_ret = hook.call(token.runtime).unwrap();
let result = token.mint_return(hook_ret).unwrap();
assert_eq!(TokenAmount::from_atto(2_000_000), result.balance);
assert_eq!(TokenAmount::from_atto(2_000_000), result.supply);
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(2_000_000));
assert_eq!(token.total_supply(), TokenAmount::from_atto(2_000_000));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: TOKEN_ACTOR.id().unwrap(),
from: TOKEN_ACTOR.id().unwrap(),
to: TREASURY.id().unwrap(),
amount: TokenAmount::from_atto(1_000_000),
operator_data: Default::default(),
token_data: Default::default(),
},
);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(1_000_000),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
let hook_ret = hook.call(token.runtime).unwrap();
let result = token.mint_return(hook_ret).unwrap();
assert_eq!(TokenAmount::from_atto(1_000_000), result.balance);
assert_eq!(TokenAmount::from_atto(3_000_000), result.supply);
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(2_000_000));
assert_eq!(token.total_supply(), TokenAmount::from_atto(3_000_000));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: TOKEN_ACTOR.id().unwrap(),
from: TOKEN_ACTOR.id().unwrap(),
to: ALICE.id().unwrap(),
amount: TokenAmount::from_atto(1_000_000),
operator_data: Default::default(),
token_data: Default::default(),
},
);
assert_eq!(token.balance_of(CAROL).unwrap(), TokenAmount::zero());
let secp_address = secp_address();
assert_eq!(token.balance_of(&secp_address).unwrap(), TokenAmount::zero());
let mut hook = token
.mint(
TOKEN_ACTOR,
&secp_address,
&TokenAmount::from_atto(1_000_000),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: TOKEN_ACTOR.id().unwrap(),
from: TOKEN_ACTOR.id().unwrap(),
to: token.runtime.resolve_id(&secp_address).unwrap(),
amount: TokenAmount::from_atto(1_000_000),
operator_data: Default::default(),
token_data: Default::default(),
},
);
let bls_address = bls_address();
assert_eq!(token.balance_of(&bls_address).unwrap(), TokenAmount::zero());
let mut hook = token
.mint(
TOKEN_ACTOR,
&bls_address,
&TokenAmount::from_atto(1_000_000),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(2_000_000));
assert_eq!(token.balance_of(&secp_address).unwrap(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.balance_of(&bls_address).unwrap(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.total_supply(), TokenAmount::from_atto(5_000_000));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: TOKEN_ACTOR.id().unwrap(),
from: TOKEN_ACTOR.id().unwrap(),
to: token.runtime.resolve_id(&bls_address).unwrap(),
amount: TokenAmount::from_atto(1_000_000),
operator_data: Default::default(),
token_data: Default::default(),
},
);
let actor_address: Address = actor_address();
token
.mint(
TOKEN_ACTOR,
&actor_address,
&TokenAmount::from_atto(1_000_000),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(2_000_000));
assert_eq!(token.balance_of(&secp_address).unwrap(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.balance_of(&bls_address).unwrap(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.total_supply(), TokenAmount::from_atto(5_000_000));
token.assert_invariants().unwrap();
}
#[test]
fn it_fails_to_mint_if_receiver_hook_aborts() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
token.runtime.syscalls.abort_next_send.replace(true);
let original_state = token.state().clone();
let mut hook = token
.mint(
TOKEN_ACTOR,
TREASURY,
&TokenAmount::from_atto(1_000_000),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
let err = hook.call(token.runtime).unwrap_err();
if let ReceiverHookError::Messaging(MessagingError::Syscall(e)) = err {
assert_eq!(e, ErrorNumber::AssertionFailed);
} else {
panic!("expected receiver hook error {err:?}");
}
token.replace(original_state);
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::zero());
token.assert_invariants().unwrap();
}
#[test]
fn it_burns() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mint_amount = TokenAmount::from_atto(1_000_000);
let burn_amount = TokenAmount::from_atto(600_000);
let mut hook = token
.mint(TOKEN_ACTOR, TREASURY, &mint_amount, Default::default(), Default::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token.burn(TREASURY, &burn_amount).unwrap();
let total_supply = token.total_supply();
assert_eq!(total_supply, TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
token.burn(TREASURY, &TokenAmount::from_atto(-1)).unwrap_err();
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(400_000));
assert_eq!(token.total_supply(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
token.burn(TREASURY, &TokenAmount::zero()).unwrap();
let remaining_balance = token.balance_of(TREASURY).unwrap();
assert_eq!(remaining_balance, TokenAmount::from_atto(400_000));
assert_eq!(token.total_supply(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
token.burn(TREASURY, &remaining_balance).unwrap();
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::zero());
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
token.assert_invariants().unwrap();
}
#[test]
fn it_fails_to_burn_below_zero() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mint_amount = TokenAmount::from_atto(1_000_000);
let burn_amount = TokenAmount::from_atto(2_000_000);
let mut hook = token
.mint(TOKEN_ACTOR, TREASURY, &mint_amount, Default::default(), Default::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token.burn(TREASURY, &burn_amount).unwrap_err();
assert_eq!(token.total_supply(), TokenAmount::from_atto(1_000_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(1_000_000));
token.assert_invariants().unwrap();
}
#[test]
fn it_sets_balances() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
token.granularity = 50;
token.set_balance(ALICE, &TokenAmount::from_atto(49)).unwrap_err();
assert_eq!(token.total_supply(), TokenAmount::zero());
let old_balance = token.set_balance(ALICE, &TokenAmount::from_atto(100)).unwrap();
assert_eq!(old_balance, TokenAmount::zero());
let new_balance = token.balance_of(ALICE).unwrap();
assert_eq!(new_balance, TokenAmount::from_atto(100));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
let old_balance = token.set_balance(ALICE, &TokenAmount::from_atto(50)).unwrap();
assert_eq!(old_balance, TokenAmount::from_atto(100));
let new_balance = token.balance_of(ALICE).unwrap();
assert_eq!(new_balance, TokenAmount::from_atto(50));
assert_eq!(token.total_supply(), TokenAmount::from_atto(50));
token.set_balance(ALICE, &TokenAmount::from_atto(-50)).unwrap_err();
let new_balance = token.balance_of(ALICE).unwrap();
assert_eq!(new_balance, TokenAmount::from_atto(50));
assert_eq!(token.total_supply(), TokenAmount::from_atto(50));
let old_balance = token.set_balance(ALICE, &TokenAmount::from_atto(0)).unwrap();
assert_eq!(old_balance, TokenAmount::from_atto(50));
let new_balance = token.balance_of(ALICE).unwrap();
assert_eq!(new_balance, TokenAmount::from_atto(0));
assert_eq!(token.total_supply(), TokenAmount::from_atto(0));
token.assert_invariants().unwrap();
}
#[test]
fn it_transfers() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let mut hook = token
.transfer(
ALICE,
BOB,
&TokenAmount::from_atto(60),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
let intermediate = hook.call(token.runtime).unwrap();
let ret = token.transfer_return(intermediate).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(40));
assert_eq!(ret.from_balance, TokenAmount::from_atto(40));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::from_atto(60));
assert_eq!(ret.to_balance, TokenAmount::from_atto(60));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: ALICE.id().unwrap(),
from: ALICE.id().unwrap(),
amount: TokenAmount::from_atto(60),
to: BOB.id().unwrap(),
operator_data: Default::default(),
token_data: Default::default(),
},
);
token
.transfer(
ALICE,
BOB,
&TokenAmount::from_atto(-1),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(40));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::from_atto(60));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
let mut hook = token
.transfer(ALICE, BOB, &TokenAmount::zero(), RawBytes::default(), RawBytes::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(40));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::from_atto(60));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: ALICE.id().unwrap(),
from: ALICE.id().unwrap(),
to: BOB.id().unwrap(),
amount: TokenAmount::zero(),
operator_data: Default::default(),
token_data: Default::default(),
},
);
}
#[test]
fn it_transfers_to_self() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let mut hook = token
.transfer(ALICE, ALICE, &TokenAmount::zero(), RawBytes::default(), RawBytes::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: ALICE.id().unwrap(),
from: ALICE.id().unwrap(),
to: ALICE.id().unwrap(),
amount: TokenAmount::zero(),
operator_data: Default::default(),
token_data: Default::default(),
},
);
let mut hook = token
.transfer(
ALICE,
ALICE,
&TokenAmount::from_atto(10),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: ALICE.id().unwrap(),
from: ALICE.id().unwrap(),
to: ALICE.id().unwrap(),
amount: TokenAmount::from_atto(10),
operator_data: Default::default(),
token_data: Default::default(),
},
);
}
#[test]
fn it_transfers_to_uninitialized_addresses() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let secp_address = &secp_address();
assert_eq!(token.balance_of(secp_address).unwrap(), TokenAmount::zero());
let mut hook = token
.transfer(
ALICE,
secp_address,
&TokenAmount::from_atto(10),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(90));
assert_eq!(token.balance_of(secp_address).unwrap(), TokenAmount::from_atto(10));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: ALICE.id().unwrap(),
from: ALICE.id().unwrap(),
to: token.runtime.resolve_id(secp_address).unwrap(),
amount: TokenAmount::from_atto(10),
operator_data: Default::default(),
token_data: Default::default(),
},
);
}
#[test]
fn it_transfers_from_uninitialized_addresses() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let secp_address = &secp_address();
assert!(token
.transfer(
secp_address,
ALICE,
&TokenAmount::from_atto(1),
Default::default(),
Default::default()
)
.is_err());
assert_eq!(token.balance_of(secp_address).unwrap(), TokenAmount::zero());
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::zero());
let mut hook = token
.transfer(
secp_address,
ALICE,
&TokenAmount::zero(),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.balance_of(secp_address).unwrap(), TokenAmount::zero());
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::zero());
assert!(&token.runtime.resolve_id(secp_address).is_ok());
let actor_address = &actor_address();
let err = token
.transfer(
actor_address,
ALICE,
&TokenAmount::zero(),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
if let TokenError::Messaging(MessagingError::AddressNotInitialized(e)) = err {
assert_eq!(e, *actor_address);
} else {
panic!("Expected AddressNotInitialized error {err:?}");
}
assert_eq!(token.balance_of(secp_address).unwrap(), TokenAmount::zero());
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::zero());
assert!(&token.runtime.resolve_id(actor_address).is_err());
token.assert_invariants().unwrap();
}
#[test]
fn it_fails_to_transfer_when_receiver_hook_aborts() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let _ = token.runtime.syscalls.abort_next_send.replace(true);
let pre_transfer_state = token.state().clone();
let mut hook = token
.transfer(
ALICE,
BOB,
&TokenAmount::from_atto(60),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap_err();
token.replace(pre_transfer_state);
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::from_atto(0));
token.runtime.syscalls.abort_next_send.replace(true);
let pre_transfer_state = token.state().clone();
let mut hook = token
.transfer(
ALICE,
ALICE,
&TokenAmount::from_atto(60),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap_err();
token.replace(pre_transfer_state);
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::from_atto(0));
token.assert_invariants().unwrap();
}
#[test]
fn it_fails_to_transfer_when_insufficient_balance() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(50),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token
.transfer(
ALICE,
BOB,
&TokenAmount::from_atto(51),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(50));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::zero());
token.assert_invariants().unwrap();
}
#[test]
fn it_tracks_allowances() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let new_allowance =
token.increase_allowance(ALICE, CAROL, &TokenAmount::from_atto(100)).unwrap();
let allowance = token.allowance(ALICE, CAROL).unwrap();
assert_eq!(new_allowance, allowance);
assert_eq!(allowance, TokenAmount::from_atto(100));
assert_eq!(token.allowance(CAROL, ALICE).unwrap(), TokenAmount::zero());
assert_eq!(token.allowance(ALICE, BOB).unwrap(), TokenAmount::zero());
token.increase_allowance(ALICE, CAROL, &TokenAmount::from_atto(-1)).unwrap_err();
token.decrease_allowance(ALICE, CAROL, &TokenAmount::from_atto(-1)).unwrap_err();
let allowance = token.allowance(ALICE, CAROL).unwrap();
assert_eq!(allowance, TokenAmount::from_atto(100));
let new_allowance =
token.decrease_allowance(ALICE, CAROL, &TokenAmount::from_atto(60)).unwrap();
let allowance = token.allowance(ALICE, CAROL).unwrap();
assert_eq!(new_allowance, allowance);
assert_eq!(allowance, TokenAmount::from_atto(40));
token.revoke_allowance(ALICE, CAROL).unwrap();
assert_eq!(token.allowance(ALICE, CAROL).unwrap(), TokenAmount::zero());
token.increase_allowance(ALICE, CAROL, &TokenAmount::from_atto(10)).unwrap();
let new_allowance =
token.decrease_allowance(ALICE, CAROL, &TokenAmount::from_atto(20)).unwrap();
assert_eq!(new_allowance, TokenAmount::zero());
assert_eq!(token.allowance(ALICE, CAROL).unwrap(), TokenAmount::zero());
let resolvable_address = &secp_address();
assert_eq!(token.allowance(ALICE, resolvable_address).unwrap(), TokenAmount::zero());
token.increase_allowance(ALICE, resolvable_address, &TokenAmount::from_atto(10)).unwrap();
assert_eq!(token.allowance(ALICE, resolvable_address).unwrap(), TokenAmount::from_atto(10));
let initializable_address = &bls_address();
assert_eq!(token.allowance(ALICE, initializable_address).unwrap(), TokenAmount::zero());
token
.increase_allowance(ALICE, initializable_address, &TokenAmount::from_atto(10))
.unwrap();
assert_eq!(
token.allowance(ALICE, initializable_address).unwrap(),
TokenAmount::from_atto(10)
);
let uninitializable_address = &actor_address();
assert_eq!(token.allowance(ALICE, uninitializable_address).unwrap(), TokenAmount::zero());
token
.increase_allowance(ALICE, uninitializable_address, &TokenAmount::from_atto(10))
.unwrap_err();
token.assert_invariants().unwrap();
}
#[test]
fn it_sets_allowances() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
token.set_allowance(ALICE, CAROL, &TokenAmount::from_atto(100)).unwrap();
let allowance = token.allowance(ALICE, CAROL).unwrap();
assert_eq!(allowance, TokenAmount::from_atto(100));
token.set_allowance(ALICE, CAROL, &TokenAmount::from_atto(120)).unwrap();
let allowance = token.allowance(ALICE, CAROL).unwrap();
assert_eq!(allowance, TokenAmount::from_atto(120));
token.set_allowance(ALICE, CAROL, &TokenAmount::from_atto(0)).unwrap();
let allowance = token.allowance(ALICE, CAROL).unwrap();
assert_eq!(allowance, TokenAmount::from_atto(0));
token.set_allowance(ALICE, CAROL, &TokenAmount::from_atto(-50)).unwrap_err();
token.assert_invariants().unwrap();
}
#[test]
fn it_allows_delegated_transfer() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
ALICE,
ALICE,
&TokenAmount::from_atto(100),
Default::default(),
Default::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token
.transfer_from(
CAROL,
ALICE,
ALICE,
&TokenAmount::zero(),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
token.increase_allowance(ALICE, CAROL, &TokenAmount::from_atto(100)).unwrap();
let mut hook = token
.transfer_from(
CAROL,
ALICE,
BOB,
&TokenAmount::from_atto(60),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
let intermediate = hook.call(token.runtime).unwrap();
let ret = token.transfer_from_return(intermediate).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(40));
assert_eq!(ret.from_balance, TokenAmount::from_atto(40));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::from_atto(60));
assert_eq!(ret.to_balance, TokenAmount::from_atto(60));
assert_eq!(token.balance_of(CAROL).unwrap(), TokenAmount::zero());
assert_eq!(token.allowance(ALICE, CAROL).unwrap(), TokenAmount::from_atto(40));
assert_eq!(ret.allowance, TokenAmount::from_atto(40));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: CAROL.id().unwrap(),
from: ALICE.id().unwrap(),
to: BOB.id().unwrap(),
amount: TokenAmount::from_atto(60),
operator_data: Default::default(),
token_data: Default::default(),
},
);
let operator_allowance = token.allowance(ALICE, CAROL).unwrap();
assert_eq!(operator_allowance, TokenAmount::from_atto(40));
let mut hook = token
.transfer_from(
CAROL,
ALICE,
CAROL,
&TokenAmount::from_atto(40),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::zero());
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::from_atto(60));
assert_eq!(token.balance_of(CAROL).unwrap(), TokenAmount::from_atto(40));
assert_last_hook_call_eq(
token.runtime,
FRC46TokenReceived {
operator: CAROL.id().unwrap(),
from: ALICE.id().unwrap(),
to: CAROL.id().unwrap(),
amount: TokenAmount::from_atto(40),
operator_data: Default::default(),
token_data: Default::default(),
},
);
assert_eq!(token.allowance(ALICE, CAROL).unwrap(), TokenAmount::zero());
}
#[test]
fn it_allows_delegated_transfer_by_resolvable_pubkey() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let initialised_address = &secp_address();
let _ = token.runtime.initialize_account(initialised_address).unwrap();
token
.transfer_from(
initialised_address,
ALICE,
initialised_address,
&TokenAmount::zero(),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.balance_of(initialised_address).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
token
.transfer_from(
initialised_address,
ALICE,
initialised_address,
&TokenAmount::from_atto(1),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.balance_of(initialised_address).unwrap(), TokenAmount::zero());
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
token.increase_allowance(ALICE, initialised_address, &TokenAmount::from_atto(100)).unwrap();
let mut hook = token
.transfer_from(
initialised_address,
ALICE,
initialised_address,
&TokenAmount::from_atto(1),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(99));
assert_eq!(token.balance_of(initialised_address).unwrap(), TokenAmount::from_atto(1));
assert_eq!(
token.allowance(ALICE, initialised_address).unwrap(),
TokenAmount::from_atto(99)
);
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
}
#[test]
fn it_disallows_delgated_transfer_by_uninitialised_pubkey() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let secp_address = &secp_address();
let err = token
.transfer_from(
secp_address,
ALICE,
ALICE,
&TokenAmount::from_atto(10),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
match err {
TokenError::TokenState(StateError::InsufficientAllowance {
owner,
operator,
allowance,
delta,
}) => {
assert_eq!(*owner, *ALICE);
assert_eq!(*operator, *secp_address);
assert_eq!(allowance, TokenAmount::zero());
assert_eq!(delta, TokenAmount::from_atto(10));
}
e => panic!("Unexpected error {e:?}"),
}
assert_eq!(token.balance_of(secp_address).unwrap(), TokenAmount::zero());
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
assert!(&token.runtime.resolve_id(secp_address).is_err());
let err = token
.transfer_from(
secp_address,
ALICE,
ALICE,
&TokenAmount::zero(),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
match err {
TokenError::TokenState(StateError::InsufficientAllowance {
owner,
operator,
allowance,
delta,
}) => {
assert_eq!(*owner, *ALICE);
assert_eq!(*operator, *secp_address);
assert_eq!(allowance, TokenAmount::zero());
assert_eq!(delta, TokenAmount::zero());
}
e => panic!("Unexpected error {e:?}"),
}
assert_eq!(token.balance_of(secp_address).unwrap(), TokenAmount::zero());
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.total_supply(), TokenAmount::from_atto(100));
assert!(&token.runtime.resolve_id(secp_address).is_err());
}
#[test]
fn it_allows_delegated_burns() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mint_amount = TokenAmount::from_atto(1_000_000);
let approval_amount = TokenAmount::from_atto(600_000);
let burn_amount = TokenAmount::from_atto(600_000);
let mut hook = token
.mint(TOKEN_ACTOR, TREASURY, &mint_amount, Default::default(), Default::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token.increase_allowance(TREASURY, ALICE, &approval_amount).unwrap();
token.burn_from(ALICE, TREASURY, &burn_amount).unwrap();
assert_eq!(token.total_supply(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(400_000));
assert_eq!(token.allowance(TREASURY, ALICE).unwrap(), TokenAmount::zero());
token.burn_from(ALICE, TREASURY, &burn_amount).unwrap_err();
assert_eq!(token.total_supply(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(400_000));
assert_eq!(token.allowance(TREASURY, ALICE).unwrap(), TokenAmount::zero());
let err = token.burn_from(ALICE, TREASURY, &burn_amount).unwrap_err();
match err {
TokenError::TokenState(StateError::InsufficientAllowance {
owner,
operator,
allowance,
delta,
}) => {
assert_eq!(*owner, *TREASURY);
assert_eq!(*operator, *ALICE);
assert_eq!(allowance, TokenAmount::zero());
assert_eq!(delta, burn_amount);
}
e => panic!("unexpected error {e:?}"),
};
assert_eq!(token.total_supply(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(400_000));
assert_eq!(token.allowance(TREASURY, ALICE).unwrap(), TokenAmount::zero());
}
#[test]
fn it_allows_delegated_burns_by_resolvable_pubkeys() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mint_amount = TokenAmount::from_atto(1_000_000);
let approval_amount = TokenAmount::from_atto(600_000);
let burn_amount = TokenAmount::from_atto(600_000);
let secp_address = &secp_address();
let secp_id = &token.runtime.initialize_account(secp_address).unwrap();
let mut hook = token
.mint(TOKEN_ACTOR, TREASURY, &mint_amount, Default::default(), Default::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token.increase_allowance(TREASURY, secp_address, &approval_amount).unwrap();
token.burn_from(secp_address, TREASURY, &burn_amount).unwrap();
assert_eq!(token.total_supply(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(400_000));
assert_eq!(token.allowance(TREASURY, secp_address).unwrap(), TokenAmount::zero());
let err = token.burn_from(secp_address, TREASURY, &burn_amount).unwrap_err();
match err {
TokenError::TokenState(StateError::InsufficientAllowance {
owner,
operator,
allowance,
delta,
}) => {
assert_eq!(*owner, *TREASURY);
assert_eq!(*operator, Address::new_id(*secp_id));
assert_eq!(allowance, TokenAmount::zero());
assert_eq!(delta, burn_amount);
}
e => panic!("unexpected error {e:?}"),
};
assert_eq!(token.total_supply(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(400_000));
assert_eq!(token.allowance(TREASURY, secp_address).unwrap(), TokenAmount::zero());
let res = token.burn_from(secp_address, TREASURY, &TokenAmount::zero());
assert!(res.is_err());
assert_eq!(token.total_supply(), TokenAmount::from_atto(400_000));
assert_eq!(token.balance_of(TREASURY).unwrap(), TokenAmount::from_atto(400_000));
assert_eq!(token.allowance(TREASURY, secp_address).unwrap(), TokenAmount::zero());
}
#[test]
fn it_disallows_delegated_burns_by_uninitialised_pubkeys() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mint_amount = TokenAmount::from_atto(1_000_000);
let burn_amount = TokenAmount::from_atto(600_000);
let secp_address = &secp_address();
let mut hook = token
.mint(TOKEN_ACTOR, TREASURY, &mint_amount, Default::default(), Default::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let err = token.burn_from(secp_address, TREASURY, &burn_amount).unwrap_err();
match err {
TokenError::TokenState(StateError::InsufficientAllowance {
owner,
operator,
allowance,
delta,
}) => {
assert_eq!(*owner, *TREASURY);
assert_eq!(*operator, *secp_address);
assert_eq!(allowance, TokenAmount::zero());
assert_eq!(delta, burn_amount);
}
e => panic!("unexpected error {e:?}"),
};
assert_eq!(token.total_supply(), mint_amount);
assert_eq!(token.balance_of(TREASURY).unwrap(), mint_amount);
assert_eq!(token.allowance(TREASURY, secp_address).unwrap(), TokenAmount::zero());
let err = token.burn_from(secp_address, TREASURY, &TokenAmount::zero()).unwrap_err();
match err {
TokenError::TokenState(StateError::InsufficientAllowance {
owner,
operator,
allowance,
delta,
}) => {
assert_eq!(*owner, *TREASURY);
assert_eq!(*operator, *secp_address);
assert_eq!(allowance, TokenAmount::zero());
assert_eq!(delta, TokenAmount::zero());
}
e => panic!("unexpected error {e:?}"),
};
assert_eq!(token.total_supply(), mint_amount);
assert_eq!(token.balance_of(TREASURY).unwrap(), mint_amount);
assert_eq!(token.allowance(TREASURY, secp_address).unwrap(), TokenAmount::zero());
assert!(&token.runtime.resolve_id(secp_address).is_err());
}
#[test]
fn it_fails_to_transfer_when_insufficient_allowance() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token.increase_allowance(ALICE, CAROL, &TokenAmount::from_atto(40)).unwrap();
token
.transfer_from(
CAROL,
ALICE,
BOB,
&TokenAmount::from_atto(60),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(100));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::zero());
assert_eq!(token.balance_of(CAROL).unwrap(), TokenAmount::zero());
assert_eq!(token.allowance(ALICE, CAROL).unwrap(), TokenAmount::from_atto(40));
token.assert_invariants().unwrap();
}
#[test]
fn it_doesnt_use_allowance_when_insufficent_balance() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(50),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token.increase_allowance(ALICE, BOB, &TokenAmount::from_atto(100)).unwrap();
token
.transfer_from(
BOB,
ALICE,
BOB,
&TokenAmount::from_atto(51),
RawBytes::default(),
RawBytes::default(),
)
.unwrap_err();
token.burn_from(BOB, ALICE, &TokenAmount::from_atto(51)).unwrap_err();
assert_eq!(token.balance_of(ALICE).unwrap(), TokenAmount::from_atto(50));
assert_eq!(token.balance_of(BOB).unwrap(), TokenAmount::zero());
assert_eq!(token.allowance(ALICE, BOB).unwrap(), TokenAmount::from_atto(100));
token.assert_invariants().unwrap();
}
#[test]
fn it_enforces_granularity() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token =
Token::<FakeSyscalls, MemoryBlockstore>::wrap(&helper, 100, &mut token_state);
assert_eq!(token.granularity(), 100);
token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(1),
Default::default(),
Default::default(),
)
.expect_err("minted below granularity");
token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(10),
RawBytes::default(),
RawBytes::default(),
)
.expect_err("minted below granularity");
token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(99),
RawBytes::default(),
RawBytes::default(),
)
.expect_err("minted below granularity");
token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(101),
RawBytes::default(),
RawBytes::default(),
)
.expect_err("minted below granularity");
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(0),
Default::default(),
Default::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(200),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let mut hook = token
.mint(
TOKEN_ACTOR,
ALICE,
&TokenAmount::from_atto(1000),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token.burn(ALICE, &TokenAmount::from_atto(1)).expect_err("burned below granularity");
token.burn(ALICE, &TokenAmount::from_atto(0)).unwrap();
token.burn(ALICE, &TokenAmount::from_atto(100)).unwrap();
token
.transfer(
ALICE,
BOB,
&TokenAmount::from_atto(1),
RawBytes::default(),
RawBytes::default(),
)
.expect_err("transfer delta below granularity");
let mut hook = token
.transfer(
ALICE,
BOB,
&TokenAmount::from_atto(0),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let mut hook = token
.transfer(
ALICE,
BOB,
&TokenAmount::from_atto(100),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
}
#[test]
fn it_doesnt_initialize_accounts_when_default_values_can_be_returned() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let token = new_token(&helper, &mut token_state);
let secp = &secp_address();
let bls = &bls_address();
let allowance = token.allowance(secp, bls).unwrap();
assert_eq!(allowance, TokenAmount::zero());
let allowance = token.allowance(bls, secp).unwrap();
assert_eq!(allowance, TokenAmount::zero());
let allowance = token.allowance(ALICE, bls).unwrap();
assert_eq!(allowance, TokenAmount::zero());
let allowance = token.allowance(bls, ALICE).unwrap();
assert_eq!(allowance, TokenAmount::zero());
let err = &token.runtime.resolve_id(bls).unwrap_err();
if let MessagingError::AddressNotResolved(e) = err {
assert_eq!(e, bls);
} else {
panic!("expected AddressNotResolved error");
}
let err = &token.runtime.resolve_id(secp).unwrap_err();
if let MessagingError::AddressNotResolved(e) = err {
assert_eq!(e, secp);
} else {
panic!("expected AddressNotResolved error");
}
let balance = token.balance_of(secp).unwrap();
assert_eq!(balance, TokenAmount::zero());
let balance = token.balance_of(bls).unwrap();
assert_eq!(balance, TokenAmount::zero());
let err = &token.runtime.resolve_id(bls).unwrap_err();
if let MessagingError::AddressNotResolved(e) = err {
assert_eq!(e, bls);
} else {
panic!("expected AddressNotResolved error");
}
let err = &token.runtime.resolve_id(secp).unwrap_err();
if let MessagingError::AddressNotResolved(e) = err {
assert_eq!(e, secp);
} else {
panic!("expected AddressNotResolved error");
}
}
#[test]
fn test_account_combinations() {
fn setup_accounts<'st>(
operator: &Address,
from: &Address,
allowance: &TokenAmount,
balance: &TokenAmount,
runtime: &'st ActorRuntime<FakeSyscalls, MemoryBlockstore>,
state: &'st mut TokenState,
) -> Token<'st, FakeSyscalls, MemoryBlockstore> {
let mut token = new_token(runtime, state);
if !allowance.is_zero() && from != operator {
token.increase_allowance(from, operator, allowance).unwrap();
}
if !balance.is_zero() {
let mut hook = token
.mint(from, from, balance, Default::default(), Default::default())
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
}
token
}
fn assert_behaviour(
operator: &Address,
from: &Address,
allowance: u32,
balance: u32,
transfer: u32,
behaviour: &str,
) {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state = TokenState::new(&helper).unwrap();
let mut token = setup_accounts(
operator,
from,
&TokenAmount::from_atto(allowance),
&TokenAmount::from_atto(balance),
&helper,
&mut token_state,
);
let assert_error = |err: TokenError, token: Token<FakeSyscalls, MemoryBlockstore>| {
match behaviour {
"ALLOWANCE_ERR" => {
if let TokenError::TokenState(StateError::InsufficientAllowance {
owner: _,
operator: _,
allowance: a,
delta,
}) = err
{
assert_eq!(a, TokenAmount::from_atto(allowance));
assert_eq!(delta, TokenAmount::from_atto(transfer));
} else {
panic!("unexpected error {err:?}");
}
}
"BALANCE_ERR" => {
if let TokenError::TokenState(StateError::InsufficientBalance {
owner,
balance: b,
delta,
}) = err
{
assert_eq!(owner, token.runtime.resolve_id(from).unwrap());
assert_eq!(delta, TokenAmount::from_atto(transfer).neg());
assert_eq!(b, TokenAmount::from_atto(balance));
} else {
panic!("unexpected error {err:?}");
}
}
"ADDRESS_ERR" => {
if let TokenError::Messaging(MessagingError::AddressNotInitialized(addr)) =
err
{
assert!((addr == *operator) || (addr == *from));
} else {
panic!("unexpected error {err:?}");
}
}
_ => panic!("test case not implemented"),
}
};
if token.runtime.same_address(operator, from) {
let res = token.transfer(
from,
operator,
&TokenAmount::from_atto(transfer),
RawBytes::default(),
RawBytes::default(),
);
if behaviour != "OK" {
assert_error(res.unwrap_err(), token);
} else {
let mut hook = res.expect("expect transfer to succeed");
hook.call(token.runtime).expect("receiver hook should succeed");
}
} else {
let res = token.transfer_from(
operator,
from,
operator,
&TokenAmount::from_atto(transfer),
RawBytes::default(),
RawBytes::default(),
);
if behaviour != "OK" {
assert_error(res.unwrap_err(), token);
} else {
let mut hook = res.expect("expect transfer to succeed");
hook.call(token.runtime).expect("receiver hook should succeed");
}
}
}
assert_behaviour(ALICE, BOB, 0, 0, 0, "ALLOWANCE_ERR");
assert_behaviour(ALICE, BOB, 0, 0, 1, "ALLOWANCE_ERR");
assert_behaviour(ALICE, BOB, 0, 1, 0, "ALLOWANCE_ERR");
assert_behaviour(ALICE, BOB, 0, 1, 1, "ALLOWANCE_ERR");
assert_behaviour(ALICE, BOB, 1, 0, 0, "OK");
assert_behaviour(ALICE, BOB, 1, 0, 1, "BALANCE_ERR");
assert_behaviour(ALICE, BOB, 1, 1, 0, "OK");
assert_behaviour(ALICE, BOB, 1, 1, 1, "OK");
assert_behaviour(&secp_address(), BOB, 0, 0, 0, "ALLOWANCE_ERR");
assert_behaviour(&secp_address(), BOB, 0, 0, 1, "ALLOWANCE_ERR");
assert_behaviour(&secp_address(), BOB, 0, 1, 0, "ALLOWANCE_ERR");
assert_behaviour(&secp_address(), BOB, 0, 1, 1, "ALLOWANCE_ERR");
assert_behaviour(BOB, &secp_address(), 0, 0, 0, "ALLOWANCE_ERR");
assert_behaviour(BOB, &secp_address(), 0, 0, 1, "ALLOWANCE_ERR");
assert_behaviour(&bls_address(), &secp_address(), 0, 0, 0, "ALLOWANCE_ERR");
assert_behaviour(&bls_address(), &secp_address(), 0, 0, 1, "ALLOWANCE_ERR");
assert_behaviour(&Address::new_actor(&[1]), &actor_address(), 0, 0, 0, "ALLOWANCE_ERR");
assert_behaviour(&Address::new_actor(&[1]), &actor_address(), 0, 0, 1, "ALLOWANCE_ERR");
assert_behaviour(ALICE, &actor_address(), 0, 0, 0, "ALLOWANCE_ERR");
assert_behaviour(ALICE, &actor_address(), 0, 0, 1, "ALLOWANCE_ERR");
assert_behaviour(&actor_address(), &actor_address(), 0, 0, 0, "ADDRESS_ERR");
assert_behaviour(&actor_address(), &actor_address(), 0, 0, 1, "ADDRESS_ERR");
assert_behaviour(ALICE, ALICE, 0, 0, 0, "OK");
assert_behaviour(ALICE, ALICE, 0, 0, 1, "BALANCE_ERR");
assert_behaviour(ALICE, ALICE, 0, 1, 0, "OK");
assert_behaviour(ALICE, ALICE, 0, 1, 1, "OK");
assert_behaviour(ALICE, ALICE, 1, 0, 0, "OK");
assert_behaviour(ALICE, ALICE, 1, 0, 1, "BALANCE_ERR");
assert_behaviour(ALICE, ALICE, 1, 1, 0, "OK");
assert_behaviour(ALICE, ALICE, 1, 1, 1, "OK");
assert_behaviour(&secp_address(), &secp_address(), 0, 0, 0, "OK");
assert_behaviour(&secp_address(), &secp_address(), 0, 0, 1, "BALANCE_ERR");
assert_behaviour(&bls_address(), &bls_address(), 0, 0, 0, "OK");
assert_behaviour(&bls_address(), &bls_address(), 0, 0, 1, "BALANCE_ERR");
}
#[test]
fn check_invariants_returns_a_state_summary() {
let helper = ActorRuntime::<FakeSyscalls, MemoryBlockstore>::new_test_runtime();
let mut token_state =
Token::<FakeSyscalls, MemoryBlockstore>::create_state(helper.bs()).unwrap();
let mut token = new_token(&helper, &mut token_state);
let mut hook = token
.mint(
ALICE,
ALICE,
&TokenAmount::from_atto(100),
Default::default(),
Default::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
token.increase_allowance(ALICE, CAROL, &TokenAmount::from_atto(100)).unwrap();
let mut hook = token
.transfer_from(
CAROL,
ALICE,
BOB,
&TokenAmount::from_atto(60),
RawBytes::default(),
RawBytes::default(),
)
.unwrap();
token.flush().unwrap();
hook.call(token.runtime).unwrap();
let summary = token.assert_invariants().unwrap();
let balance_map = summary.balance_map.unwrap();
assert_eq!(
balance_map.get(&ALICE.id().unwrap()).unwrap().clone(),
TokenAmount::from_atto(40)
);
assert_eq!(
balance_map.get(&BOB.id().unwrap()).unwrap().clone(),
TokenAmount::from_atto(60)
);
assert_eq!(
summary
.allowance_map
.unwrap()
.get(&ALICE.id().unwrap())
.unwrap()
.get(&CAROL.id().unwrap())
.unwrap()
.clone(),
TokenAmount::from_atto(40)
);
}
}