ab_system_contract_native_token/
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 NativeToken {}
20
21#[contract]
22impl Fungible for NativeToken {
23    #[update]
24    fn transfer(
25        #[env] env: &mut Env<'_>,
26        #[input] from: &Address,
27        #[input] to: &Address,
28        #[input] amount: &Balance,
29    ) -> Result<(), ContractError> {
30        if !(env.context() == from
31            || env.caller() == from
32            || env.caller() == env.own_address()
33            || env.caller() == Address::NULL)
34        {
35            return Err(ContractError::Forbidden);
36        }
37
38        env.native_token_transfer(MethodContext::Replace, env.own_address(), from, to, amount)
39    }
40
41    #[view]
42    fn balance(#[env] env: &Env<'_>, #[input] address: &Address) -> Result<Balance, ContractError> {
43        env.native_token_balance(env.own_address(), address)
44    }
45}
46
47#[contract]
48impl NativeToken {
49    /// Initialize native token on a shard with max issuance allowed by this shard.
50    ///
51    /// Block rewards will be implemented using transfers from native token's balance.
52    #[update]
53    pub fn initialize(
54        #[env] env: &mut Env<'_>,
55        #[slot] (own_address, own_balance): (&Address, &mut MaybeData<Slot>),
56        #[input] &max_issuance: &Balance,
57    ) -> Result<Self, ContractError> {
58        // Only execution environment can make a direct call here
59        if env.caller() != Address::NULL {
60            return Err(ContractError::Forbidden);
61        }
62
63        if own_address != env.own_address() {
64            return Err(ContractError::BadInput);
65        }
66
67        if own_balance.get().is_some() {
68            return Err(ContractError::Conflict);
69        }
70
71        own_balance.replace(Slot {
72            balance: max_issuance,
73        });
74
75        Ok(Self {})
76    }
77
78    #[view]
79    pub fn balance(#[slot] target: &MaybeData<Slot>) -> Balance {
80        target
81            .get()
82            .map_or_else(Balance::default, |slot| slot.balance)
83    }
84
85    #[update]
86    pub fn transfer(
87        #[env] env: &mut Env<'_>,
88        #[slot] (from_address, from): (&Address, &mut MaybeData<Slot>),
89        #[slot] to: &mut MaybeData<Slot>,
90        #[input] &amount: &Balance,
91    ) -> Result<(), ContractError> {
92        if !(env.context() == from_address
93            || env.caller() == from_address
94            || env.caller() == env.own_address()
95            || env.caller() == Address::NULL)
96        {
97            return Err(ContractError::Forbidden);
98        }
99
100        {
101            let Some(contents) = from.get_mut() else {
102                return Err(ContractError::BadInput);
103            };
104
105            match contents.balance.cmp(&amount) {
106                Ordering::Less => {
107                    return Err(ContractError::BadInput);
108                }
109                Ordering::Equal => {
110                    // All balance is transferred out, remove slot contents
111                    from.remove();
112                }
113                Ordering::Greater => {
114                    contents.balance -= amount;
115                }
116            }
117        }
118
119        to.get_mut_or_default().balance += amount;
120
121        Ok(())
122    }
123}