Skip to content

chore: deploy counterfactuals to all chains#1386

Open
tbwebb22 wants to merge 24 commits intomasterfrom
taylor/counterfactual-oft-deployments
Open

chore: deploy counterfactuals to all chains#1386
tbwebb22 wants to merge 24 commits intomasterfrom
taylor/counterfactual-oft-deployments

Conversation

@tbwebb22
Copy link
Copy Markdown
Contributor

@tbwebb22 tbwebb22 commented Mar 26, 2026

  • Adds config pattern for deployments, and reading replacing deployment args with reading from constants and deployed addresses
  • Deploys counterfactual contracts to all chains that have SpokePool, CCTP, or OFT deployments.
  • Removes metadata hashing from counterfactual contracts so that their addresses are not dependent on metadata.
  • Adds a script for generating a deployment report for easy verification of contract configurations.

latest deployment report: https://github.com/across-protocol/contracts/blob/taylor/counterfactual-oft-deployments/script/counterfactual/deployment-report.md

@tbwebb22 tbwebb22 requested review from fusmanii and grasphoper April 2, 2026 16:36
@tbwebb22 tbwebb22 marked this pull request as ready for review April 2, 2026 16:41
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97024bcf46

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

) internal {
// Append `|| true` so that non-fatal failures (e.g. etherscan verification
// timing out) don't cause ffi to revert and halt subsequent deployments.
string memory cmd = string.concat(
"forge script ",
"FOUNDRY_PROFILE=counterfactual forge script ",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve ZK profile when running child deploy scripts

_runForgeScript now forces FOUNDRY_PROFILE=counterfactual for every sub-script invocation, which breaks deployments on ZK_STACK chains (for example Lens/232) because scripts inheriting DeploymentUtils assert that ZK_STACK runs use the zksync profile. In that environment each child deploy script reverts before deployment, and this wrapper still reports success paths because failures are masked later in the shell command. This makes DeployAllCounterfactual unreliable for the exact multi-chain rollout this change targets.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm. Can we actually just pass the String profile as arg to this fn instead of hardcoding a profile here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One related thing I'm thinking is: can we make these "call foundry script" and "call cast" universal enough to put in their own script/utils/Forge.s.sol file?

So that other scripts could reuse these

Comment on lines +152 to 153
abi.encodePacked(type(AdminWithdrawManager).creationCode, abi.encode(deployer, deployer, signer))
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the same deployer index for AdminWithdrawManager init code

The AdminWithdrawManager CREATE2 prediction now depends on deployer (abi.encode(deployer, deployer, signer)), but this script derives deployer from mnemonic index 0 while the called DeployAdminWithdrawManager script derives from DEPLOYER_INDEX. If operators set DEPLOYER_INDEX to a non-zero value, the actual deployed init code/address diverges from predictedAdmin, so subsequent existence checks and role-transfer calls target the wrong address and can fail unexpectedly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

grasphoper
grasphoper previously approved these changes Apr 3, 2026
Copy link
Copy Markdown
Collaborator

@grasphoper grasphoper left a comment

Choose a reason for hiding this comment

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

Looks good. A few nits on the checker script and otherwise.
Deployments look good, subject to changes you posted in Slack.

I did end up creating a new foundry-based checker script to double-check all configs (couldn't convince myself to trust the .sh one heh), PTAL here https://github.com/across-protocol/contracts/pull/1397/changes

IMO the foundry one is better for readability

Comment on lines +26 to +35
script_name_for() {
case "$1" in
CounterfactualDeposit) echo "DeployCounterfactualDeposit" ;;
CounterfactualDepositFactory) echo "DeployCounterfactualDepositFactory" ;;
WithdrawImplementation) echo "DeployWithdrawImplementation" ;;
CounterfactualDepositSpokePool) echo "DeployCounterfactualDepositSpokePool" ;;
CounterfactualDepositCCTP) echo "DeployCounterfactualDepositCCTP" ;;
CounterfactualDepositOFT) echo "DeployCounterfactualDepositOFT" ;;
AdminWithdrawManager) echo "DeployAdminWithdrawManager" ;;
*) echo "" ;;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hm, this looks fragile. If we ever change the name of the deploy script. We should put AGENTS.md / CLAUDE.md here mentioning the dependency or something


rpc_url_for_chain() {
local chain_id="$1"
python3 -c "import os; print(os.environ.get('NODE_URL_${chain_id}', ''))"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is odd. Can't a bash script read env vars normally?


block_explorer() {
local chain_id="$1"
# Override for chains where constants.json has the wrong explorer.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hm interesting. Should we fix constants.json instead?

return
fi

jq -r '.transactions[0].contractAddress // ""' "$broadcast_file"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this going to be true for every deployment script in the future too? *first TX being the deployment one

return
fi

jq -r '.transactions[0].arguments // [] | .[]' "$broadcast_file"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same as above. We might want to find a CREATE/CREATE2 tx instead of using index 0 always

Comment on lines +128 to +157
python3 -c "
import re, sys

chain_id = '${chain_id}'
key = '${key}'
in_chain = False
in_address = False

with open('${CONFIG_FILE}') as f:
for line in f:
line = line.strip()
if line == f'[{chain_id}]':
in_chain = True
in_address = False
continue
if line == f'[{chain_id}.address]':
in_chain = True
in_address = True
continue
if line.startswith('[') and line != f'[{chain_id}]' and line != f'[{chain_id}.address]':
if in_chain:
in_chain = False
in_address = False
if in_address:
m = re.match(rf'^{key}\s*=\s*\"(.+?)\"', line)
if m:
print(m.group(1))
sys.exit(0)
print('')
" 2>/dev/null
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah these python excerpts are unfortunate.

.sh + .py in a contracts repo is a nasty combo 😄
I wonder if we should create some standard way of creating scripts like this one. cast is callable from any language we could want. I remember you had a foundry script for a similar purpose before. That script could also read constants easier

Wdyt?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Actually if we did use a foundry script instead of .sh, we could conveniently read all of the latest addresses from config.toml, instead of fishing for them in the broadcast/ folder

) internal {
// Append `|| true` so that non-fatal failures (e.g. etherscan verification
// timing out) don't cause ffi to revert and halt subsequent deployments.
string memory cmd = string.concat(
"forge script ",
"FOUNDRY_PROFILE=counterfactual forge script ",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm. Can we actually just pass the String profile as arg to this fn instead of hardcoding a profile here?

) internal {
// Append `|| true` so that non-fatal failures (e.g. etherscan verification
// timing out) don't cause ffi to revert and halt subsequent deployments.
string memory cmd = string.concat(
"forge script ",
"FOUNDRY_PROFILE=counterfactual forge script ",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One related thing I'm thinking is: can we make these "call foundry script" and "call cast" universal enough to put in their own script/utils/Forge.s.sol file?

So that other scripts could reuse these

@@ -11,4 +11,4 @@
"lib/sp1-contracts": {
"rev": "512b5e029abc27f6e46a3c7eba220dac83ecc306"
}
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can fix via formatting prob

Comment on lines +40 to +44
[profile.counterfactual-zksync.zksync]
compile = true
fallback_oz = true
mode = '3'
zksolc = "1.5.15"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A Q: does this mean that we're compiling to the EraVM variant of the bytecode or is it unrelated?

@tbwebb22
Copy link
Copy Markdown
Contributor Author

tbwebb22 commented Apr 3, 2026

Hmm. Can we actually just pass the String profile as arg to this fn instead of hardcoding a profile here?

yep I like that
a39bae5

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.

2 participants