Skip to content
Open
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
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,22 @@ jobs:
- name: Build contract (release)
run: cargo rustc --manifest-path=contracts/Cargo.toml --crate-type=cdylib --target=wasm32v1-none --release --locked

- name: Optimize WASM for deployment
run: |
WASM=target/wasm32v1-none/release/xelma_contract.wasm
BEFORE=$(stat -c%s "$WASM")
# Install wasm-opt from binaryen if not present
if ! command -v wasm-opt &>/dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq binaryen >/dev/null 2>&1 || true
fi
if command -v wasm-opt &>/dev/null; then
wasm-opt -Oz "$WASM" -o "$WASM"
AFTER=$(stat -c%s "$WASM")
echo "WASM optimized: $BEFORE -> $AFTER bytes (saved $(( BEFORE - AFTER )) bytes)"
else
echo "wasm-opt not available, skipping optimization"
fi

- name: Check WASM artifact exists
run: |
if [ ! -f "target/wasm32v1-none/release/xelma_contract.wasm" ]; then
Expand Down
111 changes: 111 additions & 0 deletions .github/workflows/diff-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Differential Verification — settlement_math

on:
push:
branches: [main, develop]
paths:
- 'contracts/src/settlement_math.rs'
- 'contracts/src/tests/diff_verify.rs'
- 'contracts/src/math_common.rs'
pull_request:
paths:
- 'contracts/src/settlement_math.rs'
- 'contracts/src/tests/diff_verify.rs'
- 'contracts/src/math_common.rs'
workflow_dispatch:
inputs:
mode:
description: 'Execution mode: fast or extended'
required: false
default: 'fast'
seed:
description: 'Base seed for deterministic replay (blank = random)'
required: false

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
diff-verify:
name: "Differential Verification (${{ github.event.inputs.mode || 'fast' }})"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cache cargo registry & build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-diff-verify-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-diff-verify-

- name: Fixed regression cases (always run)
run: |
cargo test --package xelma-contract --lib \
tests::diff_verify::differential_verify_fixed_regression \
--features testutils -- --nocapture

- name: Fuzz — fast (PR CI default)
if: ${{ github.event.inputs.mode != 'extended' }}
env:
DIFF_VERIFY_MODE: fast
SEED: ${{ github.event.inputs.seed || '3735928559' }}
run: |
cargo test --package xelma-contract --lib \
tests::diff_verify::differential_verify_fuzz \
--features testutils -- --nocapture

- name: Fuzz — extended (nightly / manual)
if: ${{ github.event.inputs.mode == 'extended' }}
env:
DIFF_VERIFY_MODE: extended
SEED: ${{ github.event.inputs.seed || '3735928559' }}
run: |
cargo test --package xelma-contract --lib \
tests::diff_verify::differential_verify_fuzz \
--features testutils -- --nocapture

nightly-extended:
name: "Nightly Extended Diff Verify"
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-diff-verify-nightly-${{ hashFiles('**/Cargo.lock') }}

- name: Fixed regression cases
run: |
cargo test --package xelma-contract --lib \
tests::diff_verify::differential_verify_fixed_regression \
--features testutils -- --nocapture

- name: Extended fuzz (≥1000 cases)
env:
DIFF_VERIFY_MODE: extended
SEED: "3735928559"
run: |
cargo test --package xelma-contract --lib \
tests::diff_verify::differential_verify_fuzz \
--features testutils -- --nocapture
73 changes: 72 additions & 1 deletion bindings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,21 @@ export const ContractError = {
77: {message:"PendingWinningsNotFound"},
/** Pending winnings expiry is not configured. */
78: {message:"ExpiryNotConfigured"},
79: {message:"EarlyCashoutDisabled"},
80: {message:"PositionNotFound"},
81: {message:"InvalidPhaseForCashout"},
82: {message:"WrongModeForCashout"},
55: {message:"RotationDelayNotElapsed"},
83: {message:"ProposalNotFound"},
84: {message:"ProposalExpired"},
85: {message:"GovInvalidState"},
86: {message:"GovUnauthorized"},
87: {message:"ClaimBatchTooLarge"},
88: {message:"DuplicateClaimAddress"},
89: {message:"AccessDenied"},
90: {message:"OracleHeartbeatUnhealthy"},
91: {message:"DisputeWindowExpired"},
92: {message:"ClaimLocked"},
/** Participant is blocked by the active access-control policy. */
79: {message:"AccessDenied"},
/** Governance proposal was not found. */
Expand Down Expand Up @@ -1475,6 +1490,62 @@ export class Client extends ContractClient {
reset_leaderboard_season: this.txFromJSON<Result<u32>>,
get_season_archive: this.txFromJSON<Option<SeasonArchive>>,
get_season_leaderboard_by_wins: this.txFromJSON<Array<SeasonLeaderboardEntry>>,
get_season_leaderboard_by_streak: this.txFromJSON<Array<SeasonLeaderboardEntry>>
get_season_leaderboard_by_streak: this.txFromJSON<Array<SeasonLeaderboardEntry>>,
announce_next_schema: this.txFromJSON<Result<void>>,
get_next_schema: this.txFromJSON<Option<u32>>,
clear_next_schema: this.txFromJSON<Result<void>>,
is_action_allowed: this.txFromJSON<boolean>,
set_deviation_ref_mode: this.txFromJSON<Result<void>>,
get_deviation_ref_mode: this.txFromJSON<u32>,
get_deviation_window_samples: this.txFromJSON<u32>,
get_twap_samples: this.txFromJSON<Array<u128>>,
set_attestation_key: this.txFromJSON<Result<void>>,
get_attestation_key: this.txFromJSON<Option<string>>,
batch_touch_ttl: this.txFromJSON<Result<void>>,
set_access_control_enabled: this.txFromJSON<Result<void>>,
is_access_control_enabled: this.txFromJSON<boolean>,
add_allowlisted: this.txFromJSON<Result<void>>,
remove_allowlisted: this.txFromJSON<Result<void>>,
add_denylisted: this.txFromJSON<Result<void>>,
remove_denylisted: this.txFromJSON<Result<void>>,
is_allowlisted: this.txFromJSON<boolean>,
is_denylisted: this.txFromJSON<boolean>,
get_access_state: this.txFromJSON<u32>,
get_access_policy: this.txFromJSON<u32>,
set_gov_approver: this.txFromJSON<Result<void>>,
get_gov_approver: this.txFromJSON<Option<string>>,
set_gov_proposal_ttl: this.txFromJSON<Result<void>>,
get_gov_proposal_ttl: this.txFromJSON<u32>,
propose_gov_action: this.txFromJSON<Result<void>>,
approve_gov_proposal: this.txFromJSON<Result<void>>,
execute_gov_proposal: this.txFromJSON<Result<void>>,
cancel_gov_proposal: this.txFromJSON<Result<void>>,
get_gov_proposal: this.txFromJSON<Option<PendingConfigChange>>,
set_min_bet: this.txFromJSON<Result<void>>,
schedule_min_bet: this.txFromJSON<Result<void>>,
get_min_bet: this.txFromJSON<Option<i128>>,
schedule_oracle_timestamp_skew: this.txFromJSON<Result<void>>,
get_oracle_timestamp_skew: this.txFromJSON<u64>,
set_pending_winnings_expiry: this.txFromJSON<Result<void>>,
schedule_pending_winnings_expiry: this.txFromJSON<Result<void>>,
get_pending_winnings_expiry: this.txFromJSON<u32>,
reclaim_expired_pending_winnings: this.txFromJSON<Result<void>>,
set_oracle_quorum_config: this.txFromJSON<Result<void>>,
get_oracle_quorum_config: this.txFromJSON<Option<OracleQuorumConfig>>,
get_bet_window_ledgers: this.txFromJSON<u32>,
get_run_window_ledgers: this.txFromJSON<u32>,
set_early_cashout_bps: this.txFromJSON<Result<void>>,
get_early_cashout_bps: this.txFromJSON<Option<u32>>,
resolve_round_multi: this.txFromJSON<Result<void>>,
claim_many: this.txFromJSON<Result<void>>,
cash_out_early: this.txFromJSON<Result<void>>,
set_dispute_ledgers: this.txFromJSON<Result<void>>,
get_dispute_ledgers: this.txFromJSON<Option<u32>>,
void_round: this.txFromJSON<Result<void>>,
finalize_round: this.txFromJSON<Result<void>>,
get_one_sided_policy: this.txFromJSON<Option<u32>>,
get_market_snapshot: this.txFromJSON<Option<RoundPoolStats>>,
set_fee_model: this.txFromJSON<Result<void>>,
get_fee_model: this.txFromJSON<FeeModel>
}
}
2 changes: 1 addition & 1 deletion contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = { workspace = true }
soroban-sdk = { workspace = true, features = ["alloc"] }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
Expand Down
18 changes: 12 additions & 6 deletions contracts/src/access_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,8 @@ pub fn set_access_control_enabled(env: Env, enabled: bool) -> Result<(), Contrac
}

#[allow(deprecated)]
env.events().publish(
(symbol_short!("access"), symbol_short!("mode")),
(enabled,),
);
env.events()
.publish((symbol_short!("access"), symbol_short!("mode")), (enabled,));

Ok(())
}
Expand Down Expand Up @@ -201,9 +199,17 @@ pub fn is_denylisted(env: Env, user: Address) -> bool {
/// Denylist takes precedence over allowlist. An address that is neither marked
/// resolves to `Open`, regardless of whether allowlist mode is enabled.
pub fn get_access_state(env: Env, user: Address) -> AccessState {
if env.storage().persistent().has(&DataKeyScoped::Denylisted(user.clone())) {
if env
.storage()
.persistent()
.has(&DataKeyScoped::Denylisted(user.clone()))
{
AccessState::Denylisted
} else if env.storage().persistent().has(&DataKeyScoped::Allowlisted(user)) {
} else if env
.storage()
.persistent()
.has(&DataKeyScoped::Allowlisted(user))
{
AccessState::Allowlisted
} else {
AccessState::Open
Expand Down
Loading