ab_example_contract_ft/
lib.rs

1#![no_std]
2
3use ab_contracts_common::ContractError;
4use ab_contracts_common::env::{Env, MethodContext};
5use ab_contracts_macros::contract;
6use ab_contracts_standards::fungible::Fungible;
7use ab_core_primitives::address::Address;
8use ab_core_primitives::balance::Balance;
9use ab_io_type::maybe_data::MaybeData;
10use ab_io_type::trivial_type::TrivialType;
11use core::cmp::Ordering;
12
13#[derive(Debug, Default, Copy, Clone, TrivialType)]
14#[repr(C)]
15pub struct Slot {
16    pub balance: Balance,
17}
18
19#[derive(Debug, Copy, Clone, TrivialType)]
20#[repr(C)]
21pub struct ExampleFt {
22    pub total_supply: Balance,
23    pub owner: Address,
24}
25
26#[contract]
27impl Fungible for ExampleFt {
28    #[update]
29    fn transfer(
30        #[env] env: &mut Env<'_>,
31        #[input] from: &Address,
32        #[input] to: &Address,
33        #[input] amount: &Balance,
34    ) -> Result<(), ContractError> {
35        if !(env.context() == from || env.caller() == from || env.caller() == env.own_address()) {
36            return Err(ContractError::Forbidden);
37        }
38
39        env.example_ft_transfer(MethodContext::Replace, env.own_address(), from, to, amount)
40    }
41
42    #[view]
43    fn balance(#[env] env: &Env<'_>, #[input] address: &Address) -> Result<Balance, ContractError> {
44        env.example_ft_balance(env.own_address(), address)
45    }
46}
47
48#[contract]
49impl ExampleFt {
50    #[init]
51    pub fn new(
52        #[slot] (owner_addr, owner): (&Address, &mut MaybeData<Slot>),
53        #[input] total_supply: &Balance,
54    ) -> Self {
55        owner.replace(Slot {
56            balance: *total_supply,
57        });
58        Self {
59            total_supply: *total_supply,
60            owner: *owner_addr,
61        }
62    }
63
64    #[update]
65    pub fn mint(
66        &mut self,
67        #[env] env: &mut Env<'_>,
68        #[slot] to: &mut MaybeData<Slot>,
69        #[input] &value: &Balance,
70    ) -> Result<(), ContractError> {
71        if env.context() != self.owner && env.caller() != self.owner {
72            return Err(ContractError::Forbidden);
73        }
74
75        if Balance::MAX - value > self.total_supply {
76            return Err(ContractError::BadInput);
77        }
78
79        self.total_supply += value;
80        to.get_mut_or_default().balance += value;
81
82        Ok(())
83    }
84
85    #[view]
86    pub fn balance(#[slot] target: &MaybeData<Slot>) -> Balance {
87        target
88            .get()
89            .map_or_else(Balance::default, |slot| slot.balance)
90    }
91
92    #[update]
93    pub fn transfer(
94        #[env] env: &mut Env<'_>,
95        #[slot] (from_address, from): (&Address, &mut MaybeData<Slot>),
96        #[slot] to: &mut MaybeData<Slot>,
97        #[input] &amount: &Balance,
98    ) -> Result<(), ContractError> {
99        if !(env.context() == from_address
100            || env.caller() == from_address
101            || env.caller() == env.own_address())
102        {
103            return Err(ContractError::Forbidden);
104        }
105
106        {
107            let Some(contents) = from.get_mut() else {
108                return Err(ContractError::BadInput);
109            };
110
111            match contents.balance.cmp(&amount) {
112                Ordering::Less => {
113                    return Err(ContractError::BadInput);
114                }
115                Ordering::Equal => {
116                    // All balance is transferred out, remove slot contents
117                    from.remove();
118                }
119                Ordering::Greater => {
120                    contents.balance -= amount;
121                }
122            }
123        }
124
125        to.get_mut_or_default().balance += amount;
126
127        Ok(())
128    }
129}