Partial Defender Deployment Fails to Maintain Deterministic Address

When a Hardhat Upgrades + Defender deployment partially completes (implementation deployed, proxy not), there is no clean recovery path that preserves the expected deterministic proxy address.

:laptop: Environment

@openzeppelin/hardhat-upgrades: "^3.9.1"
@openzeppelin/defender-sdk: "^2.5.0"
Network: BSC Mainnet
Deployment method: defender.deployProxy() with CREATE2

:memo:Details

We deploy the same set of admin contracts across multiple chains and require identical addresses on each chain. This works correctly when deployments complete successfully.

Problem
During deployment of 4 contracts (Collateral, Coordinator, Factory, and Beacon) to BSC mainnet via Defender:

  1. Collateral and Coordinator deployed successfully with the expected deterministic address
  2. Factory implementation was deployed successfully with the expected deterministic address
  3. Factory proxy was NOT deployed — the deployment did not run to completion
    Recovery Attempt
  4. Set redeployImplementation: "never" in deploy options to skip re-deploying the implementation
  5. Manually created the network manifest file (bsc.json) by:
    • Replicating the partial failure scenario locally
    • Updating addresses, txHashes, and remoteDeploymentIds with correct values from Defender API
  6. Re-ran deployment

Result

  • Factory proxy deployed successfully and Beacon as well. (Beacon's address is dependent on Factory proxy address)
  • However, the proxy address differs from the expected address (i.e., doesn't match the address that would have been generated if the original deployment had completed)
    Expected Behavior
    When recovering from a partial deployment where the implementation exists but the proxy does not, the system should be able to deploy the proxy with the same address it would have originally received.

Questions

  1. Is there a supported way to resume a partial Defender deployment that preserves the deterministic proxy address?
  2. Does the proxy address calculation depend on any state beyond what's stored in the network manifest file (e.g., nonce, Defender-side state)?
  3. Is there a way to predict/specify the proxy address when using CREATE2 through Defender?

Additional Context

  • We're using Defender's managed deployment flow, not direct contract deployment
  • The CREATE2 salt calculation may depend on Defender-internal state we don't have visibility into
  • While reviewing the transaction logs on BNB Chain, we noticed an AdminChanged event emitted during the failed deployment. When comparing this to the equivalent deployment on another chain (where the expected deterministic address was produced) the newAdmin value differs. We suspect this discrepancy may relate to the ProxyAdmin used internally by OpenZeppelin during proxy creation. However, it's unclear whether there is a way to explicitly enforce a specific admin address in order to guarantee the expected deterministic transparent proxy address

Any guidance on proper recovery procedures for partial deployments would be appreciated.