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