Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ pub mod events;
pub mod fees;
pub mod mint;
pub mod multisig;
pub mod nonce;
pub mod rate_lock;
pub mod schema;
pub mod token;
pub mod utils;

pub use crate::email_to_wallet::EmailToWalletContract;
pub use crate::nonce::ContractError;
pub use crate::nonce::NonceTracker;
pub use conversion::ConversionContract;
pub use conversion::Currency;
pub use escrow::EscrowContract;
Expand Down
36 changes: 36 additions & 0 deletions src/nonce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#![no_std]

use soroban_sdk::{contract, contracterror, contractimpl, symbol_short, Address, Env, Symbol};

#[contracterror]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ContractError {
InvalidNonce = 1,
}

#[contract]
pub struct NonceTracker;

#[contractimpl]
impl NonceTracker {
pub fn get_nonce(env: Env, user: Address) -> u64 {
let key = (user.clone(), symbol_short!("NONCE"));
env.storage().persistent().get(&key).unwrap_or(0)
}

pub fn check_and_update_nonce(
env: Env,
user: Address,
incoming: u64,
) -> Result<u64, ContractError> {
let key = (user.clone(), symbol_short!("NONCE"));
let stored: u64 = env.storage().persistent().get(&key).unwrap_or(0);

if incoming <= stored {
return Err(ContractError::InvalidNonce);
}

env.storage().persistent().set(&key, &incoming);
Ok(incoming)
}
}
35 changes: 35 additions & 0 deletions src/rate_lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![no_std]

use soroban_sdk::{contract, contracterror, contractimpl, symbol_short, Address, Env, Symbol};

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RateLockError {
NoRateLocked = 1,
RateExpired = 2,
}

#[contract]
pub struct RateLockContract;

#[contractimpl]
impl RateLockContract {
pub fn lock_rate(env: Env, user: Address, rate: i128, duration_seconds: u64) {
let expiry = env.ledger().timestamp() + duration_seconds;
let key = (user.clone(), symbol_short!("RATELOCK"));
env.storage().persistent().set(&key, &(rate, expiry));
}

pub fn validate_conversion(env: Env, user: Address) -> Result<i128, RateLockError> {
let key = (user.clone(), symbol_short!("RATELOCK"));
let stored: Option<(i128, u64)> = env.storage().persistent().get(&key);

let (rate, expiry) = stored.ok_or(RateLockError::NoRateLocked)?;

if env.ledger().timestamp() > expiry {
return Err(RateLockError::RateExpired);
}

Ok(rate)
}
}
133 changes: 133 additions & 0 deletions test_snapshots/test_lock_and_validate_rate.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
"generators": {
"address": 2,
"nonce": 0
},
"auth": [
[],
[],
[],
[]
],
"ledger": {
"protocol_version": 22,
"sequence_number": 0,
"timestamp": 61,
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
"base_reserve": 0,
"min_persistent_entry_ttl": 4096,
"min_temp_entry_ttl": 16,
"max_entry_ttl": 6312000,
"ledger_entries": [
[
{
"contract_data": {
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
"key": {
"vec": [
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
},
{
"symbol": "RATELOCK"
}
]
},
"durability": "persistent"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
"key": {
"vec": [
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
},
{
"symbol": "RATELOCK"
}
]
},
"durability": "persistent",
"val": {
"vec": [
{
"i128": {
"hi": 0,
"lo": 100
}
},
{
"u64": 60
}
]
}
}
},
"ext": "v0"
},
4095
]
],
[
{
"contract_data": {
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
"key": "ledger_key_contract_instance",
"durability": "persistent"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
"contract_instance": {
"executable": {
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"storage": null
}
}
}
},
"ext": "v0"
},
4095
]
],
[
{
"contract_code": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
4095
]
]
]
},
"events": []
}
122 changes: 122 additions & 0 deletions test_snapshots/test_nonce_tracker.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"generators": {
"address": 2,
"nonce": 0
},
"auth": [
[],
[],
[]
],
"ledger": {
"protocol_version": 22,
"sequence_number": 0,
"timestamp": 0,
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
"base_reserve": 0,
"min_persistent_entry_ttl": 4096,
"min_temp_entry_ttl": 16,
"max_entry_ttl": 6312000,
"ledger_entries": [
[
{
"contract_data": {
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": {
"vec": [
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
},
{
"symbol": "NONCE"
}
]
},
"durability": "persistent"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": {
"vec": [
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
},
{
"symbol": "NONCE"
}
]
},
"durability": "persistent",
"val": {
"u64": 2
}
}
},
"ext": "v0"
},
4095
]
],
[
{
"contract_data": {
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
"contract_instance": {
"executable": {
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"storage": null
}
}
}
},
"ext": "v0"
},
4095
]
],
[
{
"contract_code": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
4095
]
]
]
},
"events": []
}
26 changes: 26 additions & 0 deletions tests/nonce_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![cfg(test)]

use soroban_sdk::testutils::Address as _;
use soroban_sdk::{Address, Env};
use stellar_multisig_contract::nonce::ContractError;
use stellar_multisig_contract::nonce::NonceTracker;

#[test]
fn test_nonce_tracker() {
let env = Env::default();
let contract_id = env.register(NonceTracker, ());
let user = Address::generate(&env);

env.as_contract(&contract_id, || {
assert_eq!(NonceTracker::get_nonce(env.clone(), user.clone()), 0);
NonceTracker::check_and_update_nonce(env.clone(), user.clone(), 1).unwrap();
assert_eq!(NonceTracker::get_nonce(env.clone(), user.clone()), 1);
});

env.as_contract(&contract_id, || {
let err = NonceTracker::check_and_update_nonce(env.clone(), user.clone(), 1).unwrap_err();
assert_eq!(err, ContractError::InvalidNonce);
NonceTracker::check_and_update_nonce(env.clone(), user.clone(), 2).unwrap();
assert_eq!(NonceTracker::get_nonce(env.clone(), user.clone()), 2);
})
}
Loading
Loading