-
Notifications
You must be signed in to change notification settings - Fork 329
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce zeus templates (#790)
* feat: wip refactor of deploy scripts * chore: remove unused release folder * wip * wip2 * Cleanup to get compiling * Clean up template and create deploy script * Mild refactoring + adding in utils file + commenting out code * Craft transactions producing a bytestring * wip: ops timelock script * Reorder contracts * Move around code and rename variables * Finish up upgrade script * Get queueing working * Clean up unused files * Fix UpgradeViaTimelock name * Complete execute and queue flow * Add timelock as parameter to executor encoding * Finish deploy and upgrade scripts * Rename deploy script * Set up example tests * Add new base contract separating queueing and execution * Break out MultisigCallUtils into separate file * Break out SafeTxUtils into separate file * Move out all abstract contracts into template directory * Remove virtual test functions * Add boilerplate for Zeus * Fix outdated comment * Amend incorrect contract names * Document template files and make inheritance more explicit * add /var/folder access to foundry for zoooos * Remove unnecessary imports and mildly refactor ConfigParser * Refactor to updated Zeus specification * forge install: zeus-templates * Cleanup and rename example scripts * Remove file * forge install: zeus-templates * Use zeus-templates for dependency * fix: remove files in zeus-templates, and move interfaces and utils where appropriate * Update README and example path * refactor: remove template dir * refactor: update deployment struct and example * refactor: remove tests * refactor: use envvars instead of config parsing * Lays Labs lmfao * feat: add templates back into directory * refactor: use helper deployment functions + forge fmt * fix: update env and zeus-templates * Add zuesTest stubs * chore: update zeus-templates * refactor: use helper functions for getting envvars * chore: add basic test example for deploy * chore: retrigger checks * chore: forge fmt * chore: update zeus-templates for file capitalization change * chore: restore files for tests * chore: restore more files for tests * chore: restore even _more_ files for integration tests * chore: remove unnecessary Certora script --------- Co-authored-by: wadealexc <[email protected]> Co-authored-by: Justin Brower <[email protected]>
- Loading branch information
1 parent
7d083ec
commit 957eb2d
Showing
80 changed files
with
1,315 additions
and
4,979 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule forge-std
updated
44 files
Submodule zeus-templates
added at
ec220a
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
# Release Scripts | ||
|
||
This directory contains the following subdirectories: | ||
|
||
* `configs`: to store configuration data related to a given network. | ||
* `interfaces`: to store interfaces relevant to your scripts. | ||
* `releases`: to set up more subdirectories corresponding to your release, and the most important `script/` subdirectory. | ||
* `utils`: to define any utility contracts for performing common actions. | ||
|
||
It is intended to be driven by [Zeus](https://github.com/Layr-Labs/zeus), which will run `forge` commands under the hood and track the status of upgrades. | ||
|
||
## Using Zeus Templates | ||
|
||
The [zeus-templates](https://github.com/Layr-Labs/zeus-templates) repository provides two base contract classes to facilitate deployment and multisig scripts. | ||
|
||
### EOADeployer | ||
|
||
`EOADeployer` is the base contract for performing EOA deploys, providing a `Deployment` struct. The entry function is `deploy()` and requires adding `-s "deploy(string memory)" "<path_to_config>"` as a `forge script` flag. Any inheriting contract is expected to inherit the `_deploy()` internal function and return a `Deployment[]` array, containing all deployed contract details. | ||
|
||
The `Deployment` struct contains 3 fields: | ||
* `name`: the name of the contract published | ||
* `address`: the address to which the contract was published | ||
* `envToUpdate`: the environment variable in the config file to update if relevant. | ||
* Leave this as an empty string if deploying an instance of a contract that doesn't need to be tracked long-term. | ||
|
||
If you need to do something with an EOA other than deploy, the base `ConfigParser` provided by `zeus-templates` is the appropriate base contract. | ||
|
||
### MultisigBuilder | ||
|
||
`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. | ||
|
||
The `MultisigCall` struct contains 3 fields: | ||
* `to`: the address to call with the multisig | ||
* `value`: the amount of ETH to send as part of the transaction. | ||
* `data`: the calldata associated with the multisig call. | ||
|
||
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. | ||
|
||
## Setting up a Release Directory | ||
|
||
### Naming | ||
|
||
Every file must be named either `#-eoa.s.sol` or `#-multisig.s.sol`, where `#` represents the script's order in the release process and the choice of `(eoa|multisig)` represents the relevant signing strategy. | ||
|
||
## Example: How to write a Deploy-Queue-Upgrade | ||
|
||
Before diving into how to do this, a quick context blurb for those unfamiliar. If you are familiar, feel free to jump to the next subsection, [Deploy](#deploy). | ||
|
||
### Context | ||
|
||
A deploy-queue-execute flow is common given EigenLayer's [multisig governance](https://docs.eigenlayer.xyz/eigenlayer/security/multisig-governance) structure. For context, the Executor Multisig (a 1-of-2 multisig) owns many critical privileges on EigenLayer, such as the ability to upgrade core contracts. An EigenLayer upgrade will often originate from the Operations Multisig, controlled by Eigen Labs, which is 1 of the 2 signers. Well, in spirit at least. | ||
|
||
Technically, there is an intermediate contract between the Ops Multisig and Executor known as the Timelock into which actions must be queued for a waiting period (~10 days). After the delay, the Operations Multsig must then poke the Timelock to forward the transaction to the Executor, which will finally take action. | ||
|
||
### Deploy | ||
|
||
Deploy scripts are expected to inherit the `EOADeployer` script and produce a `Deployment[]` array containing all deployed contract names, addresses, and overridden config values. | ||
|
||
An example deploy script looks as follows: | ||
|
||
```solidity | ||
pragma solidity ^0.8.12; | ||
import "zeus-templates/templates/EOADeployer.sol"; | ||
... // imagine imports here | ||
contract DeployEigenPodManager is EOADeployer { | ||
function _deploy(Addresses memory, Environment memory, Params memory params) internal override returns (Deployment[] memory) { | ||
vm.startBroadcast(); | ||
address newEigenPodManager = new EigenPodManager( | ||
... // imagine params here | ||
); | ||
_deployments.push(Deployment({ | ||
name: type(EigenPodManager).name, | ||
deployedTo: newEigenPodManager, | ||
envToUpdate: "eigenPodManager.pendingImpl" | ||
})); | ||
vm.stopBroadcast(); | ||
return _deployments; | ||
} | ||
} | ||
``` | ||
|
||
Here, the script inherits the `EOADeployer` and implements the `_deploy()` function, creating a new `EigenPodManager` with relevant details and requesting for Zeus to set the `pendingImpl` field of the `eigenPodManager` in the config to the given address. It would be pending because a transaction must be issued to upgrade the EigenPodManager to this new implementation. | ||
|
||
### Queue | ||
|
||
Once the above `eigenPodManager` is deployed, a `queueTransaction` operation must be taken in the Timelock if upgrading via the Ops Multisig. This could look like something below: | ||
|
||
```solidity | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.12; | ||
import "zeus-templates/templates/OpsTimelockBuilder.sol"; | ||
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; | ||
import {IUpgradeableBeacon} from "script/interfaces/IUpgradeableBeacon.sol"; | ||
import "src/contracts/pods/EigenPodManager.sol"; | ||
contract QueueEigenPodAndManager is OpsTimelockBuilder { | ||
using MultisigCallUtils for MultisigCall[]; | ||
using SafeTxUtils for SafeTx; | ||
MultisigCall[] internal _executorCalls; | ||
function queue(Addresses memory addrs, Environment memory env, Params memory params) public override returns (MultisigCall[] memory) { | ||
// construct initialization data for eigenPodManager | ||
bytes memory eigenPodManagerData = abi.encodeWithSelector( | ||
EigenPodManager(addrs.eigenPodManager.pendingImpl).initialize.selector, | ||
... // imagine initialization data | ||
); | ||
// upgrade eigenPodManager | ||
_executorCalls.append({ | ||
to: addrs.proxyAdmin, | ||
data: abi.encodeWithSelector( | ||
ProxyAdmin.upgradeAndCall.selector, | ||
addrs.eigenPodManager.proxy, | ||
addrs.eigenPodManager.pendingImpl, | ||
eigenPodManagerData // initialize impl here | ||
) | ||
}); | ||
// upgrade eigenPod beacon implementation | ||
_executorCalls.append({ | ||
to: addrs.eigenPod.beacon, | ||
data: abi.encodeWithSelector( | ||
IUpgradeableBeacon.upgradeTo.selector, | ||
addrs.eigenPod.pendingImpl | ||
) | ||
}); | ||
return _executorCalls; | ||
} | ||
} | ||
``` | ||
|
||
After inheriting the `OpsTimelockBuilder` contract (which is an extension of `MultisigBuilder`), we implement the `queue()` function from the perspective of the Executor Multisig. All calls are eventually encoded as a `SafeTx` from the Ops Multisig to the Timelock. | ||
|
||
### Upgrade | ||
|
||
Once the delay has passed, `executeTransaction` can be called on the timelock with the appropriate calldata. As an example: | ||
|
||
```solidity | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.12; | ||
import "zeus-templates/templates/OpsTimelockBuilder.sol"; | ||
import "src/contracts/pods/EigenPodManager.sol"; | ||
import "./2-multisig.s.sol"; // using previous script to avoid rewriting | ||
contract ExecuteEigenPodManager is MultisigBuilder { | ||
using MultisigCallUtils for MultisigCall[]; | ||
using SafeTxUtils for *; | ||
function _execute(Addresses memory addrs, Environment memory env, Params memory params) internal override returns (MultisigCall[] memory) { | ||
QueueEigenPodManager queue = new QueueEigenPodManager(); | ||
MultisigCall[] memory _executorCalls = queue.queue(addrs, env, params); | ||
// steals logic from queue() to perform execute() | ||
// likely the first step of any _execute() after a _queue() | ||
bytes memory executorCalldata = queue.makeExecutorCalldata( | ||
_executorCalls, | ||
params.multiSendCallOnly, | ||
addrs.timelock | ||
); | ||
// execute queued transaction upgrading eigenPodManager | ||
_multisigCalls.append({ | ||
to: addrs.timelock, | ||
value: 0, | ||
data: abi.encodeWithSelector( | ||
ITimelock.executeTransaction.selector, | ||
executorCalldata | ||
) | ||
}); | ||
return _multisigCalls; | ||
} | ||
} | ||
``` | ||
|
||
After reusing the previous script's `queue()` function to recreate the calldata, we wrap it up in an `executeTransaction` encoded data blob and return the `MultisigCall`, which will then be crafted as a `SafeTx` by the parent contract. |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.