Skip to content
Merged
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
368 changes: 191 additions & 177 deletions .gas-snapshot

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions .github/workflows/solidity-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,13 @@ jobs:
*) echo "::error title=Storage layout check failed to run::shell/check_storage_layout.sh could not inspect the contract."
exit 1 ;;
esac

deploy-wizard:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Test deployment wizard
run: python3 test/shell/test_deploy_wizard.py
28 changes: 14 additions & 14 deletions .storage-layout
Original file line number Diff line number Diff line change
Expand Up @@ -25,47 +25,47 @@
"type": "t_mapping(t_bytes32,t_struct(RoleData)22_storage)"
},
{
"astId": 1058,
"astId": 1067,
"contract": "src/StateOracle.sol:StateOracle",
"label": "assertionAdopters",
"offset": 0,
"slot": "3",
"type": "t_mapping(t_address,t_struct(AssertionAdopter)979_storage)"
},
{
"astId": 1064,
"astId": 1073,
"contract": "src/StateOracle.sol:StateOracle",
"label": "adminVerifiers",
"offset": 0,
"slot": "4",
"type": "t_mapping(t_contract(IAdminVerifier)2524,t_bool)"
"type": "t_mapping(t_contract(IAdminVerifier)2650,t_bool)"
},
{
"astId": 1070,
"astId": 1079,
"contract": "src/StateOracle.sol:StateOracle",
"label": "daVerifiers",
"offset": 0,
"slot": "5",
"type": "t_mapping(t_contract(IDAVerifier)2553,t_bool)"
"type": "t_mapping(t_contract(IDAVerifier)2679,t_bool)"
},
{
"astId": 1075,
"astId": 1084,
"contract": "src/StateOracle.sol:StateOracle",
"label": "whitelist",
"offset": 0,
"slot": "6",
"type": "t_mapping(t_address,t_bool)"
},
{
"astId": 1078,
"astId": 1087,
"contract": "src/StateOracle.sol:StateOracle",
"label": "whitelistEnabled",
"offset": 0,
"slot": "7",
"type": "t_bool"
},
{
"astId": 1081,
"astId": 1090,
"contract": "src/StateOracle.sol:StateOracle",
"label": "maxAssertionsPerAA",
"offset": 1,
Expand All @@ -89,12 +89,12 @@
"label": "bytes32",
"numberOfBytes": "32"
},
"t_contract(IAdminVerifier)2524": {
"t_contract(IAdminVerifier)2650": {
"encoding": "inplace",
"label": "contract IAdminVerifier",
"numberOfBytes": "20"
},
"t_contract(IDAVerifier)2553": {
"t_contract(IDAVerifier)2679": {
"encoding": "inplace",
"label": "contract IDAVerifier",
"numberOfBytes": "20"
Expand Down Expand Up @@ -127,16 +127,16 @@
"numberOfBytes": "32",
"value": "t_struct(RoleData)22_storage"
},
"t_mapping(t_contract(IAdminVerifier)2524,t_bool)": {
"t_mapping(t_contract(IAdminVerifier)2650,t_bool)": {
"encoding": "mapping",
"key": "t_contract(IAdminVerifier)2524",
"key": "t_contract(IAdminVerifier)2650",
"label": "mapping(contract IAdminVerifier => bool)",
"numberOfBytes": "32",
"value": "t_bool"
},
"t_mapping(t_contract(IDAVerifier)2553,t_bool)": {
"t_mapping(t_contract(IDAVerifier)2679,t_bool)": {
"encoding": "mapping",
"key": "t_contract(IDAVerifier)2553",
"key": "t_contract(IDAVerifier)2679",
"label": "mapping(contract IDAVerifier => bool)",
"numberOfBytes": "32",
"value": "t_bool"
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: check-storage-layout update-storage-layout check-abi
.PHONY: check-storage-layout deploy update-storage-layout check-abi

check-storage-layout:
@bash shell/check_storage_layout.sh
Expand All @@ -10,3 +10,6 @@ update-storage-layout:
# Compares against ABI_BASE_REF, defaulting to origin/main.
check-abi:
@bash shell/check_abi.sh $(ABI_BASE_REF)

deploy:
./shell/deploy_wizard.sh
63 changes: 56 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ Set the following environment variables before running the deployment scripts:
- `DEPLOY_ADMIN_VERIFIER_OWNER` (true/false)
- `DEPLOY_ADMIN_VERIFIER_WHITELIST` (true/false)
- `ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS` (required when whitelist verifier is enabled)
- `STATE_ORACLE_WHITELIST_ENABLED` (optional, defaults to true)
- `DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE` (optional, defaults to false; testing only)
- `DEPLOYMENT_IS_TESTING` (must be true when the Always Approve verifier is enabled)

The following additional variables apply only to `DeployCoreWithStaging.s.sol`:

Expand All @@ -95,14 +98,28 @@ The following additional variables apply only to `DeployCoreWithStaging.s.sol`:

### Deployment Overview

Running `DeployCore` or `DeployCoreWithCreateX` will:
Running `DeployCore`, `DeployCoreWithCreateX`, or `DeployCoreWithStaging` will:

1. Deploy the DA verifier (ECDSA) and log its address.
2. Deploy the DA verifier (OnChain) and log its address.
3. Deploy `AdminVerifierOwner` if `DEPLOY_ADMIN_VERIFIER_OWNER=true` and log its address.
4. Deploy `AdminVerifierWhitelist` if `DEPLOY_ADMIN_VERIFIER_WHITELIST=true` (using `ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS` as constructor default admin and initial whitelist admin) and log its address.
5. Deploy the `StateOracle` implementation and log its address.
6. Deploy the proxy, initialize it with the configured admin verifiers and DA verifiers, and log the proxy address.
5. Deploy `AdminVerifierAlwaysApprove` if `DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE=true` and `DEPLOYMENT_IS_TESTING=true`, and log its address.
6. Deploy the `StateOracle` implementation and log its address.
7. Deploy the proxy, initialize it with the configured admin verifiers, DA verifiers, and initial whitelist state, and log the proxy address.

`DeployCoreWithStaging` repeats the last two steps for the staging oracle. It inherits the CreateX deployment path, so both State Oracle implementations, both proxies, both DA verifiers, and every selected admin verifier use named CREATE3 salts.

For a local testing deployment with registration whitelisting disabled and the Always Approve verifier enabled, combine the required values above with:

```sh
DEPLOYMENT_IS_TESTING=true \
STATE_ORACLE_WHITELIST_ENABLED=false \
DEPLOY_ADMIN_VERIFIER_OWNER=false \
DEPLOY_ADMIN_VERIFIER_WHITELIST=false \
DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE=true \
forge script script/DeployCoreWithStaging.s.sol --rpc-url "$RPC_URL" --private-key "$DEPLOYER_PRIVATE_KEY" --broadcast
```

Console output will include labeled addresses for each deployed contract, e.g.:

Expand All @@ -115,6 +132,36 @@ State Oracle Implementation deployed at <address>
State Oracle Proxy deployed at <address>
```

### Interactive deployment wizard

Run the terminal wizard from the repository root:

```sh
make deploy
```

The wizard uses the deterministic CreateX deployment backend and guides you through:

- production or testing mode, plus an optional staging State Oracle;
- admin verifier selection and assignment to the production and/or staging oracle;
- ECDSA and/or on-chain DA verification, independently assigned to each State Oracle;
- initial State Oracle whitelist state and addresses;
- optional explorer verification using `ETHERSCAN_API_KEY`;
- all State Oracle limits, timelocks, admins, and verifier-specific addresses; and
- a Foundry keystore account selected from `cast wallet list`.

The wallet password and explorer API key are read without echoing. The password is checked before
deployment and stored only in a temporary mode-`600` file that is removed when the wizard exits.
Before broadcasting, the wizard prints a redacted Forge command and a complete configuration
summary. After broadcasting, it reads Foundry's receipt file and prints every deployment address,
block number, transaction hash, and proxy admin address.

Testing mode also exposes the `Super Admin` and `Always Approve` admin verifiers. These options are
rejected by the Solidity deployment backend unless testing mode is explicitly enabled. Both testing
verifiers use named CREATE3 salts, as do every production and staging contract deployed by the wizard.
Every broadcast entrypoint in `DeployTestingAdminVerifiers.s.sol` likewise requires
`DEPLOYMENT_IS_TESTING=true`.

## Installation

1. Clone the repository:
Expand All @@ -132,8 +179,9 @@ forge install

### CreateX

The forge script `script/DeployCoreWithCreateX` uses the CreateX contract factory for maintaining and controlling contract addresses
of the protocol.
The forge scripts `script/DeployCoreWithCreateX.s.sol`, `script/DeployCoreWithStaging.s.sol`, `script/DeployWizard.s.sol`, and `script/DeployTestingAdminVerifiers.s.sol` use the CreateX contract factory to maintain stable protocol contract addresses. CREATE3 derives each address from the deployer and a contract-specific salt, so transaction order, deployer nonce, and contract init code do not change it. Production and staging State Oracles use different salts; the testing-only SuperAdmin and AlwaysApprove verifiers also have distinct salts shared by the wizard and standalone testing script.

The fixed address must be empty when first deployed. On a retained chain, rerunning a salt whose contract already exists will revert; preserve the existing proxy and upgrade it for code changes, or reset the chain before performing a clean redeployment.
The deployment address of CreateX is `0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed`.
If CreateX is not deployed on your chain, you will find a helper script to deploy CreateX at
`shell/deploy_create_x.sh`.
Expand Down Expand Up @@ -169,6 +217,7 @@ STATE_ORACLE_ADMIN_ADDRESS=0xD2EfB83dd46094775188d927323b2523EaE3d087 \
DA_PROVER_ADDRESS=0x670cFA8781BF365Aefb6c048CDc522B857946C71 \
DEPLOY_ADMIN_VERIFIER_OWNER=true \
DEPLOY_ADMIN_VERIFIER_WHITELIST=true \
STATE_ORACLE_WHITELIST_ENABLED=true \
ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS=0xD2EfB83dd46094775188d927323b2523EaE3d087 \
forge script script/DeployCoreWithCreateX.s.sol --rpc-url http://localhost:8545 --private-key 0xac431098061ca49f5b36121d01a17d30e1d0624227d08b583ff328f1efe0d4a2 --broadcast
```
Expand All @@ -193,8 +242,8 @@ Private Key: 0xac431098061ca49f5b36121d01a17d30e1d0624227d08b583ff328f1efe0d4a2
Account: (0x8d63e0FE87CA36E06a076584fCA651A684D4c97d)
```

When broadcasting `script/DeployCore.s.sol ` with the above key, the contracts will always be deployed
at the same address. Neither the initCode of the contracts nor the nonce influence the address generation.
When broadcasting `script/DeployCoreWithCreateX.s.sol` with the above key, the contracts will always be
deployed at the same addresses on a clean chain. Neither init code nor nonce influences address generation.

## Compatibility Checks

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
],
"scripts": {
"prepare": "./shell/create_artifacts.sh",
"test": "forge test",
"test": "forge test && python3 test/shell/test_deploy_wizard.py",
"test:deploy-wizard": "python3 test/shell/test_deploy_wizard.py",
"build": "forge build",
"deploy:wizard": "./shell/deploy_wizard.sh",
"format": "forge fmt",
"clean": "forge clean"
},
Expand Down
31 changes: 31 additions & 0 deletions script/CreateXDeployer.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.28;

import {ICreateX, CREATE_X_ADDRESS} from "./ICreateX.sol";

abstract contract CreateXDeployer {
string public constant SALT_DA_VERIFIER_ECDSA_NAME = "credible-layer-da-verifier-ecdsa";
string public constant SALT_DA_VERIFIER_ONCHAIN_NAME = "credible-layer-da-verifier-onchain";
string public constant SALT_ADMIN_VERIFIER_OWNER_NAME = "credible-layer-admin-verifier-owner";
string public constant SALT_ADMIN_VERIFIER_WHITELIST_NAME = "credible-layer-admin-verifier-whitelist";
string public constant SALT_ADMIN_VERIFIER_SUPER_ADMIN_NAME = "credible-layer-admin-verifier-super-admin";
string public constant SALT_ADMIN_VERIFIER_ALWAYS_APPROVE_NAME = "credible-layer-admin-verifier-always-approve";
string public constant SALT_STATE_ORACLE_NAME = "credible-layer-state-oracle-implementation";
string public constant SALT_STATE_ORACLE_PROXY_NAME = "credible-layer-state-oracle-proxy";
string public constant SALT_STAGING_STATE_ORACLE_NAME = "credible-layer-staging-state-oracle-implementation";
string public constant SALT_STAGING_STATE_ORACLE_PROXY_NAME = "credible-layer-staging-state-oracle-proxy";

ICreateX internal constant CREATE_X = ICreateX(CREATE_X_ADDRESS);

function _deployCreate3(string memory name, bytes memory initCode) internal returns (address) {
bytes32 salt = _generateCreateXSalt(msg.sender, name);
return CREATE_X.deployCreate3(salt, initCode);
}

// Set salt with frontrunning protection, i.e. first 20 bytes = deployer;
// 0 byte to switch off cross-chain redeploy protection; 11 bytes salt
// Details: https://github.com/pcaversaccio/createx#permissioned-deploy-protection-and-cross-chain-redeploy-protection
function _generateCreateXSalt(address sender, string memory name) internal pure returns (bytes32) {
return bytes32(abi.encodePacked(sender, hex"00", bytes11(keccak256(bytes(name)))));
}
}
40 changes: 36 additions & 4 deletions script/DeployCore.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {StateOracle} from "../src/StateOracle.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {IAdminVerifier} from "../src/interfaces/IAdminVerifier.sol";
import {IDAVerifier} from "../src/interfaces/IDAVerifier.sol";
import {AdminVerifierAlwaysApprove} from "../src/verification/admin/AdminVerifierAlwaysApprove.sol";
import {AdminVerifierOwner} from "../src/verification/admin/AdminVerifierOwner.sol";
import {DAVerifierECDSA} from "../src/verification/da/DAVerifierECDSA.sol";
import {DAVerifierOnChain} from "../src/verification/da/DAVerifierOnChain.sol";
Expand All @@ -29,6 +30,9 @@ contract DeployCore is Script {
address daProver;
bool deployOwnerVerifier;
bool deployWhitelistVerifier;
bool deployAlwaysApproveVerifier;
bool stateOracleWhitelistEnabled;
bool testingDeployment;
address whitelistAdmin;

function setUp() public virtual {
Expand All @@ -40,12 +44,18 @@ contract DeployCore is Script {
// Verifiers
deployOwnerVerifier = vm.envBool("DEPLOY_ADMIN_VERIFIER_OWNER");
deployWhitelistVerifier = vm.envBool("DEPLOY_ADMIN_VERIFIER_WHITELIST");
whitelistAdmin = vm.envAddress("ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS");
deployAlwaysApproveVerifier = vm.envOr("DEPLOY_ADMIN_VERIFIER_ALWAYS_APPROVE", false);
stateOracleWhitelistEnabled = vm.envOr("STATE_ORACLE_WHITELIST_ENABLED", true);
testingDeployment = vm.envOr("DEPLOYMENT_IS_TESTING", false);
if (deployWhitelistVerifier) {
whitelistAdmin = vm.envAddress("ADMIN_VERIFIER_WHITELIST_ADMIN_ADDRESS");
}

assert(daProver != address(0));
assert(assertionTimelockBlocks > 0);
assert(admin != address(0));
assert(deployWhitelistVerifier && whitelistAdmin != address(0) || !deployWhitelistVerifier);
require(testingDeployment || !deployAlwaysApproveVerifier, "Always Approve verifier is test-only");
}

modifier broadcast() {
Expand All @@ -54,6 +64,11 @@ contract DeployCore is Script {
vm.stopBroadcast();
}

modifier testingOnly() {
require(testingDeployment, "Always Approve verifier is test-only");
_;
}

function run() public virtual broadcast {
_fundPersistentAccounts();

Expand Down Expand Up @@ -102,6 +117,10 @@ contract DeployCore is Script {
_deployWhitelistAdminVerifier();
}

function deployAlwaysApproveAdminVerifier() public testingOnly broadcast {
_deployAlwaysApproveAdminVerifier();
}

function fundPersistentAccounts() public broadcast {
_fundPersistentAccounts();
}
Expand All @@ -122,13 +141,17 @@ contract DeployCore is Script {
uint256 count;
if (deployOwnerVerifier) count++;
if (deployWhitelistVerifier) count++;
if (deployAlwaysApproveVerifier) count++;
deployments = new address[](count);
uint256 index;
if (deployOwnerVerifier) {
deployments[index++] = _deployOwnerAdminVerifier();
}
if (deployWhitelistVerifier) {
deployments[index] = _deployWhitelistAdminVerifier();
deployments[index++] = _deployWhitelistAdminVerifier();
}
if (deployAlwaysApproveVerifier) {
deployments[index] = _deployAlwaysApproveAdminVerifier();
}
}

Expand All @@ -152,8 +175,10 @@ contract DeployCore is Script {
for (uint256 i = 0; i < daVerifierAddresses.length; i++) {
daVfrs[i] = IDAVerifier(daVerifierAddresses[i]);
}
bytes memory initCallData =
abi.encodeWithSelector(StateOracle.initialize.selector, admin, adminVerifiers, daVfrs, maxAssertions);
bytes memory initCallData = abi.encodeCall(
StateOracle.initializeWithWhitelist,
(admin, adminVerifiers, daVfrs, maxAssertions, stateOracleWhitelistEnabled, new address[](0))
);
address proxyAddress = address(new TransparentUpgradeableProxy(address(stateOracle), admin, initCallData));
console2.log("State Oracle Proxy deployed at", proxyAddress);
return proxyAddress;
Expand All @@ -166,11 +191,18 @@ contract DeployCore is Script {
}

function _deployWhitelistAdminVerifier() internal virtual returns (address verifier) {
require(whitelistAdmin != address(0), "Invalid whitelist admin");
verifier = address(new AdminVerifierWhitelist(whitelistAdmin));
console2.log("Admin Verifier (Whitelist) deployed at", verifier);
return verifier;
}

function _deployAlwaysApproveAdminVerifier() internal virtual returns (address verifier) {
verifier = address(new AdminVerifierAlwaysApprove());
console2.log("Testing Admin Verifier (Always Approve) deployed at", verifier);
return verifier;
}

function _fundPersistentAccounts() internal {
_fundIfEmpty(CALLER_ADDRESS, "CALLER_ADDRESS");
_fundIfEmpty(ASSERTION_CONTRACT_ADDRESS, "ASSERTION_CONTRACT_ADDRESS");
Expand Down
Loading
Loading