Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions deployments/common/addresses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[
{
"name": "ethereum",
"chainId": 1,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
},
{
"name": "optimism",
"chainId": 10,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
},
{
"name": "unichain",
"chainId": 130,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
},
{
"name": "polygon",
"chainId": 137,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
},
{
"name": "worldchain",
"chainId": 480,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
},
{
"name": "hyperevm",
"chainId": 999,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
},
{
"name": "base",
"chainId": 8453,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
},
{
"name": "arbitrum",
"chainId": 42161,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
},
{
"name": "avalanche",
"chainId": 43114,
"onlyOwnerMulticaller": "0xfc4ef5dff5a5e2c0d381ae073ad7ebb20ac60be2"
}
]
41 changes: 41 additions & 0 deletions deployments/common/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
There is a single deployment script that needs to be triggered for every new chain, `./script/common/OnlyOwnerMulticallerDeployer.s.sol`

The script requires the following environment variables:

- `DEPLOYER_PK`: the private key of the deployer wallet
- `CHAIN`: the chain to deploy on (the available options can be found in `./foundry.toml`)
- `CREATE2_FACTORY`: the address of the `CREATE2` factory to be used for deterministic deployments - the default factory should be deployed at `0x4e59b44847b379578588920ca78fbf26c0b4956c`, in case it's not available on a given chain we should deploy it there or otherwise use a different factory
- `OWNER`: the address that will own the multicaller (must be the same across all chains to produce a canonical deployment address)
- `ETHERSCAN_API_KEY`: the API key needed to verify the contracts on Etherscan-powered explorers

> **Note on canonical addresses:** The deployed address is derived from the CREATE2 factory address, the salt, and the contract creation bytecode (which includes the `OWNER` constructor argument). To get the same address on every chain, ensure `CREATE2_FACTORY`, `SALT`, and `OWNER` are identical. The project's `foundry.toml` sets `bytecode_hash = "none"` and `cbor_metadata = false` to strip non-deterministic compiler metadata from the bytecode.

### Deployment

The deployment can be triggered via the following command:

```bash
forge script ./script/common/OnlyOwnerMulticallerDeployer.s.sol:OnlyOwnerMulticallerDeployer \
--slow \
--multi \
--broadcast \
--verify \
--private-key $DEPLOYER_PK \
--create2-deployer $CREATE2_FACTORY
```

The script will automatically skip deployment if the contract already exists at the predicted address.

### Verification

The above script should do the deployment and verification altogether. However, in cases when the verification failed for some reason, it can be triggered individually via the following command:

```bash
forge verify-contract \
--chain $CHAIN \
--constructor-args $(cast abi-encode "constructor(address)" $OWNER) \
$ONLY_OWNER_MULTICALLER \
./src/common/OnlyOwnerMulticaller.sol:OnlyOwnerMulticaller
```

In case `forge` doesn't have any default explorer for a given chain, make sure to pass the following extra arguments to the `forge verify-contract` command: `--verifier-url $VERIFIER_URL --etherscan-api-key $VERIFIER_API_KEY`.
87 changes: 87 additions & 0 deletions script/common/OnlyOwnerMulticallerDeployer.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";

import {OnlyOwnerMulticaller} from "../../src/common/OnlyOwnerMulticaller.sol";

contract OnlyOwnerMulticallerDeployer is Script {
// Thrown when the predicted address doesn't match the deployed address
error IncorrectContractAddress(address predicted, address actual);

// Modify for vanity address generation
bytes32 public SALT = bytes32(uint256(1));

function setUp() public {}

function run() public {
vm.createSelectFork(vm.envString("CHAIN"));

vm.startBroadcast();

deployOnlyOwnerMulticaller(vm.envAddress("OWNER"));

vm.stopBroadcast();
}

function deployOnlyOwnerMulticaller(address owner) public returns (address) {
console2.log("Deploying OnlyOwnerMulticaller");

address create2Factory = vm.envAddress("CREATE2_FACTORY");

// Compute predicted address
address predictedAddress = address(
uint160(
uint(
keccak256(
abi.encodePacked(
bytes1(0xff),
create2Factory,
SALT,
keccak256(
abi.encodePacked(
type(OnlyOwnerMulticaller).creationCode,
abi.encode(owner)
)
)
)
)
)
)
);

console2.log("Predicted address for OnlyOwnerMulticaller", predictedAddress);

// Verify if the contract has already been deployed
if (_hasBeenDeployed(predictedAddress)) {
console2.log("OnlyOwnerMulticaller was already deployed");
return predictedAddress;
}

// Deploy
OnlyOwnerMulticaller multicaller = new OnlyOwnerMulticaller{salt: SALT}(owner);

// Ensure the predicted and actual addresses match
if (predictedAddress != address(multicaller)) {
revert IncorrectContractAddress(
predictedAddress,
address(multicaller)
);
}

console2.log("OnlyOwnerMulticaller deployed");

return address(multicaller);
}

function _hasBeenDeployed(
address addressToCheck
) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(addressToCheck)
}
return (size > 0);
}
}
Loading