Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(genesis): mainnet values in gen alloc and testing #443

Merged
merged 4 commits into from
Dec 18, 2024
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
103 changes: 48 additions & 55 deletions contracts/script/GenerateAlloc.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { IPTokenStaking } from "../src/protocol/IPTokenStaking.sol";
import { UpgradeEntrypoint } from "../src/protocol/UpgradeEntrypoint.sol";
import { UBIPool } from "../src/protocol/UBIPool.sol";

import { ChainIds } from "./utils/ChainIds.sol";
import { EIP1967Helper } from "./utils/EIP1967Helper.sol";
import { InitializableHelper } from "./utils/InitializableHelper.sol";
import { Predeploys } from "../src/libraries/Predeploys.sol";
Expand Down Expand Up @@ -49,87 +50,69 @@ contract GenerateAlloc is Script {

string internal dumpPath = getDumpPath();
bool public saveState = true;
uint256 public constant STORY_CHAIN_ID = 1415; // Story mainnet chain ID. TBD
// Optionally allocate 10k test accounts for devnets/testnets
bool private constant ALLOCATE_10K_TEST_ACCOUNTS = false;
// Optionally keep the timelock admin role for testnets
bool private constant KEEP_TIMELOCK_ADMIN_ROLE = true;

/// @notice this call should only be available from Test.sol, for speed
function disableStateDump() external {
require(block.chainid == 31337, "Only for local tests");
require(block.chainid == ChainIds.FOUNDRY, "Only for local tests");
saveState = false;
}

/// @dev this call should only be available from Test.sol
function setAdminAddresses(address protocol, address executor, address guardian) external {
Copy link
Contributor

@jdubpark jdubpark Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, we could write to internal storage slots directly in tests (but having setAdminAddresses in script doesn't impact performance or anything).

require(block.chainid == 31337, "Only for local tests");
require(block.chainid == ChainIds.FOUNDRY, "Only for local tests");
protocolAdmin = protocol;
timelockExecutor = executor;
timelockGuardian = guardian;
}

/// @notice path where alloc file will be stored
function getDumpPath() internal view returns (string memory) {
if (block.chainid == 1513) {
if (block.chainid == ChainIds.ILIAD) {
return "./iliad-alloc.json";
} else if (block.chainid == 1512) {
} else if (block.chainid == ChainIds.MININET) {
return "./mininet-alloc.json";
} else if (block.chainid == 1315) {
} else if (block.chainid == ChainIds.ODYSSEY_DEVNET) {
return "./odyssey-devnet-alloc.json";
} else if (block.chainid == 1516) {
} else if (block.chainid == ChainIds.ODYSSEY_TESTNET) {
return "./odyssey-testnet-alloc.json";
} else if (block.chainid == 31337) {
} else if (block.chainid == ChainIds.LOCAL) {
return "./local-alloc.json";
} else if (block.chainid == STORY_CHAIN_ID) {
return "./story-alloc.json";
} else if (block.chainid == ChainIds.FOUNDRY) {
return "./foundry-alloc.json";
} else if (block.chainid == ChainIds.STORY_MAINNET) {
return "./mainnet-alloc.json";
} else {
revert("Unsupported chain id");
}
}

/// @notice Get the minimum delay for the timelock
function getTimelockMinDelay() internal view returns (uint256) {
if (block.chainid == 1513) {
if (block.chainid == ChainIds.ILIAD) {
// Iliad
return 1 days;
} else if (block.chainid == 1512) {
} else if (block.chainid == ChainIds.MININET) {
// Mininet
return 10 seconds;
} else if (block.chainid == 1315) {
} else if (block.chainid == ChainIds.ODYSSEY_DEVNET) {
// Odyssey devnet
return 10 seconds;
} else if (block.chainid == 1516) {
} else if (block.chainid == ChainIds.ODYSSEY_TESTNET) {
// Odyssey testnet
return 1 days;
} else if (block.chainid == 31337) {
} else if (block.chainid == ChainIds.LOCAL) {
// Local
return 10 seconds;
} else {
revert("Unsupported chain id");
}
}

/// @notice Gets the proposers, executors and canceller for the timelocks
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, canceller);
} 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, canceller);
} else if (block.chainid == ChainIds.FOUNDRY) {
// Foundry
return 10 seconds;
} else if (block.chainid == ChainIds.STORY_MAINNET) {
// Mainnet
return 2 days;
} else {
revert("Unsupported chain id");
}
Expand All @@ -146,14 +129,16 @@ contract GenerateAlloc is Script {
if (timelockExecutor == address(0)) {
timelockExecutor = vm.envAddress("TIMELOCK_EXECUTOR_ADDRESS");
}
require(timelockExecutor != address(0), "executor not set");
if (timelockExecutor == address(0)) {
console2.log("TimelockExecutor not set, executing timelock operations is public");
}

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

if (block.chainid == STORY_CHAIN_ID) {
if (block.chainid == ChainIds.STORY_MAINNET) {
require(!KEEP_TIMELOCK_ADMIN_ROLE, "Timelock admin role not allowed on mainnet");
} else {
console2.log("Will timelock admin role be assigned?", KEEP_TIMELOCK_ADMIN_ROLE);
Expand Down Expand Up @@ -214,28 +199,37 @@ contract GenerateAlloc is Script {
/// @notice Deploy TimelockController to manage upgrades and admin actions
/// @dev this is a deterministic deployment, not a predeploy (won't show in genesis file).
function deployTimelock() internal {
// We deploy this with Create3 because we can't set storage variables in constructor with vm.etch

// WARNING: Make sure protocolAdmin and timelockGuardian are multisigs on mainnet
uint256 minDelay = getTimelockMinDelay();
(address[] memory proposers, address[] memory executors, address canceller) = getTimelockControllers();
address[] memory proposers = new address[](1);
proposers[0] = protocolAdmin;
Ramarti marked this conversation as resolved.
Show resolved Hide resolved
address[] memory executors = new address[](1);
executors[0] = timelockExecutor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it setting it to anyone can execute?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It allows having address(0) in env files so it's public

if (timelockExecutor == address(0)) {

address canceller = timelockGuardian;
Ramarti marked this conversation as resolved.
Show resolved Hide resolved

bytes memory creationCode = abi.encodePacked(
type(TimelockController).creationCode,
abi.encode(minDelay, proposers, executors, deployer)
abi.encode(minDelay, proposers, executors, protocolAdmin)
);
bytes32 salt = keccak256("STORY_TIMELOCK_CONTROLLER");
// We deploy this with Create3 because we can't set storage variables in constructor with vm.etch
timelock = Create3(Predeploys.Create3).deploy(salt, creationCode);

vm.stopPrank();
bytes32 cancellerRole = TimelockController(payable(timelock)).CANCELLER_ROLE();
vm.prank(protocolAdmin);
TimelockController(payable(timelock)).grantRole(
TimelockController(payable(timelock)).CANCELLER_ROLE(),
cancellerRole,
canceller
);
if (!KEEP_TIMELOCK_ADMIN_ROLE) {
bytes32 adminRole = TimelockController(payable(timelock)).DEFAULT_ADMIN_ROLE();
TimelockController(payable(timelock)).renounceRole(
TimelockController(payable(timelock)).DEFAULT_ADMIN_ROLE(),
deployer
adminRole,
protocolAdmin
);
}
vm.stopPrank();
vm.startPrank(deployer);

console2.log("TimelockController deployed at:", timelock);
}
Expand Down Expand Up @@ -410,9 +404,9 @@ contract GenerateAlloc is Script {
// Story's IPGraph precompile
vm.deal(0x0000000000000000000000000000000000000101, 1);
// Allocation
if (block.chainid == STORY_CHAIN_ID) {
if (block.chainid == ChainIds.STORY_MAINNET) {
// TBD
} else if (block.chainid == 1315) {
} else if (block.chainid == ChainIds.ODYSSEY_DEVNET) {
// Odyssey devnet alloc
vm.deal(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 100000000 ether);
vm.deal(0xf398C12A45Bc409b6C652E25bb0a3e702492A4ab, 100000000 ether);
Expand All @@ -422,7 +416,7 @@ contract GenerateAlloc is Script {
vm.deal(0x00FCeC044cD73e8eC6Ad771556859b00C9011111, 100000000 ether);
vm.deal(0xb5350B7CaE94C2bF6B2b56Ef6A06cC1153900000, 100000000 ether);
vm.deal(0x13919a0d8603c35DAC923f92D7E4e1D55e993898, 100000000 ether);
} else if (block.chainid == 1516) {
} else if (block.chainid == ChainIds.ODYSSEY_TESTNET) {
// Odyssey testnet alloc
vm.deal(0x5687400189B13551137e330F7ae081142EdfD866, 200000000 ether);
vm.deal(0x56A26642ad963D3542DdAe4d8fdECC396153c2f6, 200000000 ether);
Expand All @@ -443,8 +437,7 @@ contract GenerateAlloc is Script {
vm.deal(0x13919a0d8603c35DAC923f92D7E4e1D55e993898, 100000000 ether);
vm.deal(0x64a2fdc6f7CD8AA42e0bb59bf80bC47bFFbe4a73, 100000000 ether);
}
// Test for big allocations
if (ALLOCATE_10K_TEST_ACCOUNTS && block.chainid != STORY_CHAIN_ID) {
if (ALLOCATE_10K_TEST_ACCOUNTS && block.chainid != ChainIds.STORY_MAINNET) {
setTestAllocations();
}
}
Expand Down
12 changes: 12 additions & 0 deletions contracts/script/utils/ChainIds.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

library ChainIds {
uint256 public constant STORY_MAINNET = 1415; // TBD
uint256 public constant ILIAD = 1513;
uint256 public constant MININET = 1512;
uint256 public constant ODYSSEY_DEVNET = 1315;
uint256 public constant ODYSSEY_TESTNET = 1516;
uint256 public constant LOCAL = 1511;
uint256 public constant FOUNDRY = 31337;
}
31 changes: 31 additions & 0 deletions contracts/test/timelock/Timelock.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,35 @@ contract TimelockTest is Test {
timelock.execute(target, value, data3, id2, salt3);
assertEq(ipTokenStaking.fee(), 4 ether);
}

function testTimelockMinDelay() public {
address target = address(ipTokenStaking);
uint256 value = 0;
bytes memory data1 = abi.encodeWithSelector(IPTokenStaking.setFee.selector, 2 ether);
bytes32 salt1 = keccak256("SALT_1");
uint256 minDelay = timelock.getMinDelay();

// Scheduling under the min delay should fail
vm.expectRevert(
abi.encodeWithSelector(TimelockController.TimelockInsufficientDelay.selector, minDelay - 1, minDelay)
);
vm.prank(admin);
timelock.schedule(target, value, data1, bytes32(0), salt1, minDelay - 1);

// Scheduling over the min delay should succeed
vm.prank(admin);
timelock.schedule(target, value, data1, bytes32(0), salt1, minDelay + 1 minutes);

// Should revert if we try to execute before scheduled time
vm.warp(block.timestamp + minDelay);
vm.expectRevert();
vm.prank(executor);
timelock.execute(target, value, data1, bytes32(0), salt1);

// Should succeed if we wait for the delay to pass
vm.warp(block.timestamp + minDelay + 1 minutes);
vm.prank(executor);
timelock.execute(target, value, data1, bytes32(0), salt1);
assertEq(ipTokenStaking.fee(), 2 ether);
}
}
24 changes: 24 additions & 0 deletions contracts/test/upgrades/PredeployUpgrades.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,28 @@ contract PredeployUpgrades is Test {
expectRevertTimelocked(address(proxyAdmin), upgradeAction, expectedReason);
}
}

function testUpgradeLastPredeploy() public {
address newImpl = address(new InitialImplementation());
address lastPredeploy = address(uint160(Predeploys.Namespace) + uint160(Predeploys.NamespaceSize));
ProxyAdmin proxyAdmin = ProxyAdmin(EIP1967Helper.getAdmin(lastPredeploy));
assertEq(proxyAdmin.owner(), address(timelock));

// Upgrade last predeploy to new implementation
performTimelocked(
address(proxyAdmin),
abi.encodeWithSelector(
ProxyAdmin.upgradeAndCall.selector,
ITransparentUpgradeableProxy(lastPredeploy),
newImpl,
""
)
);
assertEq(EIP1967Helper.getImplementation(lastPredeploy), newImpl, "Last predeploy not upgraded");
assertEq(
keccak256(abi.encode(InitialImplementation(lastPredeploy).foo())),
keccak256(abi.encode("bar")),
"Upgraded to wrong iface"
);
}
}
Loading