feat(factory): permissionless deploy_treasury and constructor-bound wasm hash - #9
Merged
Merged
Conversation
…asm hash Org creation no longer depends on a single operator account, and the treasury wasm hash can no longer be front-run. Permissionless deployment: - deploy_treasury drops deployer.require_auth(); the org's own admin is the only required signer. - The deployer concept is removed entirely (DataKey::Deployer, the initialize parameter, NotDeployer). - Spam limit: each admin can deploy once per DEPLOY_COOLDOWN_LEDGERS (720 ledgers, ~1h), tracked in temporary storage. Failed deploys do not start the cooldown. New error DeployCooldown = 6. Atomic wasm hash: - initialize is replaced by __constructor(wasm_hash), so the hash is set in the transaction that creates the factory. The host refuses direct calls to reserved __ functions, so it cannot be set or replaced later. - deploy.sh passes the hash as a constructor argument; there is no separate initialize step. Error codes 1 (NotInitialized), 2 (AlreadyInitialized) and 3 (NotDeployer) are retired and must not be reused. Tests: 17 factory + 47 treasury pass, including front-run attempts via initialize and __constructor, factory creation without a hash, admin-only auth, and the cooldown boundaries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XtRyDqAujrnapqq74h27Y
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.
Summary
Two changes to the factory contract:
deploy_treasuryno longer requires the singledeployeraccount to sign; the new org's ownadminis the only required signer. The deployer concept is removed completely.initializeis replaced by__constructor(wasm_hash), so the treasury wasm hash is set in the same transaction that creates the factory. Previously anyone could callinitializefirst after deployment and choose the hash.Not deployed anywhere. The existing testnet factories are unchanged.
For review: spam limit
Opening up
deploy_treasuryneeded abuse mitigation. I chose a per-admin cooldown: one successful deploy per admin everyDEPLOY_COOLDOWN_LEDGERS(720 ledgers, about an hour). A failed deploy does not start it.Changes
contracts/factory/src/lib.rs__constructor(wasm_hash)replacesinitialize.deploy_treasuryrequires onlyadmin.require_auth()and enforces the cooldown.pub const DEPLOY_COOLDOWN_LEDGERS: u32 = 720.errors.rs: newDeployCooldown = 6. Codes 1 (NotInitialized), 2 (AlreadyInitialized) and 3 (NotDeployer) are retired and not reused, because clients may still decode them.types.rs:DataKey::Deployerremoved;LastDeploy(Address)added, kept in temporary storage.scripts/deploy.sh: deploys the factory with-- --wasm_hash <hash>, with no separateinitializestep.SECURITY.md, script comments: describe the new model.Why the front-run window is closed
__constructoronly while creating the contract (soroban-env-host 26.1.3,host/lifecycle.rs:81-86).__-prefixed function ("can't invoke a reserved function directly",host/frame.rs:907-921).Tests
stellar contract buildthencargo test --workspaceon the pinned Rust 1.92.0: 17 factory + 47 treasury = 64 passed, 0 failed.New or changed factory tests:
initializeor__constructorafter deploy, with all auth mocked in the attacker's favour, fails. A real treasury still deploys afterwards, which proves the hash was not replaced.Error(Auth, InvalidAction).Follow-ups (not in this PR)
initializewrapper; updateFactoryError(remove codes 1–3, add 6 with a message). The existingdeployTreasuryargument-order bug (it sendsadmin, name; the contract takesname, admin) also still needs fixing.NEXT_PUBLIC_FACTORY_CONTRACT_ID/FACTORY_CONTRACT_ID, the README address andscripts/.envat it.scripts/generate-issues.sh: still propose an event frominitialize.🤖 Generated with Claude Code