ab_contract_playground/
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_io_type::variable_bytes::VariableBytes;
8use ab_contracts_macros::contract;
9use ab_contracts_standards::fungible::Fungible;
10use core::cmp::Ordering;
11
12#[derive(Debug, Default, Copy, Clone, TrivialType)]
13#[repr(u8)]
14pub enum LastAction {
15    #[default]
16    None,
17    Mint,
18    Transfer,
19}
20
21#[derive(Debug, Default, Copy, Clone, TrivialType)]
22#[repr(C)]
23pub struct Slot {
24    pub balance: Balance,
25}
26
27#[derive(Debug, Copy, Clone, TrivialType)]
28#[repr(C)]
29pub struct Playground {
30    pub total_supply: Balance,
31    pub owner: Address,
32}
33
34#[contract]
35impl Fungible for Playground {
36    #[update]
37    fn transfer(
38        #[env] env: &mut Env<'_>,
39        #[input] from: &Address,
40        #[input] to: &Address,
41        #[input] amount: &Balance,
42    ) -> Result<(), ContractError> {
43        if !(env.context() == from || env.caller() == from || env.caller() == env.own_address()) {
44            return Err(ContractError::Forbidden);
45        }
46
47        env.playground_transfer(MethodContext::Replace, env.own_address(), from, to, amount)
48    }
49
50    #[view]
51    fn balance(#[env] env: &Env<'_>, #[input] address: &Address) -> Result<Balance, ContractError> {
52        env.playground_balance(env.own_address(), address)
53    }
54}
55
56#[contract]
57impl Playground {
58    #[init]
59    pub fn new(
60        #[slot] (owner_addr, owner): (&Address, &mut MaybeData<Slot>),
61        #[input] total_supply: &Balance,
62    ) -> Self {
63        owner.replace(Slot {
64            balance: *total_supply,
65        });
66        Self {
67            total_supply: *total_supply,
68            owner: *owner_addr,
69        }
70    }
71
72    #[init]
73    pub fn new_result(
74        #[slot] (owner_addr, owner): (&Address, &mut MaybeData<Slot>),
75        #[input] total_supply: &Balance,
76        #[output] result: &mut MaybeData<Self>,
77    ) {
78        owner.replace(Slot {
79            balance: *total_supply,
80        });
81        result.replace(Self {
82            total_supply: *total_supply,
83            owner: *owner_addr,
84        });
85    }
86
87    #[update]
88    pub fn mint(
89        &mut self,
90        #[env] env: &mut Env<'_>,
91        #[tmp] last_action: &mut MaybeData<LastAction>,
92        #[slot] to: &mut MaybeData<Slot>,
93        #[input] &value: &Balance,
94    ) -> Result<(), ContractError> {
95        if env.context() != self.owner && env.caller() != self.owner {
96            return Err(ContractError::Forbidden);
97        }
98
99        if Balance::MAX - value > self.total_supply {
100            return Err(ContractError::BadInput);
101        }
102
103        self.total_supply += value;
104
105        match to.get_mut() {
106            Some(contents) => {
107                contents.balance += value;
108            }
109            None => {
110                to.replace(Slot { balance: value });
111            }
112        }
113
114        last_action.replace(LastAction::Mint);
115
116        Ok(())
117    }
118
119    #[view]
120    pub fn balance(#[slot] target: &MaybeData<Slot>) -> Balance {
121        target
122            .get()
123            .map_or_else(Balance::default, |slot| slot.balance)
124    }
125
126    #[view]
127    pub fn balance2(#[slot] target: &MaybeData<Slot>, #[output] balance: &mut MaybeData<Balance>) {
128        balance.replace(
129            target
130                .get()
131                .map_or_else(Balance::default, |slot| slot.balance),
132        );
133    }
134
135    #[view]
136    pub fn balance3(#[slot] target: &MaybeData<Slot>, #[output] result: &mut MaybeData<Balance>) {
137        result.replace(
138            target
139                .get()
140                .map_or_else(Balance::default, |slot| slot.balance),
141        );
142    }
143
144    #[view]
145    pub fn var_bytes(#[output] _out: &mut VariableBytes<1024>) {
146        // TODO
147    }
148
149    #[update]
150    pub fn transfer(
151        #[env] env: &mut Env<'_>,
152        #[tmp] last_action: &mut MaybeData<LastAction>,
153        #[slot] (from_address, from): (&Address, &mut MaybeData<Slot>),
154        #[slot] to: &mut MaybeData<Slot>,
155        #[input] &amount: &Balance,
156    ) -> Result<(), ContractError> {
157        if !(env.context() == from_address
158            || env.caller() == from_address
159            || env.caller() == env.own_address())
160        {
161            return Err(ContractError::Forbidden);
162        }
163
164        {
165            let Some(contents) = from.get_mut() else {
166                return Err(ContractError::BadInput);
167            };
168
169            match contents.balance.cmp(&amount) {
170                Ordering::Less => {
171                    return Err(ContractError::BadInput);
172                }
173                Ordering::Equal => {
174                    // All balance is transferred out, remove slot contents
175                    from.remove();
176                }
177                Ordering::Greater => {
178                    contents.balance -= amount;
179                }
180            }
181        }
182
183        match to.get_mut() {
184            Some(contents) => {
185                contents.balance += amount;
186            }
187            None => {
188                to.replace(Slot { balance: amount });
189            }
190        }
191
192        last_action.replace(LastAction::Transfer);
193
194        Ok(())
195    }
196
197    #[update]
198    pub fn last_action(#[tmp] maybe_last_action: &MaybeData<LastAction>) -> LastAction {
199        maybe_last_action.get().copied().unwrap_or_default()
200    }
201}