Skip to content

feat(oft): deploy OFT proxies via CreateX CREATE3 for cross-chain deterministic addresses - #18

Closed
ocakciomursel-pixel wants to merge 2 commits into
GoodDollar:masterfrom
ocakciomursel-pixel:feat/oft-createx-deploy
Closed

ocakciomursel-pixel wants to merge 2 commits into
GoodDollar:masterfrom
ocakciomursel-pixel:feat/oft-createx-deploy

Conversation

@ocakciomursel-pixel

@ocakciomursel-pixel ocakciomursel-pixel commented Oct 18, 2026 •

Copy link
Copy Markdown

Summary

Replaces GoodProtocol's chain-specific ProxyFactory1967 in deployOFT.ts with the canonical CreateX universal factory (xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed) using deployCreate3AndInit so GoodDollarOFTAdapter and GoodDollarOFTMinterBurner proxy addresses are deterministic and identical across all target chains (Celo, XDC, Ethereum, Fuse, etc.).

Closes #16

Why

  • GoodProtocol's ProxyFactory1967 is deployed at different addresses across networks (e.g. xD86c... on Celo vs x5BE3... on XDC), which caused deployCode (CREATE2) proxy addresses to differ across chains.
  • CreateX is deployed at the same canonical address (xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed) on Celo, XDC, Ethereum, and Fuse.
  • Using CreateX.deployCreate3AndInit:
    • Preserves Audit Fix H-02 (atomic deploy + initialize & sender-guarded salt): Salts are constructed via �uildCreateXSalt(deployer, label) (<20-byte deployer> || 0x00 || <11-byte keccak256(label)>). CreateX._guard hashes keccak256(abi.encode(msg.sender, salt)) when msg.sender == deployer, preventing any other caller from front-running or squatting the predicted address, and executes ERC1967Proxy.initialize(logic, initData) atomically in the deployment transaction.
    • Cross-chain address consistency (x00 flag): Setting byte 20 of the salt to x00 (RedeployProtectionFlag.False) ensures �lock.chainid is not mixed into the guarded salt, producing the exact same proxy address on every chain for the same deployer.
    • Bytecode-independent (CREATE3): Unlike CREATE2, the CREATE3 proxy address depends only on (CreateX, deployer, salt) and is immune to compiler or metadata changes in ERC1967Proxy.

Changes

  • packages/bridge-contracts/deploy/utils/createX.ts: Added CREATEX_ADDRESS, CREATEX_ABI, �uildCreateXSalt, guardCreateXSalt, and computeCreateXCreate3Address.
  • packages/bridge-contracts/deploy/deployOFT.ts: Updated OFT proxy address prediction and atomic deployment to use CreateX.computeCreate3Address and CreateX.deployCreate3AndInit.
  • packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md: Updated deployment documentation to reference CreateX.deployCreate3AndInit.
  • packages/bridge-contracts/test/GoodDollarOFT.test.ts & GoodDollarOFT.e2e.test.ts: Added unit tests for �uildCreateXSalt, guardCreateXSalt, and computeCreateXCreate3Address, and updated the Celo fork E2E atomic deploy test to exercise the on-chain CreateX contract.

Summary by Sourcery

Deploy OFT proxies with CreateX CREATE3 to provide secure, bytecode-independent addresses that remain identical across target chains.

New Features:

  • Deploy OFT proxies through the canonical CreateX factory with deterministic CREATE3 addresses shared across supported chains.

Bug Fixes:

  • Preserve sender-bound, atomic deployment and initialization protections while eliminating address differences caused by chain-specific proxy factories.

Enhancements:

  • Add reusable CreateX salt construction, guarding, and deterministic address calculation utilities.
  • Handle already-deployed or concurrently deployed proxies during OFT deployment.

Deployment:

  • Require and use the canonical CreateX deployment for cross-chain OFT proxy deployments.

Documentation:

  • Update the OFT deployment guide to document sender-guarded CreateX salts and cross-chain deterministic addresses.

Tests:

  • Add unit coverage for CreateX salt and address helpers and update fork tests to validate atomic CreateX deployment and initialization.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="packages/bridge-contracts/deploy/deployOFT.ts" line_range="145" />
<code_context>
+  const oftAdapterProxySalt = buildCreateXSalt(root.address, `${envPrefix}-GoodDollarOFTAdapterV1`);
+  // Both proxy addresses are known before deployment, which breaks the MinterBurner <-> adapter init cycle
+  const minterBurnerAddress: string = await createX['computeCreate3Address(bytes32)'](
+    guardCreateXSalt(root.address, minterBurnerProxySalt),
   );
-  const oftAdapterProxySalt = ethers.BigNumber.from(
</code_context>
<issue_to_address>
**Proxy address prediction reverts**

When the OFT deployment or Celo fork test predicts a proxy address with the one-argument CreateX method, `guardCreateXSalt(...)` has already applied CreateX’s sender guard, and `computeCreate3Address(bytes32)` applies it again, so the salt’s sender prefix no longer matches and prediction reverts before proxy deployment.

Pass the raw proxy salt to `computeCreate3Address(bytes32)` and let CreateX apply its guard once.

Also at `packages/bridge-contracts/test/GoodDollarOFT.e2e.test.ts:459-464`, `packages/bridge-contracts/deploy/deployOFT.ts:147-149`.
</issue_to_address>

### Comment 2
<location path="packages/bridge-contracts/deploy/deployOFT.ts" line_range="152-160" />
<code_context>

-  const deployProxyIfMissing = async (name: string, address: string, salt: BigNumber, logic: string, initData: string) => {
+  const deployProxyIfMissing = async (name: string, address: string, salt: string, logic: string, initData: string) => {
     if ((await ethers.provider.getCode(address)) !== '0x') {
       console.log(`${name} proxy already deployed`, address);
     } else {
-      const tx = await proxyFactory.deployProxy(salt, logic, initData);
+      const proxyInitCall = proxyInterface.encodeFunctionData('initialize', [logic, initData]);
+      const tx = await createX.deployCreate3AndInit(salt, proxyArtifact.bytecode, proxyInitCall, {
+        constructorAmount: 0,
+        initCallAmount: 0,
+      });
       await tx.wait();
       console.log(`${name} proxy deployed and initialized`, address, tx.hash);
     }
</code_context>
<issue_to_address>
**Concurrent deployments abort one run**

When two `deployOFT` runs for the same network and signer start before either run's proxy transaction is mined, `deployProxyIfMissing` checks for missing code and then submits `deployCreate3AndInit` without handling a competing deployment. When two runs use the same signer and salt, the first transaction deploys the proxy and the second reverts; the losing run exits before deploying the remaining proxy, so its deployment job reports failure despite the proxy now existing.

Serialize deployments for a network, or handle a failed create by rechecking the predicted address and continuing only if the deployed proxy passes the expected initialization and implementation checks.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and the script now deploys and initializes persistent proxies through CreateX at addresses derived from the new salt scheme. If that derivation or initialization is wrong but succeeds, reverting the script will not remove or repair the deployed proxy state; remediation may require an upgrade or a separate deployment.

Blocking findings: packages/bridge-contracts/deploy/deployOFT.ts:145, packages/bridge-contracts/deploy/deployOFT.ts:160


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread packages/bridge-contracts/deploy/deployOFT.ts
Comment thread packages/bridge-contracts/deploy/deployOFT.ts Outdated
@L03TJ3 L03TJ3 closed this Oct 19, 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.

Use XCreate contracts for deterministic address

2 participants