#![allow(missing_docs)]
#[ixc::handler(Bank)]
pub mod bank {
use mockall::automock;
use ixc::*;
use ixc_core::error::unimplemented_ok;
use ixc_core::handler::Service;
#[derive(Resources)]
pub struct Bank {
#[state(prefix = 1, key(address, denom), value(amount))]
pub(crate) balances: AccumulatorMap<(AccountID, Str)>,
#[state(prefix = 2, key(denom), value(total))]
pub(crate) supply: AccumulatorMap<Str>,
#[state(prefix = 3)]
super_admin: Item<AccountID>,
#[state(prefix = 4)]
global_send_hook: Item<AccountID>,
#[state(prefix = 5)]
denom_admins: Map<Str, AccountID>,
#[state(prefix = 6)]
denom_send_hooks: Map<Str, AccountID>,
#[state(prefix = 6)]
denom_burn_hooks: Map<Str, AccountID>,
}
#[derive(SchemaValue, Clone)]
#[sealed]
pub struct Coin<'a> {
pub denom: &'a str,
pub amount: u128,
}
#[handler_api]
pub trait BankAPI {
fn get_balance(&self, ctx: &Context, account: AccountID, denom: &str) -> Result<u128>;
fn send<'a>(&self, ctx: &'a mut Context, to: AccountID, amount: &[Coin<'a>], evt: EventBus<EventSend<'_>>) -> Result<()>;
fn mint(&self, ctx: &mut Context, to: AccountID, denom: &str, amount: u128, evt: EventBus<EventMint<'_>>) -> Result<()>;
fn burn(&self, ctx: &mut Context, from: AccountID, denom: &str, amount: u128, evt: EventBus<EventBurn<'_>>) -> Result<()>;
}
#[handler_api]
#[automock]
pub trait SendHook {
fn on_send<'a>(&self, ctx: &mut Context<'a>, from: AccountID, to: AccountID, denom: &str, amount: u128) -> Result<()>;
}
#[handler_api]
#[automock]
pub trait BurnHook {
fn on_burn<'a>(&self, ctx: &mut Context<'a>, from: AccountID, denom: &str, amount: u128) -> Result<()>;
}
#[handler_api]
#[automock]
pub trait ReceiveHook {
fn on_receive<'a>(&self, ctx: &mut Context<'a>, from: AccountID, denom: &str, amount: u128) -> Result<()>;
}
#[derive(SchemaValue)]
#[non_exhaustive]
pub struct EventSend<'a> {
pub from: AccountID,
pub to: AccountID,
pub coin: Coin<'a>,
}
#[derive(SchemaValue)]
#[non_exhaustive]
pub struct EventMint<'a> {
pub to: AccountID,
pub coin: Coin<'a>,
}
#[derive(SchemaValue)]
#[non_exhaustive]
pub struct EventBurn<'a> {
pub from: AccountID,
pub coin: Coin<'a>,
}
impl Bank {
#[on_create]
pub fn create(&self, ctx: &mut Context) -> Result<()> {
self.super_admin.set(ctx, ctx.caller())?;
Ok(())
}
#[publish]
pub fn create_denom(&self, ctx: &mut Context, denom: &str, admin: AccountID) -> Result<()> {
ensure!(self.super_admin.get(ctx)? == ctx.caller(), "not authorized");
self.denom_admins.set(ctx, denom, admin)?;
Ok(())
}
#[publish]
pub fn set_global_send_hook(&self, ctx: &mut Context, hook: AccountID) -> Result<()> {
ensure!(self.super_admin.get(ctx)? == ctx.caller(), "not authorized");
self.global_send_hook.set(ctx, hook)?;
Ok(())
}
}
#[publish]
impl BankAPI for Bank {
fn get_balance(&self, ctx: &Context, account: AccountID, denom: &str) -> Result<u128> {
let amount = self.balances.get(ctx, (account, denom))?;
Ok(amount)
}
fn send<'a>(&self, ctx: &'a mut Context, to: AccountID, amount: &[Coin<'a>], mut evt: EventBus<EventSend<'a>>) -> Result<()> {
let global_send = self.global_send_hook.get(ctx)?;
for coin in amount {
if !global_send.is_empty() {
let hook_client = <dyn SendHook>::new_client(global_send);
hook_client.on_send(ctx, ctx.caller(), to, coin.denom, coin.amount)?;
}
if let Some(hook) = self.denom_send_hooks.get(ctx, coin.denom)? {
let hook_client = <dyn SendHook>::new_client(hook);
hook_client.on_send(ctx, ctx.caller(), to, coin.denom, coin.amount)?;
}
let from = ctx.caller();
let receive_hook = <dyn ReceiveHook>::new_client(to);
unimplemented_ok(receive_hook.on_receive(ctx, from, coin.denom, coin.amount))?;
self.balances.safe_sub(ctx, (from, coin.denom), coin.amount)?;
self.balances.add(ctx, (to, coin.denom), coin.amount)?;
evt.emit(ctx, &EventSend {
from,
to,
coin: coin.clone(),
})?;
}
Ok(())
}
fn mint<'a>(&self, ctx: &mut Context, to: AccountID, denom: &'a str, amount: u128, mut evt: EventBus<EventMint<'a>>) -> Result<()> {
let admin = self.denom_admins.get(ctx, denom)?
.ok_or(error!("denom not defined"))?;
ensure!(admin == ctx.caller(), "not authorized");
self.supply.add(ctx, denom, amount)?;
self.balances.add(ctx, (to, denom), amount)?;
evt.emit(ctx, &EventMint {
to,
coin: Coin { denom, amount },
})?;
Ok(())
}
fn burn(&self, ctx: &mut Context, from: AccountID, denom: &str, amount: u128, mut evt: EventBus<EventBurn<'_>>) -> Result<()> {
todo!()
}
}
}
#[cfg(test)]
mod tests {
use ixc_core::account_api::ROOT_ACCOUNT;
use ixc_core::handler::{Client, Service};
use ixc_core::routing::{find_route, Router};
use ixc_message_api::code::ErrorCode;
use ixc_message_api::handler::{Allocator, HostBackend, RawHandler};
use ixc_message_api::packet::MessagePacket;
use super::bank::*;
use ixc_testing::*;
#[test]
fn test() {
let mut app = TestApp::default();
app.register_handler::<Bank>().unwrap();
let mut root = app.client_context_for(ROOT_ACCOUNT);
let bank_client = create_account::<Bank>(&mut root, BankCreate {}).unwrap();
let mut mock_global_send_hook = MockSendHook::new();
mock_global_send_hook.expect_on_send().times(1).returning(|_, _, _, _, _| Ok(()));
let mut mock = MockHandler::new();
mock.add_handler::<dyn SendHook>(Box::new(mock_global_send_hook));
let mock_id = app.add_mock(&mut root, mock).unwrap();
bank_client.set_global_send_hook(&mut root, mock_id).unwrap();
let mut alice = app.new_client_context().unwrap();
let alice_id = alice.self_account_id();
bank_client.create_denom(&mut root, "foo", alice_id).unwrap();
bank_client.mint(&mut alice, alice_id, "foo", 1000).unwrap();
let alice_balance = bank_client.get_balance(&alice, alice_id, "foo").unwrap();
assert_eq!(alice_balance, 1000);
let mut bob = app.new_client_context().unwrap();
bank_client.send(&mut alice, bob.self_account_id(), &[Coin { denom: "foo", amount: 100 }]).unwrap();
let alice_balance = bank_client.get_balance(&alice, alice.self_account_id(), "foo").unwrap();
assert_eq!(alice_balance, 900);
let bob_balance = bank_client.get_balance(&bob, bob.self_account_id(), "foo").unwrap();
assert_eq!(bob_balance, 100);
app.exec_in(&bank_client, |bank, ctx| {
let alice_balance = bank.balances.get(ctx, (alice_id, "foo")).unwrap();
assert_eq!(alice_balance, 900);
let foo_supply = bank.supply.get(ctx, "foo").unwrap();
assert_eq!(foo_supply, 1000);
})
}
}
fn main() {}