ab_example_contract_ft/
lib.rs1#![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
79 match to.get_mut() {
80 Some(contents) => {
81 contents.balance += value;
82 }
83 None => {
84 to.replace(Slot { balance: value });
85 }
86 }
87
88 Ok(())
89 }
90
91 #[view]
92 pub fn balance(#[slot] target: &MaybeData<Slot>) -> Balance {
93 target
94 .get()
95 .map_or_else(Balance::default, |slot| slot.balance)
96 }
97
98 #[update]
99 pub fn transfer(
100 #[env] env: &mut Env<'_>,
101 #[slot] (from_address, from): (&Address, &mut MaybeData<Slot>),
102 #[slot] to: &mut MaybeData<Slot>,
103 #[input] &amount: &Balance,
104 ) -> Result<(), ContractError> {
105 if !(env.context() == from_address
106 || env.caller() == from_address
107 || env.caller() == env.own_address())
108 {
109 return Err(ContractError::Forbidden);
110 }
111
112 {
113 let Some(contents) = from.get_mut() else {
114 return Err(ContractError::BadInput);
115 };
116
117 match contents.balance.cmp(&amount) {
118 Ordering::Less => {
119 return Err(ContractError::BadInput);
120 }
121 Ordering::Equal => {
122 from.remove();
124 }
125 Ordering::Greater => {
126 contents.balance -= amount;
127 }
128 }
129 }
130
131 match to.get_mut() {
132 Some(contents) => {
133 contents.balance += amount;
134 }
135 None => {
136 to.replace(Slot { balance: amount });
137 }
138 }
139
140 Ok(())
141 }
142}