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
24 changes: 23 additions & 1 deletion contracts/bridge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ pub enum Error {
InsufficientSigners = 4,
Unauthorized = 5,
InvalidTransfer = 6,
Paused = 7,
}

#[contracttype]
pub enum DataKey {
Signers,
Threshold,
Transfer(Bytes), // Hash of transfer request -> status
Admin,
Paused,
}

#[contract]
Expand All @@ -30,17 +33,19 @@ pub struct BridgeContract;
#[contractimpl]
impl BridgeContract {
/// Initialize the bridge with a list of signers and a threshold for verification.
pub fn init(env: Env, signers: Vec<Address>, threshold: u32) {
pub fn init(env: Env, admin: Address, signers: Vec<Address>, threshold: u32) {
if env.storage().instance().has(&DataKey::Threshold) {
env.panic_with_error(Error::AlreadyInitialized);
}
if threshold == 0 || threshold > signers.len() {
env.panic_with_error(Error::InvalidThreshold);
}
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::Signers, &signers);
env.storage()
.instance()
.set(&DataKey::Threshold, &threshold);
env.storage().instance().set(&DataKey::Paused, &false);
}

/// Execute a cross-chain transfer after validating signatures from enough signers.
Expand All @@ -53,6 +58,11 @@ impl BridgeContract {
amount: u128,
signers_providing_auth: Vec<Address>,
) {
let is_paused: bool = env.storage().instance().get(&DataKey::Paused).unwrap_or(false);
if is_paused {
env.panic_with_error(Error::Paused);
}

let transfer_hash =
Self::calculate_transfer_hash(&env, &source_chain, &source_txn, &target_addr, amount);

Expand Down Expand Up @@ -116,4 +126,16 @@ impl BridgeContract {
data.append(&amount.to_xdr(env));
env.crypto().sha256(&data).into()
}

pub fn pause(env: Env) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
admin.require_auth();
env.storage().instance().set(&DataKey::Paused, &true);
}

pub fn unpause(env: Env) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
admin.require_auth();
env.storage().instance().set(&DataKey::Paused, &false);
}
}
12 changes: 9 additions & 3 deletions contracts/governance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub enum Error {
QuorumNotMet = 8,
/// Proposer's token balance is below `proposal_threshold`.
ProposalThresholdNotMet = 9,
/// Proposal parameters are invalid.
InvalidProposal = 10,
}

#[contracttype]
Expand Down Expand Up @@ -249,6 +251,10 @@ impl GovernorContract {
return Err(Error::ProposalThresholdNotMet);
}

if targets.len() != functions.len() || targets.len() != args.len() {
return Err(Error::InvalidProposal);
}

let id: u32 = env
.storage()
.instance()
Expand Down Expand Up @@ -568,8 +574,8 @@ fn load_proposal(env: &Env, proposal_id: u32) -> Result<Proposal, Error> {
/// Returns the `i`-th (target, function, args) action of a proposal, or
/// [`Error::InvalidState`] if the three action vectors have mismatched lengths.
fn proposal_action(proposal: &Proposal, i: u32) -> Result<(Address, Symbol, Vec<Val>), Error> {
let target = proposal.targets.get(i).ok_or(Error::InvalidState)?;
let function = proposal.functions.get(i).ok_or(Error::InvalidState)?;
let args = proposal.args.get(i).ok_or(Error::InvalidState)?;
let target = proposal.targets.get(i).ok_or(Error::InvalidProposal)?;
let function = proposal.functions.get(i).ok_or(Error::InvalidProposal)?;
let args = proposal.args.get(i).ok_or(Error::InvalidProposal)?;
Ok((target, function, args))
}
39 changes: 39 additions & 0 deletions contracts/governance/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,42 @@ fn test_min_quorum_blocks_queueing() {
}));
assert!(result.is_err(), "queue should have failed for insufficient quorum");
}

#[test]
#[should_panic(expected = "Error(Contract, #10)")]
fn test_propose_mismatched_lengths() {
let env = Env::default();
env.mock_all_auths();

let proposer = Address::generate(&env);
let token_id = env.register_contract(None, VotingToken);
let token_client = VotingTokenClient::new(&env, &token_id);
token_client.set_balance(&proposer, &1000);

let timelock_id = env.register_contract(None, MockTimelock);

let governor_id = env.register_contract(None, GovernorContract);
let client = GovernorContractClient::new(&env, &governor_id);

client.init(
&token_id,
&timelock_id,
&4000,
&5001,
&1000,
&0,
&500,
&1000,
);

let target = Address::generate(&env);
// targets has 1, functions has 0 -> mismatch
client.propose(
&proposer,
&vec![&env, target],
&vec![&env],
&vec![&env],
&symbol_short!("prop"),
);
}

9 changes: 9 additions & 0 deletions contracts/proxy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ const VERSION: Symbol = symbol_short!("VERSION");
const IMPL_HASH: Symbol = symbol_short!("IMPLHASH");
const INITIALISED: Symbol = symbol_short!("INIT");

// ~1 ledger per 5 seconds
const WEEK_IN_LEDGERS: u32 = 7 * 24 * 60 * 60 / 5;
const MONTH_IN_LEDGERS: u32 = 30 * WEEK_IN_LEDGERS / 7;

// ── Error codes ─────────────────────────────────────────────────────────────────

#[contracttype]
Expand Down Expand Up @@ -155,6 +159,8 @@ impl UupsProxy {
env.storage().instance().set(&ADMIN, &admin);
env.storage().instance().set(&VERSION, &1u32);
env.storage().instance().set(&IMPL_HASH, &impl_hash);

env.storage().instance().extend_ttl(WEEK_IN_LEDGERS, MONTH_IN_LEDGERS);

env.events()
.publish((symbol_short!("init"),), (admin, 1u32));
Expand All @@ -178,6 +184,7 @@ impl UupsProxy {

env.storage().instance().set(&VERSION, &next_version);
env.storage().instance().set(&IMPL_HASH, &new_wasm);
env.storage().instance().extend_ttl(WEEK_IN_LEDGERS, MONTH_IN_LEDGERS);

env.events()
.publish((symbol_short!("upgraded"),), (next_version, new_wasm));
Expand All @@ -189,6 +196,7 @@ impl UupsProxy {
pub fn transfer_admin(env: Env, new_admin: Address) {
Self::require_admin(&env);
env.storage().instance().set(&PEND_ADMIN, &new_admin);
env.storage().instance().extend_ttl(WEEK_IN_LEDGERS, MONTH_IN_LEDGERS);
env.events().publish((symbol_short!("adm_nom"),), new_admin);
}

Expand All @@ -203,6 +211,7 @@ impl UupsProxy {

env.storage().instance().set(&ADMIN, &pending);
env.storage().instance().remove(&PEND_ADMIN);
env.storage().instance().extend_ttl(WEEK_IN_LEDGERS, MONTH_IN_LEDGERS);

env.events().publish((symbol_short!("adm_xfer"),), pending);
}
Expand Down
14 changes: 11 additions & 3 deletions contracts/token-with-bugs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ pub struct TokenWithBugs;

// Storage key for per-account balances.
const BALANCE: Symbol = symbol_short!("BALANCE");
const ADMIN_KEY: Symbol = symbol_short!("ADMIN");

#[contractimpl]
impl TokenWithBugs {
/// Initialise the token.
///
/// NOTE – intentionally incomplete: does not persist `admin`, `name`, or
/// NOTE – intentionally incomplete: does not persist `name`, or
/// `symbol` so that Sanctifier can flag the missing initialisation guard.
pub fn initialize(e: Env, _admin: Address, _name: String, _symbol: String) {
pub fn initialize(e: Env, admin: Address, _name: String, _symbol: String) {
// Mark as initialised so re-entrancy can be detected.
e.storage().instance().set(&symbol_short!("init"), &true);
e.storage().instance().set(&ADMIN_KEY, &admin);
}

pub fn balance(e: Env, id: Address) -> i128 {
Expand All @@ -34,7 +36,11 @@ impl TokenWithBugs {
}

// VULNERABILITY: No overflow check – `current_balance + amount` can wrap.
pub fn mint(e: Env, to: Address, amount: i128) {
pub fn mint(e: Env, admin: Address, to: Address, amount: i128) {
admin.require_auth();
let stored_admin: Address = e.storage().instance().get(&ADMIN_KEY).unwrap();
assert!(admin == stored_admin, "not admin");

let current_balance = Self::balance(e.clone(), to.clone());
let new_balance = current_balance + amount;
e.storage().persistent().set(&to, &new_balance);
Expand All @@ -60,3 +66,5 @@ impl TokenWithBugs {
String::from_str(&e, "TKN")
}
}

mod test;
32 changes: 32 additions & 0 deletions contracts/token-with-bugs/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![cfg(test)]
use super::*;
use soroban_sdk::{testutils::Address as _, Env};

#[test]
fn test_mint_admin() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, TokenWithBugs);
let client = TokenWithBugsClient::new(&env, &contract_id);
let admin = Address::generate(&env);
client.initialize(&admin, &String::from_str(&env, "Token"), &String::from_str(&env, "TKN"));

let to = Address::generate(&env);
client.mint(&admin, &to, &1000);
assert_eq!(client.balance(&to), 1000);
}

#[test]
#[should_panic(expected = "not admin")]
fn test_mint_non_admin() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, TokenWithBugs);
let client = TokenWithBugsClient::new(&env, &contract_id);
let admin = Address::generate(&env);
client.initialize(&admin, &String::from_str(&env, "Token"), &String::from_str(&env, "TKN"));

let to = Address::generate(&env);
let not_admin = Address::generate(&env);
client.mint(&not_admin, &to, &1000);
}
Loading