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
158 changes: 150 additions & 8 deletions contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod invariants;
mod lock;

pub mod rewards;
pub mod staking;
mod storage_types;
pub mod strategy;
pub mod token;
Expand Down Expand Up @@ -200,14 +201,6 @@ impl NesteraContract {
true
}

pub fn mint(env: Env, payload: MintPayload, signature: BytesN<64>) -> i128 {
Self::verify_signature(env.clone(), payload.clone(), signature);
let amount = payload.amount;
env.events()
.publish((symbol_short!("mint"), payload.user), amount);
amount
}

pub fn is_initialized(env: Env) -> bool {
env.storage().instance().has(&DataKey::Initialized)
}
Expand Down Expand Up @@ -776,6 +769,151 @@ impl NesteraContract {
token::get_token_metadata(&env)
}

// ========== Token Minting & Burning Functions (#376, #377) ==========

/// Mints new tokens to the specified address.
/// Only callable by governance or rewards module.
/// Updates total supply and emits TokenMinted event.
///
/// # Arguments
/// * `caller` - Address calling the function (must be governance or rewards)
/// * `to` - Address to receive the minted tokens
/// * `amount` - Amount of tokens to mint (must be positive)
///
/// # Returns
/// * `Ok(i128)` - New total supply after minting
/// * `Err(SavingsError)` if unauthorized, invalid amount, or overflow
pub fn mint_tokens(
env: Env,
caller: Address,
to: Address,
amount: i128,
) -> Result<i128, SavingsError> {
caller.require_auth();

// Check if caller is governance or admin
let is_governance = crate::governance::validate_admin_or_governance(&env, &caller).is_ok();
let admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.ok_or(SavingsError::Unauthorized)?;
let is_admin = admin == caller;

if !is_governance && !is_admin {
return Err(SavingsError::Unauthorized);
}

token::mint(&env, to, amount)
}

/// Burns tokens from the specified address.
/// Reduces total supply and emits TokenBurned event.
///
/// # Arguments
/// * `env` - Contract environment
/// * `from` - Address to burn tokens from
/// * `amount` - Amount of tokens to burn (must be positive)
///
/// # Returns
/// * `Ok(i128)` - New total supply after burning
/// * `Err(SavingsError)` if invalid amount or underflow
pub fn burn(env: Env, from: Address, amount: i128) -> Result<i128, SavingsError> {
from.require_auth();
token::burn(&env, from, amount)
}

// ========== Staking Functions (#442) ==========

/// Initializes staking configuration (admin only)
pub fn init_staking_config(
env: Env,
admin: Address,
config: staking::storage_types::StakingConfig,
) -> Result<(), SavingsError> {
let stored_admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.ok_or(SavingsError::Unauthorized)?;
stored_admin.require_auth();
if admin != stored_admin {
return Err(SavingsError::Unauthorized);
}
staking::storage::initialize_staking_config(&env, config)
}

/// Updates staking configuration (admin only)
pub fn update_staking_config(
env: Env,
admin: Address,
config: staking::storage_types::StakingConfig,
) -> Result<(), SavingsError> {
let stored_admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.ok_or(SavingsError::Unauthorized)?;
stored_admin.require_auth();
if admin != stored_admin {
return Err(SavingsError::Unauthorized);
}
staking::storage::update_staking_config(&env, config)
}

/// Gets the staking configuration
pub fn get_staking_config(
env: Env,
) -> Result<staking::storage_types::StakingConfig, SavingsError> {
staking::storage::get_staking_config(&env)
}

/// Stakes tokens for a user
pub fn stake(env: Env, user: Address, amount: i128) -> Result<i128, SavingsError> {
user.require_auth();
ensure_not_paused(&env)?;
crate::security::acquire_reentrancy_guard(&env)?;
let res = staking::storage::stake(&env, user, amount);
crate::security::release_reentrancy_guard(&env);
res
}

/// Unstakes tokens for a user
pub fn unstake(env: Env, user: Address, amount: i128) -> Result<(i128, i128), SavingsError> {
user.require_auth();
ensure_not_paused(&env)?;
crate::security::acquire_reentrancy_guard(&env)?;
let res = staking::storage::unstake(&env, user, amount);
crate::security::release_reentrancy_guard(&env);
res
}

/// Claims staking rewards for a user
pub fn claim_staking_rewards(env: Env, user: Address) -> Result<i128, SavingsError> {
user.require_auth();
ensure_not_paused(&env)?;
crate::security::acquire_reentrancy_guard(&env)?;
let res = staking::storage::claim_staking_rewards(&env, user);
crate::security::release_reentrancy_guard(&env);
res
}

/// Gets a user's stake information
pub fn get_user_stake(env: Env, user: Address) -> staking::storage_types::Stake {
staking::storage::get_user_stake(&env, &user)
}

/// Gets pending staking rewards for a user
pub fn get_pending_staking_rewards(env: Env, user: Address) -> Result<i128, SavingsError> {
staking::storage::update_rewards(&env)?;
staking::storage::calculate_pending_rewards(&env, &user)
}

/// Gets staking statistics (total_staked, total_rewards, reward_per_token)
pub fn get_staking_stats(env: Env) -> Result<(i128, i128, i128), SavingsError> {
staking::storage::get_staking_stats(&env)
}

// ========== Rewards Functions ==========

pub fn init_rewards_config(
Expand Down Expand Up @@ -1365,8 +1503,12 @@ mod governance_tests;
#[cfg(test)]
mod rates_test;
#[cfg(test)]
mod staking_tests;
#[cfg(test)]
mod test;
#[cfg(test)]
mod token_tests;
#[cfg(test)]
mod transition_tests;
#[cfg(test)]
mod ttl_tests;
Expand Down
43 changes: 43 additions & 0 deletions contracts/src/staking/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Event definitions and helpers for the staking module (#442).

use soroban_sdk::{symbol_short, Address, Env};

use super::storage_types::{StakeCreated, StakeWithdrawn, StakingRewardsClaimed};

/// Emits a StakeCreated event.
pub fn emit_stake_created(env: &Env, user: Address, amount: i128, total_staked: i128) {
let event = StakeCreated {
user: user.clone(),
amount,
total_staked,
};
env.events().publish(
(symbol_short!("staking"), symbol_short!("stake"), user),
event,
);
}

/// Emits a StakeWithdrawn event.
pub fn emit_stake_withdrawn(env: &Env, user: Address, amount: i128, total_staked: i128) {
let event = StakeWithdrawn {
user: user.clone(),
amount,
total_staked,
};
env.events().publish(
(symbol_short!("staking"), symbol_short!("unstake"), user),
event,
);
}

/// Emits a StakingRewardsClaimed event.
pub fn emit_staking_rewards_claimed(env: &Env, user: Address, amount: i128) {
let event = StakingRewardsClaimed {
user: user.clone(),
amount,
};
env.events().publish(
(symbol_short!("staking"), symbol_short!("rewards"), user),
event,
);
}
6 changes: 6 additions & 0 deletions contracts/src/staking/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Staking mechanism for Nestera protocol (#442).
//! Allows users to stake tokens to earn additional rewards or governance power.

pub mod events;
pub mod storage;
pub mod storage_types;
Loading
Loading