ab_system_contract_native_token/
lib.rs

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