diff --git a/token/src/contract.rs b/token/src/contract.rs index 0b6be07c..388e9a43 100644 --- a/token/src/contract.rs +++ b/token/src/contract.rs @@ -3,6 +3,7 @@ use crate::admin::{has_administrator, read_administrator, write_administrator}; use crate::allowance::{read_allowance, spend_allowance, write_allowance}; use crate::balance::{read_balance, receive_balance, spend_balance}; +use crate::supply::{has_supply, read_supply, write_supply, decrement_supply, increment_supply}; use crate::metadata::{read_decimal, read_name, read_symbol, write_metadata}; #[cfg(test)] use crate::storage_types::{AllowanceDataKey, AllowanceValue, DataKey}; @@ -23,7 +24,7 @@ pub struct Token; #[contractimpl] impl Token { - pub fn initialize(e: Env, admin: Address, decimal: u32, name: String, symbol: String) { + pub fn initialize(e: Env, admin: Address, decimal: u32, name: String, symbol: String, supply: i128) { if has_administrator(&e) { panic!("already initialized") } @@ -32,6 +33,10 @@ impl Token { panic!("Decimal must fit in a u8"); } + if supply > 0 { + write_supply(&e, &supply); + } + write_metadata( &e, TokenMetadata { @@ -47,11 +52,18 @@ impl Token { let admin = read_administrator(&e); admin.require_auth(); + if has_supply(&e) && amount > read_supply(&e) { + panic!("Amount greater than remaining supply"); + } + e.storage() .instance() .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); receive_balance(&e, to.clone(), amount); + if has_supply(&e) { + decrement_supply(&e, &amount); + } TokenUtils::new(&e).events().mint(admin, to, amount); } @@ -73,6 +85,18 @@ impl Token { let allowance = e.storage().temporary().get::<_, AllowanceValue>(&key); allowance } + + pub fn supply(e: Env) -> i128 { + if !has_supply(&e) { + return 0; + } + + e.storage() + .instance() + .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); + + read_supply(&e) + } } #[contractimpl] @@ -145,6 +169,10 @@ impl token::Interface for Token { .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); spend_balance(&e, from.clone(), amount); + if has_supply(&e) { + increment_supply(&e, &amount); + } + TokenUtils::new(&e).events().burn(from, amount); } @@ -159,6 +187,10 @@ impl token::Interface for Token { spend_allowance(&e, from.clone(), spender, amount); spend_balance(&e, from.clone(), amount); + if has_supply(&e) { + increment_supply(&e, &amount); + } + TokenUtils::new(&e).events().burn(from, amount) } diff --git a/token/src/lib.rs b/token/src/lib.rs index b5f04e4d..5e6ae30c 100644 --- a/token/src/lib.rs +++ b/token/src/lib.rs @@ -6,6 +6,7 @@ mod balance; mod contract; mod metadata; mod storage_types; +mod supply; mod test; pub use crate::contract::TokenClient; diff --git a/token/src/storage_types.rs b/token/src/storage_types.rs index 5710c057..28b8a45e 100644 --- a/token/src/storage_types.rs +++ b/token/src/storage_types.rs @@ -28,4 +28,5 @@ pub enum DataKey { Nonce(Address), State(Address), Admin, + Supply } diff --git a/token/src/supply.rs b/token/src/supply.rs new file mode 100644 index 00000000..b9106164 --- /dev/null +++ b/token/src/supply.rs @@ -0,0 +1,33 @@ +use soroban_sdk::{Env}; +use crate::storage_types::DataKey; + +pub fn has_supply(e: &Env) -> bool { + let key = DataKey::Supply; + e.storage().instance().has(&key) +} + +pub fn read_supply(e: &Env) -> i128 { + let key = DataKey::Supply; + e.storage().instance().get(&key).unwrap() +} + +pub fn write_supply(e: &Env, amount: &i128) { + let key = DataKey::Supply; + e.storage().instance().set(&key, amount); +} + +pub fn decrement_supply(e: &Env, amount: &i128) { + let key = DataKey::Supply; + let current_supply: i128 = e.storage().instance().get(&key).unwrap(); + let new_supply = current_supply - amount; + + e.storage().instance().set(&key, &new_supply); +} + +pub fn increment_supply(e: &Env, amount: &i128) { + let key = DataKey::Supply; + let current_supply: i128 = e.storage().instance().get(&key).unwrap(); + let new_supply = current_supply + amount; + + e.storage().instance().set(&key, &new_supply); +} \ No newline at end of file diff --git a/token/src/test.rs b/token/src/test.rs index 8f75daf7..b15aaf8b 100644 --- a/token/src/test.rs +++ b/token/src/test.rs @@ -8,9 +8,9 @@ use soroban_sdk::{ Address, Env, IntoVal, Symbol, }; -fn create_token<'a>(e: &Env, admin: &Address) -> TokenClient<'a> { +fn create_token<'a>(e: &Env, admin: &Address, supply: &i128) -> TokenClient<'a> { let token = TokenClient::new(e, &e.register_contract(None, Token {})); - token.initialize(admin, &7, &"name".into_val(e), &"symbol".into_val(e)); + token.initialize(admin, &7, &"name".into_val(e), &"symbol".into_val(e), &supply); token } @@ -24,7 +24,7 @@ fn test() { let user1 = Address::generate(&e); let user2 = Address::generate(&e); let user3 = Address::generate(&e); - let token = create_token(&e, &admin1); + let token = create_token(&e, &admin1, &0); token.mint(&user1, &1000); assert_eq!( @@ -145,7 +145,7 @@ fn test_burn() { let admin = Address::generate(&e); let user1 = Address::generate(&e); let user2 = Address::generate(&e); - let token = create_token(&e, &admin); + let token = create_token(&e, &admin, &0); token.mint(&user1, &1000); assert_eq!(token.balance(&user1), 1000); @@ -202,7 +202,7 @@ fn transfer_insufficient_balance() { let admin = Address::generate(&e); let user1 = Address::generate(&e); let user2 = Address::generate(&e); - let token = create_token(&e, &admin); + let token = create_token(&e, &admin, &0); token.mint(&user1, &1000); assert_eq!(token.balance(&user1), 1000); @@ -220,7 +220,7 @@ fn transfer_from_insufficient_allowance() { let user1 = Address::generate(&e); let user2 = Address::generate(&e); let user3 = Address::generate(&e); - let token = create_token(&e, &admin); + let token = create_token(&e, &admin, &0); token.mint(&user1, &1000); assert_eq!(token.balance(&user1), 1000); @@ -236,9 +236,9 @@ fn transfer_from_insufficient_allowance() { fn initialize_already_initialized() { let e = Env::default(); let admin = Address::generate(&e); - let token = create_token(&e, &admin); + let token = create_token(&e, &admin, &0); - token.initialize(&admin, &10, &"name".into_val(&e), &"symbol".into_val(&e)); + token.initialize(&admin, &10, &"name".into_val(&e), &"symbol".into_val(&e), &0); } #[test] @@ -252,6 +252,7 @@ fn decimal_is_over_max() { &(u32::from(u8::MAX) + 1), &"name".into_val(&e), &"symbol".into_val(&e), + &0 ); } @@ -264,8 +265,67 @@ fn test_zero_allowance() { let admin = Address::generate(&e); let spender = Address::generate(&e); let from = Address::generate(&e); - let token = create_token(&e, &admin); + let token = create_token(&e, &admin, &0); token.transfer_from(&spender, &from, &spender, &0); assert!(token.get_allowance(&from, &spender).is_none()); } + +#[test] +fn test_no_supply() { + let e = Env::default(); + e.mock_all_auths(); + + let admin = Address::generate(&e); + let token = create_token(&e, &admin, &0); + assert_eq!(token.supply(), 0_i128); +} + +#[test] +fn test_supply() { + let e = Env::default(); + e.mock_all_auths(); + + let admin = Address::generate(&e); + let address = Address::generate(&e); + let token = create_token(&e, &admin, &100); + + token.mint(&address, &50); + assert_eq!(token.supply(), 50_i128); + +} + +#[test] +#[should_panic(expected = "Amount greater than remaining supply")] +fn test_mint_greater_than_supply() { + let e = Env::default(); + e.mock_all_auths(); + + let admin = Address::generate(&e); + let address = Address::generate(&e); + let token = create_token(&e, &admin, &100); + + token.mint(&address, &101); +} + +#[test] +fn test_burn_with_supply() { + let e = Env::default(); + e.mock_all_auths(); + + let admin = Address::generate(&e); + let address1 = Address::generate(&e); + let address2 = Address::generate(&e); + let address3 = Address::generate(&e); + + let token = create_token(&e, &admin, &100); + + token.mint(&address1, &10); + token.mint(&address2, &20); + token.mint(&address3, &30); + + assert_eq!(token.supply(), 40_i128); + + token.burn(&address3, &25); + assert_eq!(token.supply(), 65_i128); +}