Skip to content

fix(ops): recover real address on dojo-utils already-deployed path#64

Closed
kariy wants to merge 1 commit into
mainfrom
fix/ops-already-deployed-zero-address
Closed

fix(ops): recover real address on dojo-utils already-deployed path#64
kariy wants to merge 1 commit into
mainfrom
fix/ops-already-deployed-zero-address

Conversation

@kariy
Copy link
Copy Markdown
Member

@kariy kariy commented Apr 22, 2026

Problem

saya-ops core-contract deploy --salt X is not idempotent after PR #63. The second invocation with the same salt emits contract_address: "0x0" in both text and JSON output:

$ SAYA_OPS_OUTPUT=json saya-ops core-contract ... declare-and-deploy-tee-registry-mock --salt 0x7ee
{"command":"...","contract_address":"0x0","salt":"0x7ee","tx_hash":null,"deployed_block":null}

Downstream scripts (our docker-compose init container, our own tests/saya-tee/ if it were to re-run) write 0x0 into bootstrap manifests and the next step (setup-program) panics because there's no contract at the zero address.

Root cause

dojo-utils::Deployer::deploy_via_udc_getcall computes the deterministic UDC-derived contract address, checks is_deployed, and returns Ok(None) when the contract exists. deploy_via_udc maps that to Ok((Felt::ZERO, TransactionResult::Noop)):

// dojo-utils/src/tx/deployer.rs
let (contract_address, call) = match self
    .deploy_via_udc_getcall(class_hash, salt, constructor_calldata, deployer_address)
    .await?
{
    Some(res) => res,
    None => return Ok((Felt::ZERO, TransactionResult::Noop)),  // ← the real address is dropped here
};

dojo-utils internally traces contract_address="0x037189b18..." at that point but throws it away.

Fix

Workaround in saya-ops: when deploy_via_udc returns (Felt::ZERO, Noop), recompute the address locally via starknet::core::utils::get_contract_address using the same inputs dojo-utils used (salt + class_hash + constructor_calldata + deployer_address=Felt::ZERO). That's exactly what dojo-utils computed and discarded, so we end up with the real address.

Only 13 lines, gated on the specific (ZERO, Noop) condition so it's a no-op on any other path.

Verification

Against a local katana L2 with a previously-deployed TEE registry at salt 0x7ee:

# Before
$ SAYA_OPS_OUTPUT=json saya-ops ... declare-and-deploy-tee-registry-mock --salt 0x7ee
{"command":"...","contract_address":"0x0","salt":"0x7ee",...}

# After
$ SAYA_OPS_OUTPUT=json saya-ops ... declare-and-deploy-tee-registry-mock --salt 0x7ee
{"command":"...","contract_address":"0x37189b1807f1358074b70b3dc8ab79167bbf72cff1296286052f6dfe31c8f15","salt":"0x7ee",...}

Follow-up

The proper fix is in dojoengine/dojo — have deploy_via_udc return (real_address, Noop) instead of (ZERO, Noop). Filing that separately once this lands; once merged upstream and saya's dojo-utils git dep advances, the workaround here can be removed.

Test plan

  • cargo check -p saya-ops / cargo build -p saya-ops clean
  • Reproduced the bug against a local katana --dev with a previously-deployed salt
  • Verified the fix returns the correct address (matches what dojo-utils trace-logs internally before discarding)
  • Fresh salts still work unchanged (non-zero contract_address, tx_hash, deployed_block populated)
  • Downstream: katana's docker/scripts/deploy-contracts.sh can pick this up once merged, making the compose bundle idempotent across restarts

🤖 Generated with Claude Code

`dojo-utils::Deployer::deploy_via_udc` computes the deterministic
UDC-derived contract address, checks whether a contract is already
deployed there via `is_deployed`, and returns early with
`Ok((Felt::ZERO, TransactionResult::Noop))` when it is. The real
address is computed and then dropped — callers see zero.

That's a real problem for us: `saya-ops core-contract deploy --salt X`
is meant to be idempotent, but the second invocation with the same salt
emits `contract_address: "0x0"` in both text and JSON output. Consumers
(including our docker-compose init container) then write 0x0 into the
bootstrap manifest and the next step (setup-program) panics on a
nonexistent contract.

Workaround in saya-ops: when we see `(Felt::ZERO, Noop)`, recompute the
address ourselves via `starknet::core::utils::get_contract_address`
using the same inputs dojo-utils used. That's exactly what dojo-utils
computed and discarded.

Verified against a local katana L2 that had already been deployed
against with salt 0x7ee from a prior run:

    Before fix: contract_address: "0x0"
    After fix:  contract_address: "0x37189b1807f1358074b70b3dc8ab79167bbf72cff1296286052f6dfe31c8f15"

This is a local defense; the proper fix is in dojo-utils (return
`(real_address, Noop)` instead of `(ZERO, Noop)`). Filing that
separately against dojoengine/dojo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kariy
Copy link
Copy Markdown
Member Author

kariy commented Apr 22, 2026

Superseded by the root-cause fix in dojo: dojoengine/dojo#3404. Once that merges and saya's dojo-utils git dep advances past it, the already-deployed path returns the correct address without any saya-side workaround.

@kariy kariy closed this Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant