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, PartialOrd, PartialEq, 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        to.get_mut_or_default().balance += value;
105
106        last_action.replace(LastAction::Mint);
107
108        Ok(())
109    }
110
111    #[view]
112    pub fn balance(#[slot] target: &MaybeData<Slot>) -> Balance {
113        target
114            .get()
115            .map_or_else(Balance::default, |slot| slot.balance)
116    }
117
118    #[view]
119    pub fn balance2(#[slot] target: &MaybeData<Slot>, #[output] balance: &mut MaybeData<Balance>) {
120        balance.replace(
121            target
122                .get()
123                .map_or_else(Balance::default, |slot| slot.balance),
124        );
125    }
126
127    #[view]
128    pub fn balance3(#[slot] target: &MaybeData<Slot>, #[output] result: &mut MaybeData<Balance>) {
129        result.replace(
130            target
131                .get()
132                .map_or_else(Balance::default, |slot| slot.balance),
133        );
134    }
135
136    #[view]
137    pub fn var_bytes(#[output] _out: &mut VariableBytes<1024>) {
138        // TODO
139    }
140
141    #[update]
142    pub fn transfer(
143        #[env] env: &mut Env<'_>,
144        #[tmp] last_action: &mut MaybeData<LastAction>,
145        #[slot] (from_address, from): (&Address, &mut MaybeData<Slot>),
146        #[slot] to: &mut MaybeData<Slot>,
147        #[input] &amount: &Balance,
148    ) -> Result<(), ContractError> {
149        if !(env.context() == from_address
150            || env.caller() == from_address
151            || env.caller() == env.own_address())
152        {
153            return Err(ContractError::Forbidden);
154        }
155
156        {
157            let Some(contents) = from.get_mut() else {
158                return Err(ContractError::BadInput);
159            };
160
161            match contents.balance.cmp(&amount) {
162                Ordering::Less => {
163                    return Err(ContractError::BadInput);
164                }
165                Ordering::Equal => {
166                    // All balance is transferred out, remove slot contents
167                    from.remove();
168                }
169                Ordering::Greater => {
170                    contents.balance -= amount;
171                }
172            }
173        }
174
175        to.get_mut_or_default().balance += amount;
176
177        last_action.replace(LastAction::Transfer);
178
179        Ok(())
180    }
181
182    #[update]
183    pub fn last_action(#[tmp] maybe_last_action: &MaybeData<LastAction>) -> LastAction {
184        maybe_last_action.get().copied().unwrap_or_default()
185    }
186}