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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions contracts/allowlist/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ soroban-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
proptest = "1.4"

[[test]]
name = "auth_boundary"
Expand Down
53 changes: 53 additions & 0 deletions contracts/allowlist/tests/proptest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

use soroban_sdk::{testutils::Address as _, Address, Env, Symbol, Vec};
use allowlist::{AllowlistContract, AllowlistContractClient, AllowlistError};
use proptest::prelude::*;

struct Ctx {
env: Env,
Expand Down Expand Up @@ -308,3 +309,55 @@ fn batch_is_allow_duplicates() {
assert!(*is_allowed, "duplicate address should still be allowed");
}
}

proptest! {
#![proptest_config(ProptestConfig {
cases: 32,
.. ProptestConfig::default()
})]

#[test]
fn batch_is_allowed_properties(
allowlist_len in 1usize..=5,
arbitrary_indices in prop::collection::vec(0usize..10, 0..=10),
absent_len in 1usize..=5,
) {
let ctx = Ctx::new(10);
let list_id = ctx.sym("batch_props");
ctx.client.create_allowlist(&ctx.admin, &list_id);

let mut members = Vec::new(&ctx.env);
for index in 0..allowlist_len {
members.push_back(ctx.user(index as u32));
}
ctx.client.add_addresses(&ctx.admin, &list_id, &members);

for scenario in 0u8..5 {
let query_indices: std::vec::Vec<usize> = match scenario {
0 => std::vec::Vec::new(),
// One more address than the allowlist contains.
1 => (0..=allowlist_len).collect(),
// The query consists entirely of allowlisted addresses.
2 => (0..allowlist_len).collect(),
// These indices are outside the allowlist's 0..5 member range.
3 => (5..5 + absent_len).collect(),
// Arbitrary inputs also exercise duplicates and mixed membership.
_ => arbitrary_indices.clone(),
};

let mut query = Vec::new(&ctx.env);
for index in &query_indices {
query.push_back(ctx.user(*index as u32));
}

let results = ctx.client.batch_is_allowed(&list_id, &query);
prop_assert_eq!(results.len() as usize, query_indices.len());

for (position, index) in query_indices.iter().enumerate() {
let (address, allowed) = results.get(position as u32).unwrap();
prop_assert_eq!(address, ctx.user(*index as u32));
prop_assert_eq!(allowed, *index < allowlist_len);
}
}
}
}
166 changes: 83 additions & 83 deletions contracts/markets/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,83 +1,83 @@
use soroban_sdk::contracterror;
/// A stable catalog of errors for the markets smart contract.
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum ContractError {
/// Action is unauthorized; typically thrown when an admin action is invoked by a non-admin.
Unauthorized = 1,
/// Market not found; thrown when querying or interacting with a non-existent market.
MarketNotFound = 2,
/// Market is closed; thrown when trying to interact with a market that has already ended.
MarketClosed = 3,
/// Market already resolved; thrown when attempting to resolve a market more than once.
MarketAlreadyResolved = 4,
/// Market not resolved; thrown when attempting to claim winnings before resolution.
MarketNotResolved = 5,
/// Invalid outcome; thrown when the provided outcome is not supported by the market.
InvalidOutcome = 6,
/// Invalid configuration; thrown when market setup parameters are out of bounds.
InvalidConfig = 7,
/// Overflow; thrown when math operations overflow, preventing unsafe state changes.
Overflow = 8,
/// Stake too small; thrown when a deposit or bet is below the minimum threshold.
StakeTooSmall = 9,
/// Invalid State; thrown when a generic state transition fails.
InvalidState = 10,
/// Admin action timelocked; thrown when an admin action is invoked before the cooldown has elapsed.
AdminActionTimelocked = 11,
// ===== ADMIN-SPECIFIC ERROR VARIANTS (12-19) =====
/// Admin cooldown period is still active; the action cannot be executed yet.
AdminCooldownActive = 12,
/// The provided admin address is invalid (e.g., zero address or self-referential).
AdminAddressInvalid = 13,
/// The requested admin operation is not permitted in the current contract state.
AdminOperationNotPermitted = 14,
/// Winnings already claimed; thrown when a winner attempts to claim a second time
/// for the same market. Guards the checks-effects-interactions invariant in
/// `claim_winnings`.
AlreadyClaimed = 15,
/// Proposal not found; thrown when interacting with a non-existent pause proposal.
ProposalNotFound = 16,
/// Duplicate approval; thrown when an admin attempts to approve the same proposal twice.
ProposalAlreadyApproved = 17,
/// Proposal not executable; timelock has not expired or approval threshold not met.
ProposalNotExecutable = 18,
/// Invalid payout curve configuration.
InvalidPayoutCurve = 19,
/// Insufficient balance for withdrawal or operation.
InsufficientBalance = 20,
/// The configured maximum number of markets has been reached.
MarketLimitReached = 21,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_variants() {
assert_eq!(ContractError::Unauthorized as u32, 1);
assert_eq!(ContractError::MarketNotFound as u32, 2);
assert_eq!(ContractError::MarketClosed as u32, 3);
assert_eq!(ContractError::MarketAlreadyResolved as u32, 4);
assert_eq!(ContractError::MarketNotResolved as u32, 5);
assert_eq!(ContractError::InvalidOutcome as u32, 6);
assert_eq!(ContractError::InvalidConfig as u32, 7);
assert_eq!(ContractError::Overflow as u32, 8);
assert_eq!(ContractError::StakeTooSmall as u32, 9);
assert_eq!(ContractError::InvalidState as u32, 10);
assert_eq!(ContractError::AdminActionTimelocked as u32, 11);
assert_eq!(ContractError::AdminCooldownActive as u32, 12);
assert_eq!(ContractError::AdminAddressInvalid as u32, 13);
assert_eq!(ContractError::AdminOperationNotPermitted as u32, 14);
assert_eq!(ContractError::AlreadyClaimed as u32, 15);
assert_eq!(ContractError::ProposalNotFound as u32, 16);
assert_eq!(ContractError::ProposalAlreadyApproved as u32, 17);
assert_eq!(ContractError::ProposalNotExecutable as u32, 18);
assert_eq!(ContractError::InvalidPayoutCurve as u32, 19);
assert_eq!(ContractError::InsufficientBalance as u32, 20);
assert_eq!(ContractError::MarketLimitReached as u32, 21);
}
}
use soroban_sdk::contracterror;

/// A stable catalog of errors for the markets smart contract.
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum ContractError {
/// Action is unauthorized; typically thrown when an admin action is invoked by a non-admin.
Unauthorized = 1,
/// Market not found; thrown when querying or interacting with a non-existent market.
MarketNotFound = 2,
/// Market is closed; thrown when trying to interact with a market that has already ended.
MarketClosed = 3,
/// Market already resolved; thrown when attempting to resolve a market more than once.
MarketAlreadyResolved = 4,
/// Market not resolved; thrown when attempting to claim winnings before resolution.
MarketNotResolved = 5,
/// Invalid outcome; thrown when the provided outcome is not supported by the market.
InvalidOutcome = 6,
/// Invalid configuration; thrown when market setup parameters are out of bounds.
InvalidConfig = 7,
/// Overflow; thrown when math operations overflow, preventing unsafe state changes.
Overflow = 8,
/// Stake too small; thrown when a deposit or bet is below the minimum threshold.
StakeTooSmall = 9,
/// Invalid State; thrown when a generic state transition fails.
InvalidState = 10,
/// Admin action timelocked; thrown when an admin action is invoked before the cooldown has elapsed.
AdminActionTimelocked = 11,
// ===== ADMIN-SPECIFIC ERROR VARIANTS (12-19) =====
/// Admin cooldown period is still active; the action cannot be executed yet.
AdminCooldownActive = 12,
/// The provided admin address is invalid (e.g., zero address or self-referential).
AdminAddressInvalid = 13,
/// The requested admin operation is not permitted in the current contract state.
AdminOperationNotPermitted = 14,
/// Winnings already claimed; thrown when a winner attempts to claim a second time
/// for the same market. Guards the checks-effects-interactions invariant in
/// `claim_winnings`.
AlreadyClaimed = 15,
/// Proposal not found; thrown when interacting with a non-existent pause proposal.
ProposalNotFound = 16,
/// Duplicate approval; thrown when an admin attempts to approve the same proposal twice.
ProposalAlreadyApproved = 17,
/// Proposal not executable; timelock has not expired or approval threshold not met.
ProposalNotExecutable = 18,
/// Invalid payout curve configuration.
InvalidPayoutCurve = 19,
/// Insufficient balance for withdrawal or operation.
InsufficientBalance = 20,
/// The configured maximum number of markets has been reached.
MarketLimitReached = 21,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_error_variants() {
assert_eq!(ContractError::Unauthorized as u32, 1);
assert_eq!(ContractError::MarketNotFound as u32, 2);
assert_eq!(ContractError::MarketClosed as u32, 3);
assert_eq!(ContractError::MarketAlreadyResolved as u32, 4);
assert_eq!(ContractError::MarketNotResolved as u32, 5);
assert_eq!(ContractError::InvalidOutcome as u32, 6);
assert_eq!(ContractError::InvalidConfig as u32, 7);
assert_eq!(ContractError::Overflow as u32, 8);
assert_eq!(ContractError::StakeTooSmall as u32, 9);
assert_eq!(ContractError::InvalidState as u32, 10);
assert_eq!(ContractError::AdminActionTimelocked as u32, 11);
assert_eq!(ContractError::AdminCooldownActive as u32, 12);
assert_eq!(ContractError::AdminAddressInvalid as u32, 13);
assert_eq!(ContractError::AdminOperationNotPermitted as u32, 14);
assert_eq!(ContractError::AlreadyClaimed as u32, 15);
assert_eq!(ContractError::ProposalNotFound as u32, 16);
assert_eq!(ContractError::ProposalAlreadyApproved as u32, 17);
assert_eq!(ContractError::ProposalNotExecutable as u32, 18);
assert_eq!(ContractError::InvalidPayoutCurve as u32, 19);
assert_eq!(ContractError::InsufficientBalance as u32, 20);
assert_eq!(ContractError::MarketLimitReached as u32, 21);
}
}
Loading
Loading