Skip to content

Migrate initialize to a Soroban constructor to close the deploy/init front-running race #33

Description

@miraclesonly

Background / Context

Follow-up from the access-control audit in #30. All three contracts'
initialize(admin, treasury, fee_bps) now call admin.require_auth()
(landed in #30's PR), but that only prevents naming a third-party
address as admin without their consent — it does not prevent an
attacker from front-running the legitimate deployer's initialize
call by simply naming themselves as admin instead, since they
trivially satisfy require_auth() for their own address. Today,
deploy and initialize are two separate transactions
(scripts/deploy.mjs then scripts/invoke.mjs ... initialize, or the
equivalent stellar contract deploy + stellar contract invoke
two-step in the README), which leaves a window between them where the
contract exists on-chain, not-yet-initialized, and callable by anyone.

Problem Statement

No in-contract signature check can close this race, because the
vulnerability is purely about transaction ordering ("whoever's
initialize call lands first wins"), not about verifying a specific
address's consent. The structural fix is Soroban's native constructor
support (#[contractimpl] impl ... { pub fn __constructor(...) },
available since roughly soroban-sdk 21/22 — this repo is already on
26.1.0), which runs automatically as part of the same host operation
that creates the contract instance. That makes deploy and init atomic
in a single transaction, so there is no separate initialize call and
therefore no window for a racer to land first.

Requirements

  • Migrate initialize to __constructor in all three contracts
    (contracts/escrow, contracts/milestones, contracts/maintenance-pool).
  • Update scripts/deploy.mjs / scripts/invoke.mjs and the Makefile
    deploy targets to pass constructor args at deploy time instead of a
    separate initialize invocation.
  • Update the README's "Build, test, deploy" section and the three
    contracts' function-signature listings accordingly.
  • Decide how to handle the already-initialized testnet deployments
    listed in the README (they'll need redeployment under new contract
    IDs, since a constructor can't be retrofitted onto an already-deployed
    instance).
  • Add/adjust tests to cover construction via the new path (soroban-sdk's
    testutils register helpers support constructor args directly).

Acceptance Criteria

  • All three contracts use __constructor instead of a separately-invoked initialize
  • No window exists between contract creation and full initialization
  • Deploy scripts, Makefile, and README updated to match
  • cargo test --workspace green, wasm32v1-none release build still produces valid wasm
  • Testnet redeployment plan documented (new contract IDs)

Technical Notes / Hints

Difficulty Justification

Requires understanding Soroban's constructor semantics and host-level
guarantees (not just adding a check), and touches the deploy pipeline
end-to-end (scripts, Makefile, README, already-deployed testnet
contracts), so it's a coordinated multi-file change rather than a
single-function fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26documentationImprovements or additions to documentationspikeOpen-ended research/investigation task

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions