stellar_interchain_token/
contract.rs

1use soroban_token_sdk::event::Events as TokenEvents;
2use soroban_token_sdk::metadata::TokenMetadata;
3use soroban_token_sdk::TokenUtils;
4use stellar_axelar_std::events::Event;
5use stellar_axelar_std::interfaces::OwnableInterface;
6use stellar_axelar_std::token::{StellarAssetInterface, TokenInterface};
7use stellar_axelar_std::{
8    assert_with_error, contract, contractimpl, ensure, interfaces, only_owner, soroban_sdk, token,
9    Address, BytesN, Env, String, Upgradable,
10};
11
12use crate::error::ContractError;
13use crate::event::{MinterAddedEvent, MinterRemovedEvent};
14use crate::interface::InterchainTokenInterface;
15use crate::storage::{self, AllowanceDataKey, AllowanceValue};
16
17#[contract]
18#[derive(Upgradable)]
19pub struct InterchainToken;
20
21#[contractimpl]
22impl InterchainToken {
23    pub fn __constructor(
24        env: Env,
25        owner: Address,
26        minter: Option<Address>,
27        token_id: BytesN<32>,
28        token_metadata: TokenMetadata,
29    ) {
30        interfaces::set_owner(&env, &owner);
31
32        Self::write_metadata(&env, token_metadata);
33
34        storage::set_token_id(&env, &token_id);
35
36        if let Some(minter) = minter {
37            storage::set_minter_status(&env, minter.clone());
38
39            MinterAddedEvent { minter }.emit(&env);
40        }
41    }
42}
43
44#[contractimpl]
45impl OwnableInterface for InterchainToken {
46    #[allow_during_migration]
47    fn owner(env: &Env) -> Address {
48        interfaces::owner(env)
49    }
50
51    fn transfer_ownership(env: &Env, new_owner: Address) {
52        let old_owner = Self::owner(env);
53
54        interfaces::transfer_ownership::<Self>(env, new_owner.clone());
55
56        TokenEvents::new(env).set_admin(old_owner, new_owner);
57    }
58}
59
60// Note: Some methods below are intentionally unimplemented as they are not supported by this token
61#[contractimpl]
62impl StellarAssetInterface for InterchainToken {
63    fn set_admin(env: Env, admin: Address) {
64        Self::transfer_ownership(&env, admin);
65    }
66
67    fn admin(env: Env) -> Address {
68        Self::owner(&env)
69    }
70
71    fn set_authorized(_env: Env, _id: Address, _authorize: bool) {
72        unimplemented!()
73    }
74
75    fn authorized(_env: Env, _id: Address) -> bool {
76        unimplemented!()
77    }
78
79    fn mint(env: Env, to: Address, amount: i128) {
80        let owner = Self::owner(&env);
81        owner.require_auth();
82
83        Self::validate_amount(&env, amount);
84
85        Self::receive_balance(&env, to.clone(), amount);
86
87        TokenUtils::new(&env).events().mint(owner, to, amount);
88    }
89
90    fn clawback(_env: Env, _from: Address, _amount: i128) {
91        unimplemented!()
92    }
93}
94
95#[contractimpl]
96impl InterchainTokenInterface for InterchainToken {
97    fn token_id(env: &Env) -> BytesN<32> {
98        storage::token_id(env)
99    }
100
101    fn is_minter(env: &Env, minter: Address) -> bool {
102        storage::is_minter(env, minter)
103    }
104
105    fn mint_from(
106        env: &Env,
107        minter: Address,
108        to: Address,
109        amount: i128,
110    ) -> Result<(), ContractError> {
111        minter.require_auth();
112
113        ensure!(
114            Self::is_minter(env, minter.clone()),
115            ContractError::NotMinter
116        );
117
118        Self::validate_amount(env, amount);
119
120        Self::receive_balance(env, to.clone(), amount);
121
122        TokenUtils::new(env).events().mint(minter, to, amount);
123
124        Ok(())
125    }
126
127    #[only_owner]
128    fn add_minter(env: &Env, minter: Address) {
129        assert_with_error!(
130            env,
131            !Self::is_minter(env, minter.clone()),
132            ContractError::MinterAlreadyExists
133        );
134
135        storage::set_minter_status(env, minter.clone());
136
137        MinterAddedEvent { minter }.emit(env);
138    }
139
140    #[only_owner]
141    fn remove_minter(env: &Env, minter: Address) {
142        assert_with_error!(
143            env,
144            Self::is_minter(env, minter.clone()),
145            ContractError::NotMinter
146        );
147
148        storage::remove_minter_status(env, minter.clone());
149
150        MinterRemovedEvent { minter }.emit(env);
151    }
152}
153
154#[contractimpl]
155impl token::Interface for InterchainToken {
156    fn allowance(env: Env, from: Address, spender: Address) -> i128 {
157        Self::read_allowance(&env, from, spender).amount
158    }
159
160    fn approve(env: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) {
161        from.require_auth();
162
163        Self::validate_amount(&env, amount);
164
165        Self::write_allowance(
166            &env,
167            from.clone(),
168            spender.clone(),
169            amount,
170            expiration_ledger,
171        );
172
173        TokenUtils::new(&env)
174            .events()
175            .approve(from, spender, amount, expiration_ledger);
176    }
177
178    fn balance(env: Env, id: Address) -> i128 {
179        storage::try_balance(&env, id).unwrap_or_default()
180    }
181
182    fn transfer(env: Env, from: Address, to: Address, amount: i128) {
183        from.require_auth();
184
185        Self::validate_amount(&env, amount);
186        Self::spend_balance(&env, from.clone(), amount);
187        Self::receive_balance(&env, to.clone(), amount);
188
189        TokenUtils::new(&env).events().transfer(from, to, amount);
190    }
191
192    fn transfer_from(env: Env, spender: Address, from: Address, to: Address, amount: i128) {
193        spender.require_auth();
194
195        Self::validate_amount(&env, amount);
196        Self::spend_allowance(&env, from.clone(), spender, amount);
197        Self::spend_balance(&env, from.clone(), amount);
198        Self::receive_balance(&env, to.clone(), amount);
199
200        TokenUtils::new(&env).events().transfer(from, to, amount);
201    }
202
203    fn burn(env: Env, from: Address, amount: i128) {
204        from.require_auth();
205
206        Self::validate_amount(&env, amount);
207        Self::spend_balance(&env, from.clone(), amount);
208
209        TokenUtils::new(&env).events().burn(from, amount);
210    }
211
212    fn burn_from(env: Env, spender: Address, from: Address, amount: i128) {
213        spender.require_auth();
214
215        Self::validate_amount(&env, amount);
216        Self::spend_allowance(&env, from.clone(), spender, amount);
217        Self::spend_balance(&env, from.clone(), amount);
218
219        TokenUtils::new(&env).events().burn(from, amount);
220    }
221
222    fn decimals(env: Env) -> u32 {
223        TokenUtils::new(&env).metadata().get_metadata().decimal
224    }
225
226    fn name(env: Env) -> String {
227        TokenUtils::new(&env).metadata().get_metadata().name
228    }
229
230    fn symbol(env: Env) -> String {
231        TokenUtils::new(&env).metadata().get_metadata().symbol
232    }
233}
234
235impl InterchainToken {
236    fn validate_amount(env: &Env, amount: i128) {
237        assert_with_error!(env, amount >= 0, ContractError::InvalidAmount);
238    }
239
240    fn read_allowance(env: &Env, from: Address, spender: Address) -> AllowanceValue {
241        let key = AllowanceDataKey { from, spender };
242        storage::try_allowance(env, key).map_or(
243            AllowanceValue {
244                amount: 0,
245                expiration_ledger: 0,
246            },
247            |allowance| {
248                if allowance.expiration_ledger < env.ledger().sequence() {
249                    AllowanceValue {
250                        amount: 0,
251                        expiration_ledger: allowance.expiration_ledger,
252                    }
253                } else {
254                    allowance
255                }
256            },
257        )
258    }
259
260    fn write_allowance(
261        env: &Env,
262        from: Address,
263        spender: Address,
264        amount: i128,
265        expiration_ledger: u32,
266    ) {
267        let allowance = AllowanceValue {
268            amount,
269            expiration_ledger,
270        };
271
272        assert_with_error!(
273            env,
274            !(amount > 0 && expiration_ledger < env.ledger().sequence()),
275            ContractError::InvalidExpirationLedger
276        );
277
278        let key = AllowanceDataKey { from, spender };
279        storage::set_allowance(env, key.clone(), &allowance);
280
281        if amount > 0 {
282            let live_for = expiration_ledger
283                .checked_sub(env.ledger().sequence())
284                .unwrap();
285
286            storage::extend_allowance_ttl(env, key, live_for, live_for);
287        }
288    }
289
290    fn spend_allowance(env: &Env, from: Address, spender: Address, amount: i128) {
291        let allowance = Self::read_allowance(env, from.clone(), spender.clone());
292
293        assert_with_error!(
294            env,
295            allowance.amount >= amount,
296            ContractError::InsufficientAllowance
297        );
298
299        if amount > 0 {
300            Self::write_allowance(
301                env,
302                from,
303                spender,
304                allowance
305                    .amount
306                    .checked_sub(amount)
307                    .expect("insufficient allowance"),
308                allowance.expiration_ledger,
309            );
310        }
311    }
312
313    fn receive_balance(env: &Env, addr: Address, amount: i128) {
314        let current_balance = storage::try_balance(env, addr.clone()).unwrap_or_default();
315
316        storage::set_balance(env, addr, &(current_balance + amount));
317    }
318
319    fn spend_balance(env: &Env, addr: Address, amount: i128) {
320        let balance = storage::try_balance(env, addr.clone()).unwrap_or_default();
321
322        assert_with_error!(env, balance >= amount, ContractError::InsufficientBalance);
323
324        storage::set_balance(env, addr, &(balance - amount));
325    }
326
327    fn write_metadata(env: &Env, metadata: TokenMetadata) {
328        TokenUtils::new(env).metadata().set_metadata(&metadata);
329    }
330}