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
1 change: 1 addition & 0 deletions contracts/parameters-contract/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ pub enum ParametersError {
NotAdmin = 2,
InvalidParameters = 3,
NotInitialized = 4,
ReentrancyDetected = 5,
}
19 changes: 19 additions & 0 deletions contracts/parameters-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ impl ParametersContract {
pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) {
let admin = storage::get_admin(&env).unwrap_or_else(|err| panic_with_error!(&env, err));
admin.require_auth();
Self::enter_non_reentrant(&env);
env.deployer().update_current_contract_wasm(new_wasm_hash);
Self::exit_non_reentrant(&env);
}
pub fn get_admin(env: Env) -> Result<Address, ParametersError> {
storage::get_admin(&env)
Expand All @@ -48,8 +50,10 @@ impl ParametersContract {
old_admin.require_auth();
access::require_admin(&env, &old_admin);

Self::enter_non_reentrant(&env);
storage::set_admin(&env, &new_admin);
events::emit_admin_updated(&env, &old_admin, &new_admin);
Self::exit_non_reentrant(&env);
}

pub fn get_parameters(env: Env) -> Result<ProtocolParameters, ParametersError> {
Expand All @@ -61,8 +65,10 @@ impl ParametersContract {
access::require_admin(&env, &admin);
Self::validate_parameters(&env, &params);

Self::enter_non_reentrant(&env);
storage::set_parameters(&env, &params);
events::emit_parameters_updated(&env, &admin, &params);
Self::exit_non_reentrant(&env);
}

fn validate_parameters(env: &Env, params: &ProtocolParameters) {
Expand All @@ -73,6 +79,19 @@ impl ParametersContract {
panic_with_error!(env, ParametersError::InvalidParameters);
}
}

fn enter_non_reentrant(env: &Env) {
if storage::is_reentrancy_locked(env)
.unwrap_or_else(|err| panic_with_error!(env, err))
{
panic_with_error!(env, ParametersError::ReentrancyDetected);
}
storage::set_reentrancy_locked(env, true);
}

fn exit_non_reentrant(env: &Env) {
storage::set_reentrancy_locked(env, false);
}
}

#[cfg(test)]
Expand Down
13 changes: 13 additions & 0 deletions contracts/parameters-contract/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::types::ProtocolParameters;

pub const ADMIN_KEY: Symbol = symbol_short!("ADMIN");
pub const PARAMS_KEY: Symbol = symbol_short!("PARAMS");
pub const REENTRANCY_LOCK: Symbol = symbol_short!("LOCKED");

pub fn has_admin(env: &Env) -> bool {
env.storage().instance().has(&ADMIN_KEY)
Expand All @@ -31,3 +32,15 @@ pub fn get_parameters(env: &Env) -> Result<ProtocolParameters, ParametersError>
pub fn set_parameters(env: &Env, params: &ProtocolParameters) {
env.storage().instance().set(&PARAMS_KEY, params);
}

pub fn is_reentrancy_locked(env: &Env) -> Result<bool, ParametersError> {
Ok(env
.storage()
.instance()
.get(&REENTRANCY_LOCK)
.unwrap_or(false))
}

pub fn set_reentrancy_locked(env: &Env, locked: bool) {
env.storage().instance().set(&REENTRANCY_LOCK, &locked);
}
6 changes: 5 additions & 1 deletion contracts/parameters-contract/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use crate::{
default_parameters, ParametersContract, ParametersContractClient, ParametersError,
ProtocolParameters,
};
use soroban_sdk::{testutils::Address as _, Address, Env};
use soroban_sdk::{
contract, contractimpl,
testutils::Address as _,
Address, Env, Symbol,
};

fn setup() -> (Env, ParametersContractClient<'static>, Address) {
let env = Env::default();
Expand Down
1 change: 1 addition & 0 deletions contracts/reputation-contract/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ pub enum ReputationError {
Overflow = 4,
Underflow = 5,
NotInitialized = 6,
ReentrancyDetected = 7,
}
45 changes: 45 additions & 0 deletions contracts/reputation-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ impl ReputationContract {
updater.require_auth();
access::require_updater(&env, &updater);

Self::enter_non_reentrant(&env);

let old_score = storage::read_score(&env, &user)
.unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err));
let new_score = old_score
Expand All @@ -50,6 +52,8 @@ impl ReputationContract {

let reason = symbol_short!("increase");
events::emit_score_changed(&env, &user, old_score, new_score, &reason);

Self::exit_non_reentrant(&env);
}

/// Decrease a user's reputation score by a given amount
Expand All @@ -58,6 +62,8 @@ impl ReputationContract {
updater.require_auth();
access::require_updater(&env, &updater);

Self::enter_non_reentrant(&env);

let old_score = storage::read_score(&env, &user)
.unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err));
let new_score = match old_score.checked_sub(amount) {
Expand All @@ -69,6 +75,8 @@ impl ReputationContract {

let reason = symbol_short!("decrease");
events::emit_score_changed(&env, &user, old_score, new_score, &reason);

Self::exit_non_reentrant(&env);
}

/// Set a user's reputation score to a specific value
Expand All @@ -81,12 +89,16 @@ impl ReputationContract {
soroban_sdk::panic_with_error!(&env, ReputationError::OutOfBounds);
}

Self::enter_non_reentrant(&env);

let old_score = storage::read_score(&env, &user)
.unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err));
storage::write_score(&env, &user, new_score);

let reason = symbol_short!("set");
events::emit_score_changed(&env, &user, old_score, new_score, &reason);

Self::exit_non_reentrant(&env);
}

/// Add a mentor vouching boost to a user's reputation score.
Expand All @@ -95,6 +107,8 @@ impl ReputationContract {
updater.require_auth();
access::require_updater(&env, &updater);

Self::enter_non_reentrant(&env);

let old_score = storage::read_score(&env, &user)
.unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err));
let new_score = old_score
Expand All @@ -110,6 +124,8 @@ impl ReputationContract {

let reason = symbol_short!("boost");
events::emit_score_changed(&env, &user, old_score, new_score, &reason);

Self::exit_non_reentrant(&env);
}

/// Remove a mentor vouching boost from a user's reputation score.
Expand All @@ -118,6 +134,8 @@ impl ReputationContract {
updater.require_auth();
access::require_updater(&env, &updater);

Self::enter_non_reentrant(&env);

let old_score = storage::read_score(&env, &user)
.unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err));
let new_score = match old_score.checked_sub(amount) {
Expand All @@ -129,6 +147,8 @@ impl ReputationContract {

let reason = symbol_short!("unboost");
events::emit_score_changed(&env, &user, old_score, new_score, &reason);

Self::exit_non_reentrant(&env);
}

/// Set or remove an address as an authorized updater
Expand All @@ -137,8 +157,12 @@ impl ReputationContract {
admin.require_auth();
access::require_admin(&env, &admin);

Self::enter_non_reentrant(&env);

storage::set_updater(&env, &updater, allowed);
events::emit_updater_changed(&env, &updater, allowed);

Self::exit_non_reentrant(&env);
}

/// Check if an address is an authorized updater
Expand All @@ -156,8 +180,13 @@ impl ReputationContract {
// Admin exists, require current admin authorization
old_admin.require_auth();
access::require_admin(&env, &old_admin);

Self::enter_non_reentrant(&env);

storage::set_admin(&env, &new_admin);
events::emit_admin_changed(&env, &old_admin, &new_admin);

Self::exit_non_reentrant(&env);
} else {
// No admin exists, allow setting (initialization)
storage::set_admin(&env, &new_admin);
Expand All @@ -171,11 +200,27 @@ impl ReputationContract {
let admin = storage::get_admin(&env)
.unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err));
admin.require_auth();

Self::enter_non_reentrant(&env);
env.deployer().update_current_contract_wasm(new_wasm_hash);
Self::exit_non_reentrant(&env);
}
pub fn get_admin(env: Env) -> Result<Address, ReputationError> {
storage::get_admin(&env)
}

fn enter_non_reentrant(env: &Env) {
if storage::is_reentrancy_locked(env)
.unwrap_or_else(|err| soroban_sdk::panic_with_error!(env, err))
{
soroban_sdk::panic_with_error!(env, ReputationError::ReentrancyDetected);
}
storage::set_reentrancy_locked(env, true);
}

fn exit_non_reentrant(env: &Env) {
storage::set_reentrancy_locked(env, false);
}
}

#[cfg(test)]
Expand Down
13 changes: 13 additions & 0 deletions contracts/reputation-contract/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::errors::ReputationError;
pub const ADMIN_KEY: Symbol = symbol_short!("ADMIN");
pub const UPDATERS_MAP: Symbol = symbol_short!("UPDATERS");
pub const SCORES_MAP: Symbol = symbol_short!("SCORES");
pub const REENTRANCY_LOCK: Symbol = symbol_short!("LOCKED");

/// Get the admin address from storage
pub fn get_admin(env: &Env) -> Result<Address, ReputationError> {
Expand Down Expand Up @@ -70,3 +71,15 @@ pub fn set_updater(env: &Env, updater: &Address, allowed: bool) {

env.storage().instance().set(&UPDATERS_MAP, &updaters);
}

pub fn is_reentrancy_locked(env: &Env) -> Result<bool, ReputationError> {
Ok(env
.storage()
.instance()
.get(&REENTRANCY_LOCK)
.unwrap_or(false))
}

pub fn set_reentrancy_locked(env: &Env, locked: bool) {
env.storage().instance().set(&REENTRANCY_LOCK, &locked);
}
Loading
Loading