ab_example_contract_ft/
lib.rs

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