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#[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}