Skip to content

Commit

Permalink
feat: introduce zeus templates (#790)
Browse files Browse the repository at this point in the history
* 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
3 people authored Nov 1, 2024
1 parent 7d083ec commit 957eb2d
Show file tree
Hide file tree
Showing 80 changed files with 1,315 additions and 4,979 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "lib/openzeppelin-contracts-upgradeable-v4.9.0"]
path = lib/openzeppelin-contracts-upgradeable-v4.9.0
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/zeus-templates"]
path = lib/zeus-templates
url = https://github.com/Layr-Labs/zeus-templates
18 changes: 0 additions & 18 deletions certora/scripts/libraries/verifyStructuredLinkedList.sh

This file was deleted.

2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
src = 'src'
out = 'out'
libs = ['lib']
fs_permissions = [{ access = "read-write", path = "./"}]
fs_permissions = [{ access = "read-write", path = "./"}, { access = "read-write", path = "/var/folders"}]
gas_reports = ["*"]
# ignore upgrade testing in scripts by default
no_match_test = "queueUpgrade"
Expand Down
1 change: 1 addition & 0 deletions lib/zeus-templates
Submodule zeus-templates added at ec220a
197 changes: 197 additions & 0 deletions script/README.md
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.
71 changes: 0 additions & 71 deletions script/admin/mainnet/Mainnet_Unpause_Deposits.s.sol

This file was deleted.

Loading

0 comments on commit 957eb2d

Please sign in to comment.