Skip to content

Commit 7a5e105

Browse files
committed
refactor(interchain-token): use contractstorage
1 parent 71a8cae commit 7a5e105

File tree

8 files changed

+299
-145
lines changed

8 files changed

+299
-145
lines changed

contracts/stellar-interchain-token/src/contract.rs

Lines changed: 32 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use soroban_token_sdk::metadata::TokenMetadata;
55
use soroban_token_sdk::TokenUtils;
66
use stellar_axelar_std::events::Event;
77
use stellar_axelar_std::interfaces::OwnableInterface;
8-
use stellar_axelar_std::ttl::{extend_instance_ttl, extend_persistent_ttl};
8+
use stellar_axelar_std::ttl::extend_instance_ttl;
99
use stellar_axelar_std::{ensure, interfaces, Upgradable};
1010

1111
use crate::error::ContractError;
1212
use crate::event::{MinterAddedEvent, MinterRemovedEvent};
1313
use crate::interface::InterchainTokenInterface;
14-
use crate::storage_types::{AllowanceDataKey, AllowanceValue, DataKey};
14+
use crate::storage::{self, AllowanceDataKey, AllowanceValue};
1515

1616
#[contract]
1717
#[derive(Upgradable)]
@@ -30,12 +30,10 @@ impl InterchainToken {
3030

3131
Self::write_metadata(&env, token_metadata);
3232

33-
env.storage().instance().set(&DataKey::TokenId, &token_id);
33+
storage::set_token_id(&env, &token_id);
3434

3535
if let Some(minter) = minter {
36-
env.storage()
37-
.instance()
38-
.set(&DataKey::Minter(minter.clone()), &());
36+
storage::set_minter_status(&env, minter.clone());
3937

4038
MinterAddedEvent { minter }.emit(&env);
4139
}
@@ -96,14 +94,11 @@ impl StellarAssetInterface for InterchainToken {
9694
#[contractimpl]
9795
impl InterchainTokenInterface for InterchainToken {
9896
fn token_id(env: &Env) -> BytesN<32> {
99-
env.storage()
100-
.instance()
101-
.get(&DataKey::TokenId)
102-
.expect("token id not found")
97+
storage::token_id(env)
10398
}
10499

105100
fn is_minter(env: &Env, minter: Address) -> bool {
106-
env.storage().instance().has(&DataKey::Minter(minter))
101+
storage::is_minter(env, minter)
107102
}
108103

109104
fn mint_from(
@@ -133,23 +128,15 @@ impl InterchainTokenInterface for InterchainToken {
133128
fn add_minter(env: &Env, minter: Address) {
134129
Self::owner(env).require_auth();
135130

136-
env.storage()
137-
.instance()
138-
.set(&DataKey::Minter(minter.clone()), &());
139-
140-
extend_instance_ttl(env);
131+
storage::set_minter_status(env, minter.clone());
141132

142133
MinterAddedEvent { minter }.emit(env);
143134
}
144135

145136
fn remove_minter(env: &Env, minter: Address) {
146137
Self::owner(env).require_auth();
147138

148-
env.storage()
149-
.instance()
150-
.remove(&DataKey::Minter(minter.clone()));
151-
152-
extend_instance_ttl(env);
139+
storage::remove_minter_status(env, minter.clone());
153140

154141
MinterRemovedEvent { minter }.emit(env);
155142
}
@@ -183,8 +170,7 @@ impl token::Interface for InterchainToken {
183170
}
184171

185172
fn balance(env: Env, id: Address) -> i128 {
186-
extend_instance_ttl(&env);
187-
Self::read_balance(&env, id)
173+
storage::try_balance(&env, id).unwrap_or_default()
188174
}
189175

190176
fn transfer(env: Env, from: Address, to: Address, amount: i128) {
@@ -257,26 +243,23 @@ impl InterchainToken {
257243
}
258244

259245
fn read_allowance(env: &Env, from: Address, spender: Address) -> AllowanceValue {
260-
let key = DataKey::Allowance(AllowanceDataKey { from, spender });
261-
env.storage()
262-
.temporary()
263-
.get::<_, AllowanceValue>(&key)
264-
.map_or(
265-
AllowanceValue {
266-
amount: 0,
267-
expiration_ledger: 0,
268-
},
269-
|allowance| {
270-
if allowance.expiration_ledger < env.ledger().sequence() {
271-
AllowanceValue {
272-
amount: 0,
273-
expiration_ledger: allowance.expiration_ledger,
274-
}
275-
} else {
276-
allowance
246+
let key = AllowanceDataKey { from, spender };
247+
storage::try_allowance(env, key).map_or(
248+
AllowanceValue {
249+
amount: 0,
250+
expiration_ledger: 0,
251+
},
252+
|allowance| {
253+
if allowance.expiration_ledger < env.ledger().sequence() {
254+
AllowanceValue {
255+
amount: 0,
256+
expiration_ledger: allowance.expiration_ledger,
277257
}
278-
},
279-
)
258+
} else {
259+
allowance
260+
}
261+
},
262+
)
280263
}
281264

282265
fn write_allowance(
@@ -297,17 +280,15 @@ impl InterchainToken {
297280
ContractError::InvalidExpirationLedger
298281
);
299282

300-
let key = DataKey::Allowance(AllowanceDataKey { from, spender });
301-
env.storage().temporary().set(&key, &allowance);
283+
let key = AllowanceDataKey { from, spender };
284+
storage::set_allowance(env, key.clone(), &allowance);
302285

303286
if amount > 0 {
304287
let live_for = expiration_ledger
305288
.checked_sub(env.ledger().sequence())
306289
.unwrap();
307290

308-
env.storage()
309-
.temporary()
310-
.extend_ttl(&key, live_for, live_for)
291+
storage::extend_allowance_ttl(env, key, live_for, live_for);
311292
}
312293
}
313294

@@ -334,45 +315,21 @@ impl InterchainToken {
334315
}
335316
}
336317

337-
fn read_balance(env: &Env, addr: Address) -> i128 {
338-
let key = DataKey::Balance(addr);
339-
env.storage()
340-
.persistent()
341-
.get::<_, i128>(&key)
342-
.inspect(|_| {
343-
// Extend the TTL of the balance entry when the balance is successfully retrieved.
344-
extend_persistent_ttl(env, &key);
345-
})
346-
.unwrap_or_default()
347-
}
348-
349318
fn receive_balance(env: &Env, addr: Address, amount: i128) {
350-
let key = DataKey::Balance(addr);
319+
let current_balance = storage::try_balance(env, addr.clone()).unwrap_or_default();
351320

352-
env.storage()
353-
.persistent()
354-
.update(&key, |balance: Option<i128>| {
355-
balance.unwrap_or_default() + amount
356-
});
321+
storage::set_balance(env, addr, &(current_balance + amount));
357322
}
358323

359324
fn spend_balance(env: &Env, addr: Address, amount: i128) {
360-
let balance = Self::read_balance(env, addr.clone());
325+
let balance = storage::try_balance(env, addr.clone()).unwrap_or_default();
361326

362327
assert_with_error!(env, balance >= amount, ContractError::InsufficientBalance);
363328

364-
Self::write_balance(env, addr, balance - amount);
329+
storage::set_balance(env, addr, &(balance - amount));
365330
}
366331

367332
fn write_metadata(env: &Env, metadata: TokenMetadata) {
368333
TokenUtils::new(env).metadata().set_metadata(&metadata);
369334
}
370-
371-
fn write_balance(env: &Env, addr: Address, amount: i128) {
372-
let key = DataKey::Balance(addr);
373-
374-
env.storage().persistent().set(&key, &amount);
375-
376-
extend_persistent_ttl(env, &key);
377-
}
378335
}

contracts/stellar-interchain-token/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ cfg_if::cfg_if! {
1515
pub use interface::{InterchainTokenClient, InterchainTokenInterface};
1616
} else {
1717
pub mod event;
18-
mod storage_types;
18+
mod storage;
1919
mod contract;
2020

2121
pub use contract::{InterchainToken, InterchainTokenClient};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use soroban_sdk::{contracttype, Address, BytesN};
2+
use stellar_axelar_std::contractstorage;
3+
4+
#[derive(Clone)]
5+
#[contracttype]
6+
pub struct AllowanceDataKey {
7+
pub from: Address,
8+
pub spender: Address,
9+
}
10+
11+
#[contracttype]
12+
pub struct AllowanceValue {
13+
pub amount: i128,
14+
pub expiration_ledger: u32,
15+
}
16+
17+
/// Do not use the symbol `METADATA` as a key as it is reserved for token metadata.
18+
#[contractstorage]
19+
#[derive(Clone)]
20+
pub enum DataKey {
21+
#[temporary]
22+
#[value(AllowanceValue)]
23+
Allowance { key: AllowanceDataKey },
24+
25+
#[persistent]
26+
#[value(i128)]
27+
Balance { address: Address },
28+
29+
#[instance]
30+
#[status]
31+
Minter { minter: Address },
32+
33+
#[instance]
34+
#[value(BytesN<32>)]
35+
TokenId,
36+
}

contracts/stellar-interchain-token/src/storage_types.rs

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// Do not use the symbol `METADATA` as a key as it is reserved for token metadata.
2+
#[derive(Clone)]
3+
pub enum DataKey {
4+
5+
#[temporary]
6+
#[value(AllowanceValue)]
7+
Allowance { key: AllowanceDataKey },
8+
9+
#[persistent]
10+
#[value(i128)]
11+
Balance { address: Address },
12+
13+
#[instance]
14+
#[status]
15+
Minter { minter: Address },
16+
17+
#[instance]
18+
#[value(BytesN<32>)]
19+
TokenId,
20+
}

contracts/stellar-interchain-token/src/tests/test.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,3 +570,87 @@ fn clawback_fails() {
570570

571571
token.clawback(&token.owner(), &1);
572572
}
573+
574+
#[test]
575+
fn allowance_returns_zero_when_expired() {
576+
let env = Env::default();
577+
578+
let user1 = Address::generate(&env);
579+
let user2 = Address::generate(&env);
580+
let amount = 1000;
581+
582+
let (token, _) = setup_token(&env);
583+
584+
// Set current ledger to 100
585+
let current_ledger = 100;
586+
env.ledger().set_sequence_number(current_ledger);
587+
588+
// Set allowance to expire at ledger 200
589+
let expiration_ledger = 200;
590+
assert_auth!(
591+
user1,
592+
token.approve(&user1, &user2, &amount, &expiration_ledger)
593+
);
594+
assert_eq!(token.allowance(&user1, &user2), amount);
595+
596+
// Move to ledger after expiration
597+
env.ledger().set_sequence_number(expiration_ledger + 1);
598+
599+
// Allowance should be 0 after expiration
600+
assert_eq!(token.allowance(&user1, &user2), 0);
601+
}
602+
603+
#[test]
604+
#[should_panic(expected = "HostError: Error(Contract, #7)")] // InvalidExpirationLedger
605+
fn approve_fails_with_expired_ledger() {
606+
let env = Env::default();
607+
608+
let user1 = Address::generate(&env);
609+
let user2 = Address::generate(&env);
610+
let amount = 1000;
611+
612+
let (token, _) = setup_token(&env);
613+
614+
// Set current ledger to 100
615+
let current_ledger = 100;
616+
env.ledger().set_sequence_number(current_ledger);
617+
618+
// Try to set allowance with already expired ledger (before current)
619+
let expired_ledger = current_ledger - 1;
620+
621+
token
622+
.mock_all_auths()
623+
.approve(&user1, &user2, &amount, &expired_ledger);
624+
}
625+
626+
#[test]
627+
#[should_panic(expected = "HostError: Error(Contract, #7)")] // InvalidExpirationLedger
628+
fn allowance_preserves_expiration_when_expired() {
629+
let env = Env::default();
630+
let user1 = Address::generate(&env);
631+
let user2 = Address::generate(&env);
632+
let amount = 1000;
633+
let (token, _) = setup_token(&env);
634+
635+
// Set current ledger to 100
636+
let current_ledger = 100;
637+
env.ledger().set_sequence_number(current_ledger);
638+
639+
// Set allowance to expire at ledger 200
640+
let expiration_ledger = current_ledger + 100;
641+
assert_auth!(
642+
user1,
643+
token.approve(&user1, &user2, &amount, &expiration_ledger)
644+
);
645+
646+
// Move past expiration
647+
env.ledger().set_sequence_number(expiration_ledger + 1);
648+
649+
// First check returns 0
650+
assert_eq!(token.allowance(&user1, &user2), 0);
651+
652+
// Try to set new allowance with the same expired ledger - should fail
653+
token
654+
.mock_all_auths()
655+
.approve(&user1, &user2, &amount, &expiration_ledger);
656+
}

0 commit comments

Comments
 (0)