Skip to content

Commit c7ce1c7

Browse files
authored
Merge pull request #449 from Shredder401k/feat/contract
feat: implement staking module with minting and burning functionality for tokens
2 parents 97f56c6 + 2954103 commit c7ce1c7

File tree

8 files changed

+1183
-8
lines changed

8 files changed

+1183
-8
lines changed

contracts/src/lib.rs

Lines changed: 150 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod invariants;
1717
mod lock;
1818

1919
pub mod rewards;
20+
pub mod staking;
2021
mod storage_types;
2122
pub mod strategy;
2223
pub mod token;
@@ -200,14 +201,6 @@ impl NesteraContract {
200201
true
201202
}
202203

203-
pub fn mint(env: Env, payload: MintPayload, signature: BytesN<64>) -> i128 {
204-
Self::verify_signature(env.clone(), payload.clone(), signature);
205-
let amount = payload.amount;
206-
env.events()
207-
.publish((symbol_short!("mint"), payload.user), amount);
208-
amount
209-
}
210-
211204
pub fn is_initialized(env: Env) -> bool {
212205
env.storage().instance().has(&DataKey::Initialized)
213206
}
@@ -776,6 +769,151 @@ impl NesteraContract {
776769
token::get_token_metadata(&env)
777770
}
778771

772+
// ========== Token Minting & Burning Functions (#376, #377) ==========
773+
774+
/// Mints new tokens to the specified address.
775+
/// Only callable by governance or rewards module.
776+
/// Updates total supply and emits TokenMinted event.
777+
///
778+
/// # Arguments
779+
/// * `caller` - Address calling the function (must be governance or rewards)
780+
/// * `to` - Address to receive the minted tokens
781+
/// * `amount` - Amount of tokens to mint (must be positive)
782+
///
783+
/// # Returns
784+
/// * `Ok(i128)` - New total supply after minting
785+
/// * `Err(SavingsError)` if unauthorized, invalid amount, or overflow
786+
pub fn mint_tokens(
787+
env: Env,
788+
caller: Address,
789+
to: Address,
790+
amount: i128,
791+
) -> Result<i128, SavingsError> {
792+
caller.require_auth();
793+
794+
// Check if caller is governance or admin
795+
let is_governance = crate::governance::validate_admin_or_governance(&env, &caller).is_ok();
796+
let admin: Address = env
797+
.storage()
798+
.instance()
799+
.get(&DataKey::Admin)
800+
.ok_or(SavingsError::Unauthorized)?;
801+
let is_admin = admin == caller;
802+
803+
if !is_governance && !is_admin {
804+
return Err(SavingsError::Unauthorized);
805+
}
806+
807+
token::mint(&env, to, amount)
808+
}
809+
810+
/// Burns tokens from the specified address.
811+
/// Reduces total supply and emits TokenBurned event.
812+
///
813+
/// # Arguments
814+
/// * `env` - Contract environment
815+
/// * `from` - Address to burn tokens from
816+
/// * `amount` - Amount of tokens to burn (must be positive)
817+
///
818+
/// # Returns
819+
/// * `Ok(i128)` - New total supply after burning
820+
/// * `Err(SavingsError)` if invalid amount or underflow
821+
pub fn burn(env: Env, from: Address, amount: i128) -> Result<i128, SavingsError> {
822+
from.require_auth();
823+
token::burn(&env, from, amount)
824+
}
825+
826+
// ========== Staking Functions (#442) ==========
827+
828+
/// Initializes staking configuration (admin only)
829+
pub fn init_staking_config(
830+
env: Env,
831+
admin: Address,
832+
config: staking::storage_types::StakingConfig,
833+
) -> Result<(), SavingsError> {
834+
let stored_admin: Address = env
835+
.storage()
836+
.instance()
837+
.get(&DataKey::Admin)
838+
.ok_or(SavingsError::Unauthorized)?;
839+
stored_admin.require_auth();
840+
if admin != stored_admin {
841+
return Err(SavingsError::Unauthorized);
842+
}
843+
staking::storage::initialize_staking_config(&env, config)
844+
}
845+
846+
/// Updates staking configuration (admin only)
847+
pub fn update_staking_config(
848+
env: Env,
849+
admin: Address,
850+
config: staking::storage_types::StakingConfig,
851+
) -> Result<(), SavingsError> {
852+
let stored_admin: Address = env
853+
.storage()
854+
.instance()
855+
.get(&DataKey::Admin)
856+
.ok_or(SavingsError::Unauthorized)?;
857+
stored_admin.require_auth();
858+
if admin != stored_admin {
859+
return Err(SavingsError::Unauthorized);
860+
}
861+
staking::storage::update_staking_config(&env, config)
862+
}
863+
864+
/// Gets the staking configuration
865+
pub fn get_staking_config(
866+
env: Env,
867+
) -> Result<staking::storage_types::StakingConfig, SavingsError> {
868+
staking::storage::get_staking_config(&env)
869+
}
870+
871+
/// Stakes tokens for a user
872+
pub fn stake(env: Env, user: Address, amount: i128) -> Result<i128, SavingsError> {
873+
user.require_auth();
874+
ensure_not_paused(&env)?;
875+
crate::security::acquire_reentrancy_guard(&env)?;
876+
let res = staking::storage::stake(&env, user, amount);
877+
crate::security::release_reentrancy_guard(&env);
878+
res
879+
}
880+
881+
/// Unstakes tokens for a user
882+
pub fn unstake(env: Env, user: Address, amount: i128) -> Result<(i128, i128), SavingsError> {
883+
user.require_auth();
884+
ensure_not_paused(&env)?;
885+
crate::security::acquire_reentrancy_guard(&env)?;
886+
let res = staking::storage::unstake(&env, user, amount);
887+
crate::security::release_reentrancy_guard(&env);
888+
res
889+
}
890+
891+
/// Claims staking rewards for a user
892+
pub fn claim_staking_rewards(env: Env, user: Address) -> Result<i128, SavingsError> {
893+
user.require_auth();
894+
ensure_not_paused(&env)?;
895+
crate::security::acquire_reentrancy_guard(&env)?;
896+
let res = staking::storage::claim_staking_rewards(&env, user);
897+
crate::security::release_reentrancy_guard(&env);
898+
res
899+
}
900+
901+
/// Gets a user's stake information
902+
pub fn get_user_stake(env: Env, user: Address) -> staking::storage_types::Stake {
903+
staking::storage::get_user_stake(&env, &user)
904+
}
905+
906+
/// Gets pending staking rewards for a user
907+
pub fn get_pending_staking_rewards(env: Env, user: Address) -> Result<i128, SavingsError> {
908+
staking::storage::update_rewards(&env)?;
909+
staking::storage::calculate_pending_rewards(&env, &user)
910+
}
911+
912+
/// Gets staking statistics (total_staked, total_rewards, reward_per_token)
913+
pub fn get_staking_stats(env: Env) -> Result<(i128, i128, i128), SavingsError> {
914+
staking::storage::get_staking_stats(&env)
915+
}
916+
779917
// ========== Rewards Functions ==========
780918

781919
pub fn init_rewards_config(
@@ -1365,8 +1503,12 @@ mod governance_tests;
13651503
#[cfg(test)]
13661504
mod rates_test;
13671505
#[cfg(test)]
1506+
mod staking_tests;
1507+
#[cfg(test)]
13681508
mod test;
13691509
#[cfg(test)]
1510+
mod token_tests;
1511+
#[cfg(test)]
13701512
mod transition_tests;
13711513
#[cfg(test)]
13721514
mod ttl_tests;

contracts/src/staking/events.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//! Event definitions and helpers for the staking module (#442).
2+
3+
use soroban_sdk::{symbol_short, Address, Env};
4+
5+
use super::storage_types::{StakeCreated, StakeWithdrawn, StakingRewardsClaimed};
6+
7+
/// Emits a StakeCreated event.
8+
pub fn emit_stake_created(env: &Env, user: Address, amount: i128, total_staked: i128) {
9+
let event = StakeCreated {
10+
user: user.clone(),
11+
amount,
12+
total_staked,
13+
};
14+
env.events().publish(
15+
(symbol_short!("staking"), symbol_short!("stake"), user),
16+
event,
17+
);
18+
}
19+
20+
/// Emits a StakeWithdrawn event.
21+
pub fn emit_stake_withdrawn(env: &Env, user: Address, amount: i128, total_staked: i128) {
22+
let event = StakeWithdrawn {
23+
user: user.clone(),
24+
amount,
25+
total_staked,
26+
};
27+
env.events().publish(
28+
(symbol_short!("staking"), symbol_short!("unstake"), user),
29+
event,
30+
);
31+
}
32+
33+
/// Emits a StakingRewardsClaimed event.
34+
pub fn emit_staking_rewards_claimed(env: &Env, user: Address, amount: i128) {
35+
let event = StakingRewardsClaimed {
36+
user: user.clone(),
37+
amount,
38+
};
39+
env.events().publish(
40+
(symbol_short!("staking"), symbol_short!("rewards"), user),
41+
event,
42+
);
43+
}

contracts/src/staking/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//! Staking mechanism for Nestera protocol (#442).
2+
//! Allows users to stake tokens to earn additional rewards or governance power.
3+
4+
pub mod events;
5+
pub mod storage;
6+
pub mod storage_types;

0 commit comments

Comments
 (0)