Skip to content

Commit e87f933

Browse files
feat: update generator script (#1581)
**Motivation:** We want a zeus script to update the generator **Modifications:** - Create update generator script - Update testnet generator config - Add readme for one-off Zeus scripts **Result:** Better ops --------- Co-authored-by: Nadir Akhtar <[email protected]>
1 parent 53bb19c commit e87f933

File tree

7 files changed

+222
-6
lines changed

7 files changed

+222
-6
lines changed

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
sparse_mode = true
7878

7979
no_match_test = "queueUpgrade"
80-
no_match_path = "script/releases/**/*.sol"
80+
no_match_path = "script/**/*.sol"
8181
fs_permissions = [{ access = "read-write", path = "./"}]
8282

8383
[profile.default.fmt]

script/operations/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Operations Scripting With Zeus
2+
3+
This directory is where you will build [Zeus](https://github.com/Layr-Labs/zeus) scripts to do one-off protocol interactions. It *should not* be used for pauser multisig interactions - see the Pauser guide for more information.
4+
5+
When running or testing a script, _you tell zeus which environment to use,_ and it will fork the corresponding network state and setup environment variables for that environment's params/deployment addresses.
6+
7+
zeus script --env testnet-hoodi --multisig script/releases/tests/ExecuteTransferOwnership.s.sol
8+
9+
##### Getting Started
10+
11+
* Install [Zeus](https://github.com/Layr-Labs/zeus)
12+
* Run `zeus login`
13+
14+
At this point, you should be able to view an environment's config (try `zeus env show preprod`)
15+
16+
---
17+
18+
### Writing a Script
19+
20+
Operations scripts are generally single script that require a multisig step. The step should include comprehensive test functions to validate the changes at each stage of the upgrade process.
21+
22+
### Upgrade.json
23+
The `upgrade.json` file denotes the steps of the script. See previous upgrade scripts for examples
24+
25+
### MultisigBuilder
26+
27+
`MultisigBuilder` is the base class for any action to be taken by a Safe Multisig. The entry function is `execute()` and requires adding `-s "execute(string memory)" "<path_to_config>"` as a `forge script` flag. Any inheriting contract is expected to inherit the `_execute()` internal function and return a `MultisigCall[]` object, containing all intended multisig calls.
28+
29+
The `MultisigCall` struct contains 3 fields:
30+
* `to`: the address to call with the multisig
31+
* `value`: the amount of ETH to send as part of the transaction.
32+
* `data`: the calldata associated with the multisig call.
33+
34+
Once the `_execute()` function is implemented, the base MultisigBuilder contract will combine these calls into one `SafeTx` object, which is intended to pass all calls into the [MultiSendCallOnly](https://github.com/safe-global/safe-smart-account/blob/6fde75d29c8b52d5ac0c93a6fb7631d434b64119/contracts/libraries/MultiSendCallOnly.sol) contract.
35+
36+
### Running A Script
37+
38+
Example script:
39+
40+
```bash
41+
zeus script --env <env> --<eoa or multisig> <path to script>
42+
43+
# zeus script --env testnet-sepolia --multisig script/operations/update-generator/1-updateGenerator.s.sol
44+
```
45+
46+
To run tests:
47+
48+
```bash
49+
zeus test --env <env> <path to script> --rpcUrl <rpc_url>
50+
51+
# zeus test script/operations/update-generator/1-updateGenerator.s.sol --env testnet-sepolia --rpcUrl $RPC_SEPOLIA
52+
```
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import "../../releases/Env.sol";
5+
import {MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol";
6+
7+
// Types
8+
import "src/contracts/interfaces/IOperatorTableCalculator.sol";
9+
import "src/contracts/libraries/OperatorSetLib.sol";
10+
import "src/contracts/libraries/BN254.sol";
11+
import "src/contracts/interfaces/IBaseCertificateVerifier.sol";
12+
13+
// For TOML parsing
14+
import {stdToml} from "forge-std/StdToml.sol";
15+
16+
/**
17+
* Purpose: Update the generator on a TESTNET environment
18+
*/
19+
contract QueueTransferProxyAdmin is MultisigBuilder {
20+
using Env for *;
21+
using OperatorSetLib for OperatorSet;
22+
using stdToml for string;
23+
24+
string private constant TESTNET_CONFIG_PATH = "script/releases/v1.7.0-multichain/configs/testnet.toml";
25+
26+
function _runAsMultisig() internal virtual override prank(Env.opsMultisig()) {
27+
GeneratorParams memory generatorParams = _getGeneratorParams(TESTNET_CONFIG_PATH);
28+
Env.proxy.operatorTableUpdater().updateGenerator(generatorParams.generator, generatorParams.generatorInfo);
29+
}
30+
31+
function testScript() public virtual {
32+
// Require that the environment is a testnet environment supported by multichain
33+
require(
34+
Env._strEq(Env.env(), "testnet-sepolia") || Env._strEq(Env.env(), "testnet-base-sepolia"),
35+
"Environment must be a testnet environment"
36+
);
37+
38+
// Update the generator
39+
execute();
40+
41+
// Get the generator params
42+
GeneratorParams memory generatorParams = _getGeneratorParams(TESTNET_CONFIG_PATH);
43+
44+
// Check that the generator has been updated in the operator table updater
45+
OperatorTableUpdater operatorTableUpdater = Env.proxy.operatorTableUpdater();
46+
assertEq(
47+
operatorTableUpdater.getGenerator().key(),
48+
generatorParams.generator.key(),
49+
"operatorTableUpdater.generator invalid"
50+
);
51+
assertEq(
52+
operatorTableUpdater.getGeneratorReferenceTimestamp(),
53+
operatorTableUpdater.GENERATOR_REFERENCE_TIMESTAMP(),
54+
"operatorTableUpdater.generatorReferenceTimestamp invalid"
55+
);
56+
assertEq(
57+
operatorTableUpdater.getGeneratorReferenceTimestamp(),
58+
1,
59+
"operatorTableUpdater.generatorReferenceTimestamp invalid"
60+
);
61+
assertEq(
62+
operatorTableUpdater.getGlobalTableRootByTimestamp(1),
63+
operatorTableUpdater.GENERATOR_GLOBAL_TABLE_ROOT(),
64+
"operatorTableUpdater.generatorGlobalTableRoot invalid"
65+
);
66+
67+
// Check that the generator has been updated in the certificate verifier
68+
BN254CertificateVerifier certificateVerifier = Env.proxy.bn254CertificateVerifier();
69+
assertEq(
70+
certificateVerifier.latestReferenceTimestamp(generatorParams.generator),
71+
operatorTableUpdater.GENERATOR_REFERENCE_TIMESTAMP(),
72+
"certificateVerifier.latestReferenceTimestamp invalid"
73+
);
74+
assertEq(
75+
IBaseCertificateVerifier(address(certificateVerifier)).maxOperatorTableStaleness(generatorParams.generator),
76+
operatorTableUpdater.GENERATOR_MAX_STALENESS_PERIOD(),
77+
"certificateVerifier.maxStalenessPeriod invalid"
78+
);
79+
assertEq(
80+
certificateVerifier.getOperatorSetOwner(generatorParams.generator),
81+
address(operatorTableUpdater),
82+
"certificateVerifier.operatorSetOwner invalid"
83+
);
84+
// Get the operatorSetInfo
85+
IOperatorTableCalculatorTypes.BN254OperatorSetInfo memory operatorSetInfo = certificateVerifier
86+
.getOperatorSetInfo(generatorParams.generator, operatorTableUpdater.GENERATOR_REFERENCE_TIMESTAMP());
87+
assertEq(
88+
operatorSetInfo.numOperators,
89+
generatorParams.generatorInfo.numOperators,
90+
"certificateVerifier.getOperatorSetInfo.numOperators invalid"
91+
);
92+
assertEq(
93+
operatorSetInfo.operatorInfoTreeRoot,
94+
generatorParams.generatorInfo.operatorInfoTreeRoot,
95+
"certificateVerifier.getOperatorSetInfo.operatorInfoTreeRoot invalid"
96+
"certificateVerifier.getOperatorSetInfo invalid"
97+
);
98+
assertEq(
99+
operatorSetInfo.aggregatePubkey.X,
100+
generatorParams.generatorInfo.aggregatePubkey.X,
101+
"certificateVerifier.getOperatorSetInfo.aggregatePubkey.X invalid"
102+
);
103+
assertEq(
104+
operatorSetInfo.aggregatePubkey.Y,
105+
generatorParams.generatorInfo.aggregatePubkey.Y,
106+
"certificateVerifier.getOperatorSetInfo.aggregatePubkey.Y invalid"
107+
);
108+
assertEq(
109+
operatorSetInfo.totalWeights,
110+
generatorParams.generatorInfo.totalWeights,
111+
"certificateVerifier.getOperatorSetInfo.totalWeights invalid"
112+
);
113+
}
114+
115+
function _getGeneratorParams(
116+
string memory path
117+
) internal view returns (GeneratorParams memory generatorParams) {
118+
// Read the TOML file
119+
string memory root = vm.projectRoot();
120+
string memory fullPath = string.concat(root, "/", path);
121+
string memory toml = vm.readFile(fullPath);
122+
123+
// Parse globalRootConfirmerSet
124+
address avs = toml.readAddress(".globalRootConfirmerSet.avs");
125+
uint32 id = uint32(toml.readUint(".globalRootConfirmerSet.id"));
126+
generatorParams.generator = OperatorSet({avs: avs, id: id});
127+
128+
// Parse globalRootConfirmerSetInfo
129+
generatorParams.generatorInfo.numOperators = uint256(toml.readUint(".globalRootConfirmerSetInfo.numOperators"));
130+
generatorParams.generatorInfo.operatorInfoTreeRoot =
131+
toml.readBytes32(".globalRootConfirmerSetInfo.operatorInfoTreeRoot");
132+
generatorParams.generatorInfo.totalWeights = toml.readUintArray(".globalRootConfirmerSetInfo.totalWeights");
133+
uint256 apkX = toml.readUint(".globalRootConfirmerSetInfo.aggregatePubkey.X");
134+
uint256 apkY = toml.readUint(".globalRootConfirmerSetInfo.aggregatePubkey.Y");
135+
generatorParams.generatorInfo.aggregatePubkey = BN254.G1Point({X: apkX, Y: apkY});
136+
137+
return generatorParams;
138+
}
139+
140+
struct GeneratorParams {
141+
OperatorSet generator;
142+
IOperatorTableCalculatorTypes.BN254OperatorSetInfo generatorInfo;
143+
}
144+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Generator Update
2+
3+
The `generator`, signs off on roots. In order to generate this operatorSet, we use the following [script](../../deploy/multichain/deploy_globalRootConfirmerSet.s.sol) The script outputs two items:
4+
5+
1. `network.wallet.json`: Private keys and BLS sig info (should be kept secure)
6+
2. `network.toml`: A toml file passed into initialization on `operatorTableUpdater`
7+
8+
See our [docs](../../../docs/multichain/) for more information.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "update-generator",
3+
"from": "1.8.0-rc.0",
4+
"to": "1.8.0-rc.0",
5+
"phases": [
6+
{
7+
"type": "multisig",
8+
"filename": "1-updateGenerator.s.sol"
9+
}
10+
]
11+
}

script/releases/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This directory is where you will build [Zeus](https://github.com/Layr-Labs/zeus)
55
* Update environment after a release is run to completion
66
* Track status of releases to ensure steps are run (in order) only once per environment
77

8-
**Note about environments:** Zeus scripts are intended to be written once, and run across _any_ environment we use. We currently have 5 live environments (`preprod`, `testnet`, `testnet-sepolia`, `testnet-hoodi` and `mainnet`), and the params/deployment addresses for each live in separate folders in [`layr-labs/eigenlayer-contracts-zeus-metadata`](https://github.com/Layr-Labs/eigenlayer-contracts-zeus-metadata).
8+
**Note about environments:** Zeus scripts are intended to be written once, and run across _any_ environment we use. We currently have 6 live environments (`preprod`, `testnet`, `testnet-sepolia`, `testnet-hoodi`, `testnet-base-sepolia`, and `mainnet`), and the params/deployment addresses for each live in separate folders in [`layr-labs/eigenlayer-contracts-zeus-metadata`](https://github.com/Layr-Labs/eigenlayer-contracts-zeus-metadata).
99

1010
When running or testing a script, _you tell zeus which environment to use,_ and it will fork the corresponding network state and setup environment variables for that environment's params/deployment addresses.
1111

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
globalRootConfirmationThreshold = 10000
2+
23
[globalRootConfirmerSet]
34
avs = "0xda29bb71669f46f2a779b4b62f03644a84ee3479"
4-
id = 0
5+
id = 1
56

67
[globalRootConfirmerSetInfo]
78
numOperators = 1
8-
operatorInfoTreeRoot = "0x28422d2f3fa660e25c063fb34742fa02f7e75fba3c78f80031cd7e9a3a5c68d9"
9+
operatorInfoTreeRoot = "0x77cafd4719966ab4cc333c09487a48ee9e664d388bbe3be164a1560aa1ec9483"
910
totalWeights = [1]
1011

1112
[globalRootConfirmerSetInfo.aggregatePubkey]
12-
X = "9091272969368059399846805175941234921833445882000488923774697344827079706946"
13-
Y = "19134916441917375104496425063226216431905713150529026164932839559599921516998"
13+
X = "1248397600432326960268390197274148803246429440823159447015902310300913566841"
14+
Y = "15744627742693879328932387454791059921878461529355996890388287808150717741641"

0 commit comments

Comments
 (0)