Skip to content

Commit

Permalink
feat(genesis): create3 and timelock (#242)
Browse files Browse the repository at this point in the history
Adds `CREATE3` predeploy, which deploys a `TimelockController` that will
control all the admin methods and upgrades.

issue: none
  • Loading branch information
Ramarti authored Oct 20, 2024
1 parent cb2e900 commit 64bbd41
Show file tree
Hide file tree
Showing 12 changed files with 584 additions and 190 deletions.
12 changes: 7 additions & 5 deletions contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ To generate this first state:
1. Add a .env file in `contracts/.env`

```
ADMIN_ADDRESS=0x123...
UPGRADE_ADMIN_ADDRESS=0x234...
ADMIN_ADDRESS=0x...
TIMELOCK_EXECUTOR_ADDRESS=0x...
TIMELOCK_GUARDIAN_ADDRESS=0x...
```
- `ADMIN_ADDRESS` will be the owner of `IPTokenStaking` and `UpgradeEntryPoint`, able to execute admin methods.
- `UPGRADE_ADMIN_ADDRESS` will be the owner of the [ProxyAdmin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/ProxyAdmin.sol) for each upgradeable predeploy.
- `ADMIN_ADDRESS` will be the owner of the `TimelockController` contract. Will be able to propose transactions to the timelock, and cancel them.
- `TIMELOCK_EXECUTOR_ADDRESS` address allowed to execute the scheduled actions once the timelock matures.
- `TIMELOCK_GUARDIAN_ADDRESS` address allowed to cancel proposals

2. Run
1. Run
```
forge script script/GenerateAlloc.s.sol -vvvv --chain-id <DESIRED_CHAIN_ID>
```
Expand Down
138 changes: 120 additions & 18 deletions contracts/script/GenerateAlloc.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pragma solidity ^0.8.23;
import { Script } from "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";
import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
import { IIPTokenStaking } from "../src/interfaces/IIPTokenStaking.sol";
import { IPTokenStaking } from "../src/protocol/IPTokenStaking.sol";
import { UpgradeEntrypoint } from "../src/protocol/UpgradeEntrypoint.sol";
Expand All @@ -15,7 +15,7 @@ import { UBIPool } from "../src/protocol/UBIPool.sol";
import { EIP1967Helper } from "./utils/EIP1967Helper.sol";
import { InitializableHelper } from "./utils/InitializableHelper.sol";
import { Predeploys } from "../src/libraries/Predeploys.sol";

import { Create3 } from "../src/deploy/Create3.sol";
/**
* @title GenerateAlloc
* @dev A script to generate the alloc section of EL genesis
Expand All @@ -33,11 +33,15 @@ contract GenerateAlloc is Script {
*/
address internal deployer = 0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd;

// Upgrade admin controls upgradeability (by being Owner of each ProxyAdmin),
// protocol admin is Owner of precompiles (admin/governance methods).
// To disable upgradeability, we transfer ProxyAdmin ownership to a dead address
address internal upgradeAdmin;
// TimelockController
address internal timelock;
// Governance multi-sig
address internal protocolAdmin;
// Executor of scheduled operations
address internal timelockExecutor;
// Guardian of timelock
address internal timelockGuardian;

string internal dumpPath = getDumpPath();
bool public saveState = true;
uint256 public constant MAINNET_CHAIN_ID = 0; // TBD
Expand All @@ -49,10 +53,11 @@ contract GenerateAlloc is Script {
}

/// @notice call from Test.sol only
function setAdminAddresses(address upgrade, address protocol) external {
function setAdminAddresses(address protocol, address executor, address guardian) external {
require(block.chainid == 31337, "Only for local tests");
upgradeAdmin = upgrade;
protocolAdmin = protocol;
timelockExecutor = executor;
timelockGuardian = guardian;
}

/// @notice path where alloc file will be stored
Expand All @@ -74,18 +79,70 @@ contract GenerateAlloc is Script {
}
}

/// @notice main script method
function run() public {
if (upgradeAdmin == address(0)) {
upgradeAdmin = vm.envAddress("UPGRADE_ADMIN_ADDRESS");
/// @notice Get the minimum delay for the timelock
function getTimelockMinDelay() internal view returns (uint256) {
if (block.chainid == 1513) {
// Iliad
return 1 days;
} else if (block.chainid == 1512) {
// Mininet
return 10 seconds;
} else if (block.chainid == 1315) {
// Odyssey devnet
return 10 seconds;
} else if (block.chainid == 1516) {
// Odyssey testnet
return 1 days;
} else if (block.chainid == 31337) {
// Local
return 10 seconds;
} else {
revert("Unsupported chain id");
}
require(upgradeAdmin != address(0), "upgradeAdmin not set");
}

function getTimelockControllers()
internal
view
returns (address[] memory proposers, address[] memory executors, address canceller)
{
proposers = new address[](1);
executors = new address[](1);
if (block.chainid == 1513) {
// Iliad
proposers[0] = protocolAdmin;
executors[0] = protocolAdmin;
canceller = protocolAdmin;
return (proposers, executors, protocolAdmin);
} else if (block.chainid == 1512 || block.chainid == 1315 || block.chainid == 31337 || block.chainid == 1516) {
// Mininet, Odyssey devnet, Local, Odyssey testnet
proposers[0] = protocolAdmin;
executors[0] = timelockExecutor;
canceller = timelockGuardian;
return (proposers, executors, protocolAdmin);
} else {
revert("Unsupported chain id");
}
}

/// @notice main script method
function run() public {
// Tests should set these addresses first
if (protocolAdmin == address(0)) {
protocolAdmin = vm.envAddress("ADMIN_ADDRESS");
}
require(protocolAdmin != address(0), "protocolAdmin not set");

if (timelockExecutor == address(0)) {
timelockExecutor = vm.envAddress("TIMELOCK_EXECUTOR_ADDRESS");
}
require(timelockExecutor != address(0), "executor not set");

if (timelockGuardian == address(0)) {
timelockGuardian = vm.envAddress("TIMELOCK_GUARDIAN_ADDRESS");
}
require(timelockGuardian != address(0), "canceller not set");

vm.startPrank(deployer);

setPredeploys();
Expand All @@ -111,6 +168,11 @@ contract GenerateAlloc is Script {
}

function setPredeploys() internal {
// predeploys that are not upgradable
setCreate3();
deployTimelock();

// predeploys that are upgradable
setProxy(Predeploys.Staking);
setProxy(Predeploys.Upgrades);
setProxy(Predeploys.UBIPool);
Expand All @@ -120,6 +182,32 @@ contract GenerateAlloc is Script {
setUBIPool();
}

/// @notice Deploy TimelockController
function deployTimelock() internal {
// We deploy this with Create3 because we can't set storage variables in constructor with vm.etch

uint256 minDelay = getTimelockMinDelay();
(address[] memory proposers, address[] memory executors, address canceller) = getTimelockControllers();

bytes memory creationCode = abi.encodePacked(
type(TimelockController).creationCode,
abi.encode(minDelay, proposers, executors, deployer)
);
bytes32 salt = keccak256("STORY_TIMELOCK_CONTROLLER");
timelock = Create3(Predeploys.Create3).deploy(salt, creationCode);

TimelockController(payable(timelock)).grantRole(
TimelockController(payable(timelock)).CANCELLER_ROLE(),
canceller
);
TimelockController(payable(timelock)).renounceRole(
TimelockController(payable(timelock)).DEFAULT_ADMIN_ROLE(),
deployer
);

console2.log("TimelockController deployed at:", timelock);
}

function setProxy(address proxyAddr) internal {
address impl = Predeploys.getImplAddress(proxyAddr);

Expand All @@ -129,7 +217,7 @@ contract GenerateAlloc is Script {
vm.etch(impl, "00");

// use new, so that the immutable variable the holds the ProxyAdmin proxyAddr is set in properly in bytecode
address tmp = address(new TransparentUpgradeableProxy(impl, upgradeAdmin, ""));
address tmp = address(new TransparentUpgradeableProxy(impl, timelock, ""));
vm.etch(proxyAddr, tmp.code);

// set implempentation storage manually
Expand Down Expand Up @@ -170,7 +258,7 @@ contract GenerateAlloc is Script {

InitializableHelper.disableInitializers(impl);
IIPTokenStaking.InitializerArgs memory args = IIPTokenStaking.InitializerArgs({
owner: protocolAdmin,
owner: timelock,
minStakeAmount: 1024 ether,
minUnstakeAmount: 1024 ether,
minCommissionRate: 5_00, // 5% in basis points
Expand Down Expand Up @@ -201,7 +289,7 @@ contract GenerateAlloc is Script {
vm.resetNonce(tmp);

InitializableHelper.disableInitializers(impl);
UpgradeEntrypoint(Predeploys.Upgrades).initialize(protocolAdmin);
UpgradeEntrypoint(Predeploys.Upgrades).initialize(timelock);

console2.log("UpgradeEntrypoint proxy deployed at:", Predeploys.Upgrades);
console2.log("UpgradeEntrypoint ProxyAdmin deployed at:", EIP1967Helper.getAdmin(Predeploys.Upgrades));
Expand All @@ -220,17 +308,30 @@ contract GenerateAlloc is Script {
vm.resetNonce(tmp);

InitializableHelper.disableInitializers(impl);
UBIPool(Predeploys.UBIPool).initialize(protocolAdmin);
UBIPool(Predeploys.UBIPool).initialize(timelock);

console2.log("UBIPool proxy deployed at:", Predeploys.UBIPool);
console2.log("UBIPool ProxyAdmin deployed at:", EIP1967Helper.getAdmin(Predeploys.UBIPool));
console2.log("UBIPool impl at:", EIP1967Helper.getImplementation(Predeploys.UBIPool));
console2.log("UBIPool owner:", UBIPool(Predeploys.UBIPool).owner());
}

function setCreate3() internal {
address tmp = address(new Create3());
vm.etch(Predeploys.Create3, tmp.code);

// reset tmp
vm.etch(tmp, "");
vm.store(tmp, 0, "0x");
vm.resetNonce(tmp);

vm.deal(Predeploys.Create3, 1);
console2.log("Create3 deployed at:", Predeploys.Create3);
}

function setAllocations() internal {
// EL Predeploys
vm.deal(0x0000000000000000000000000000000000000001, 1);
// Geth precompiles
vm.deal(0x0000000000000000000000000000000000000001, 1);
vm.deal(0x0000000000000000000000000000000000000002, 1);
vm.deal(0x0000000000000000000000000000000000000003, 1);
Expand All @@ -240,6 +341,7 @@ contract GenerateAlloc is Script {
vm.deal(0x0000000000000000000000000000000000000007, 1);
vm.deal(0x0000000000000000000000000000000000000008, 1);
vm.deal(0x0000000000000000000000000000000000000009, 1);
// Story's IPGraph precompile
vm.deal(0x000000000000000000000000000000000000001a, 1);
// Allocation
if (block.chainid == MAINNET_CHAIN_ID) {
Expand Down
82 changes: 0 additions & 82 deletions contracts/script/TestPrecompileUpgrades.s.sol

This file was deleted.

4 changes: 4 additions & 0 deletions contracts/src/libraries/Predeploys.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ library Predeploys {
address internal constant UBIPool = 0xCccCCC0000000000000000000000000000000002;
address internal constant Upgrades = 0xccCCcc0000000000000000000000000000000003;

/// @notice Create3 factory address
/// @dev We maximize compatibility with the contracts deployed by ZeframLou
address internal constant Create3 = 0x9fBB3DF7C40Da2e5A0dE984fFE2CCB7C47cd0ABf;

/// @notice Return true if `addr` is not proxied
function notProxied(address addr) internal pure returns (bool) {
return addr == WIP;
Expand Down
8 changes: 1 addition & 7 deletions contracts/test/deploy/Create3.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,11 @@ pragma solidity ^0.8.23;
/// NOTE: pragma allowlist-secret must be inline (same line as the pubkey hex string) to avoid false positive
/// flag "Hex High Entropy String" in CI run detect-secrets

import { Test } from "forge-std/Test.sol";
import { Test } from "../utils/Test.sol";

import { Create3 } from "../../src/deploy/Create3.sol";

contract Create3Test is Test {
Create3 private create3;

function setUp() public {
create3 = new Create3();
}

function testCreate3_deploy() public {
// deploy and getDeployed should return same address when deployed by the same deployer and with same salt.
bytes32 salt = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
Expand Down
Loading

0 comments on commit 64bbd41

Please sign in to comment.