Title: fix(factory): atomically deploy and initialize pools to close admin-hijack front-running window - #105
Merged
prodbycorne merged 3 commits intoJul 26, 2026
Conversation
✅ Deploy Preview for sdcontracts ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
…initialize # Conflicts: # soroban/contracts/factory/Cargo.toml # soroban/contracts/factory/src/lib.rs # soroban/contracts/factory/src/test.rs # soroban/contracts/factory/test_snapshots/test/test_admin_getter_returns_stored_address.1.json # soroban/contracts/factory/test_snapshots/test/test_create_pool_emits_pool_crtd_event.1.json # soroban/contracts/factory/test_snapshots/test/test_create_pool_emits_pool_crtd_event_with_payload.1.json # soroban/contracts/factory/test_snapshots/test/test_create_pool_increments_count_after_each_pool.1.json # soroban/contracts/factory/test_snapshots/test/test_create_pool_records_zero_daily_rate.1.json # soroban/contracts/factory/test_snapshots/test/test_create_pool_rejects_unmatched_non_admin_auth.1.json # soroban/contracts/factory/test_snapshots/test/test_create_pool_returns_incrementing_ids.1.json # soroban/contracts/factory/test_snapshots/test/test_create_pool_uses_deterministic_pool_addresses.1.json # soroban/contracts/factory/test_snapshots/test/test_double_initialize_returns_error.1.json # soroban/contracts/factory/test_snapshots/test/test_get_pool_bumps_pool_record_ttl.1.json # soroban/contracts/factory/test_snapshots/test/test_get_pool_returns_correct_record.1.json # soroban/contracts/factory/test_snapshots/test/test_get_pool_returns_error_on_missing_id.1.json # soroban/contracts/factory/test_snapshots/test/test_get_pools_by_asset_returns_empty_when_no_pools.1.json # soroban/contracts/factory/test_snapshots/test/test_get_pools_by_asset_returns_matching_ids.1.json # soroban/contracts/factory/test_snapshots/test/test_get_pools_by_asset_unknown_asset_returns_empty.1.json # soroban/contracts/factory/test_snapshots/test/test_initialize_sets_admin.1.json # soroban/contracts/factory/test_snapshots/test/test_list_pools_caps_limit_at_twenty.1.json # soroban/contracts/factory/test_snapshots/test/test_list_pools_returns_empty_when_start_is_beyond_count.1.json # soroban/contracts/factory/test_snapshots/test/test_list_pools_returns_first_page.1.json # soroban/contracts/factory/test_snapshots/test/test_list_pools_returns_partial_last_page.1.json # soroban/contracts/factory/test_snapshots/test/test_list_pools_returns_second_page.1.json # soroban/contracts/factory/test_snapshots/test/test_multiple_pools_stored_independently.1.json # soroban/contracts/factory/test_snapshots/test/test_old_admin_cannot_create_pool_after_transfer_but_new_admin_can.1.json # soroban/contracts/factory/test_snapshots/test/test_pool_count_zero_after_initialize.1.json # soroban/contracts/factory/test_snapshots/test/test_pool_wasm_hash_returns_stored_hash.1.json # soroban/contracts/factory/test_snapshots/test/test_transfer_admin_changes_admin.1.json # soroban/contracts/factory/test_snapshots/test/test_transfer_admin_emits_event_with_old_and_new_admin.1.json # soroban/contracts/factory/test_snapshots/test/test_transfer_admin_non_admin_rejected.1.json # soroban/contracts/factory/tests/factory_pool_integration.rs
Contributor
Author
|
@prodbycorne Please Review |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #79
create_poolnow deploys and initializes the pool atomically —deploy_v2and a call to the pool'sinitializehappen inside the same invocation, so there's no externally-observable window where a deployed pool exists but is uninitialized for an attacker to race.Why this uses
env.invoke_contractwith raw args, not a typedFarmingPoolClientAn earlier version of this fix used
FarmingPoolClient::new(&env, &pool_address).initialize(...). That fails to link:farming-pool's#[contractimpl]macro exportsadminandtransfer_adminas top-level contract functions, and linking that crate intofactory's own cdylib (which exports functions of the same names) collides at link time:rust-lld: error: duplicate symbol: admin
rust-lld: error: duplicate symbol: transfer_admin
cargo build/cargo testnever catch this — they only compile therlibcrate type, nevercdylib. The actual deployable target has to be built explicitly to catch it:cargo build -p factory --target wasm32v1-none --release
This PR builds
initialize's call viaenv.invoke_contractwith rawValarguments instead, avoiding the typed client and the symbol collision entirely. Verified by running the real WASM build target, not justcargo build/cargo test— confirmed clean both before and after this change.Since
env.invoke_contractdoesn't needFarmingPoolClient,farming-poolmoved back to[dev-dependencies]infactory/Cargo.toml(used only by tests, not at runtime) — this closes the collision risk for good, not just foradmin/transfer_adminspecifically.Design decisions
load_admin), not a caller-supplied delegate. Closes the front-running window, which is this issue's scope; per-pool admin delegation is#80's separate concern, not addressed here.assetis the same variable passed to bothPoolRecord.assetand the pool'sinitializecall — they cannot diverge, closing the stake_token-hijack variant the issue also flags.#80-scoped additions from an existing unmerged commit that solves this same collision (a newglobal_multiplierparameter,daily_rate→credit_rateconversion,Resultreturn type) — those are out of scope for#79.Tests
test_create_pool_pool_admin_is_set_atomically—admin()on the deployed pool equals the intended admin immediately aftercreate_poolreturns.test_third_party_cannot_reinitialize_deployed_pool— an unrelated address'sinitializeattempt on the deployed pool returnsPoolError::AlreadyInitializedas a typed error, not a panic; confirms admin is unchanged.#87's real-WASM integration test infrastructure.Verification
cargo build --workspacecargo build -p factory --target wasm32v1-none --release(the real gate)cargo test --workspacecargo clippy -p factory --all-targetsCross-reference
Same root cause as #78 — resolving together as the issue requests.
Task completed
@prodbycorne Please Review