Skip to content

Commit

Permalink
modify upgrade scripts for non-multisig, non-openzeppelin-defender de…
Browse files Browse the repository at this point in the history
…ployments (#1705)

* use the same LC file for LCV2 since a separate file has not been used for further deployments

* changes to script for upgradge deployment alongside readme updates

* add support for mnenomic offset

* modified scripts required for upgrading the LC without OPZ defender
  • Loading branch information
alysiahuggins authored Jul 24, 2024
1 parent 3fe0e95 commit cd9ae47
Show file tree
Hide file tree
Showing 14 changed files with 854 additions and 26 deletions.
153 changes: 153 additions & 0 deletions contracts/broadcast/LightClient.s.sol/11155111/run-1720726496.json

Large diffs are not rendered by default.

153 changes: 153 additions & 0 deletions contracts/broadcast/LightClient.s.sol/11155111/run-latest.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

131 changes: 131 additions & 0 deletions contracts/broadcast/UpgradeLightClient.s.sol/11155111/run-latest.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion contracts/script/LightClient.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity ^0.8.0;
import "forge-std/Script.sol";

import { LightClient as LC } from "../src/LightClient.sol";
import { LightClientMock as LCMock } from "../test/mocks/LightClientMock.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract DeployLightClientContractScript is Script {
Expand Down
67 changes: 53 additions & 14 deletions contracts/script/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@ Upgradeable Smart contracts are deployed with Openzeppelin Defender to enable a
and also uses a multi-sig Safe wallet. When deploying using openzeppelin the `defender` profile in the `foundry.toml`
file is used.

Note: One must set the environment variable `FOUNDRY_PROFILE` accordingly to use the appropriate profile in the
`foundry.toml` settings. E.g.

```bash
export FOUNDRY_PROFILE=defender
```

The profile, `profile.default` is used by default. Setting the `FOUNDRY_PROFILE` variable overrides the `foundry.toml`
settings.

## Prerequisites

1. Create a multisig wallet using [Safe](https://app.safe.global/welcome/accounts) on the network you'd like to deploy
Expand All @@ -29,8 +19,10 @@ settings.
Sepolia) and "Production Environment" for mainnet.
1. Choose a network
1. Select the approval process created in Step 2
1. Be sure to save DEFENDER_SECRET ("Team Secret key") and DEFENDER_KEY ("Team API Key"), that is shown at the end of
this step, into the .env file. The keys won't be available later at a later point.
1. Be sure to save `DEFENDER_SECRET` ("Team Secret key") and `DEFENDER_KEY` ("Team API Key"), that is shown at the
end of this step, into the `.env` file. The keys won't be available later at a later point.
4. In the home folder of this repo, you're in a nix shell: Enter `nix-shell` in the terminal
5. If the contracts have never been compiled run, `forge build`

## Deployments

Expand All @@ -41,7 +33,7 @@ Steps:
1. Run the Deployment command.

```bash
forge clean && forge script contracts/script/FeeContractWithDefender.s.sol:FeeContractDefenderDeployScript --ffi --rpc-url https://ethereum-sepolia.publicnode.com --build-info true
forge clean && FeeContractWithDefender.s.sol:FeeContractDefenderDeployScript --ffi --rpc-url https://ethereum-sepolia.publicnode.com --build-info true
```

1. Go to the [deploy](https://defender.openzeppelin.com/v2/#/deploy) tab OpenZeppelin Defender's UI and click on the
Expand Down Expand Up @@ -102,7 +94,7 @@ forge verify-contract --chain-id 11155111 \
--watch --etherscan-api-key $ETHERSCAN_API_KEY \
--compiler-version $SOLC_VERSION \
$LIGHT_CLIENT_CONTRACT_ADDRESS \
contracts/src/LightClient.sol:LightClient --watch
contracts/src/LightClient.sol:LightClient
```
3. Inform Etherscan that it's a Proxy When the proxy is deployed, go to Etherscan. Go to Contract > Code > More Options
Expand Down Expand Up @@ -169,3 +161,50 @@ This error occurs when build_info is set to true in the foundry.toml configurati
foundry profile is set to default when running commands like `just gen-bindings`.

Solution: `export FOUNDRY_PROFILE=default`

# Deploying Upgradable Contracts without OpenZeppelin Defender or a Safe Multisig Wallet

## LightClient Contract Deployment

```bash
forge script contracts/script/LightClient.s.sol:DeployLightClientContractScript $numBlocksPerEpoch $numInitValidators \
--sig 'run(uint32, uint32)' \
--ffi \
--rpc-url https://ethereum-sepolia.publicnode.com
```

## LightClient Contract Upgrade

```bash
forge script contracts/script/UpgradeLightClient.s.sol:UpgradeLightClientScript $admin $mostRecentlyDeployedProxy \
--sig 'run(address, address)' \
--ffi \
--rpc-url https://ethereum-sepolia.publicnode.com
```

# Deploy and Upgrade without Defender

Change the $MNEMONIC in the .env file to the one of the admin

To Deploy

```bash
forge script contracts/script/LightClient.s.sol:DeployLightClientContractScript $numBlocksPerEpoch $numInitValidators \
--sig 'run(uint32, uint32)' \
--ffi \
--rpc-url https://ethereum-sepolia.publicnode.com\
--broadcast --legacy
```

To Upgrade (assuming it's the same LightClient.sol file being used (pre-mainnet))
```bash
forge script contracts/script/UpgradeSameLightClient.s.sol:UpgradeLightClientScript $mnemonicOffset $mostRecentlyDeployedProxy \
--sig 'run(uint32, address)' \
--ffi \
--rpc-url https://ethereum-sepolia.publicnode.com \
--broadcast --legacy
```
Note: the `$mnemonicOffset` should be zero by default if address referenced by the `$MNEMONIC` in the `.env` is the
first address in that wallet. Otherwise, please specify the correct `$mnemonicOffset`
1 change: 0 additions & 1 deletion contracts/script/UpgradeBox.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ contract UpgradeBoxScript is Script {
DemoBoxV1 proxy = DemoBoxV1(proxyAddress); //make the function call on the previous
// implementation
vm.prank(admin);

proxy.upgradeToAndCall(newBox, ""); //proxy address now points to the new implementation
return address(proxy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ contract UpgradeLightClientScript is Script {
/// @param mostRecentlyDeployedProxy address of deployed proxy
/// @return address of the proxy
/// TODO get the most recent deployment from the devops tooling
function run(address admin, address mostRecentlyDeployedProxy) external returns (address) {
address proxy = upgradeLightClient(admin, mostRecentlyDeployedProxy, address(new LCV2()));
function run(uint32 seedPhraseOffset, address mostRecentlyDeployedProxy)
external
returns (address)
{
string memory seedPhrase = vm.envString("MNEMONIC");
(address admin,) = deriveRememberKey(seedPhrase, seedPhraseOffset);
vm.startBroadcast(admin);
address proxy = upgradeLightClient(mostRecentlyDeployedProxy, address(new LCV2()));
return proxy;
}

Expand All @@ -22,15 +28,14 @@ contract UpgradeLightClientScript is Script {
/// @param proxyAddress address of proxy
/// @param newLightClient address of new implementation
/// @return address of the proxy
function upgradeLightClient(address admin, address proxyAddress, address newLightClient)
function upgradeLightClient(address proxyAddress, address newLightClient)
public
returns (address)
{
LC proxy = LC(proxyAddress); //make the function call on the previous implementation
vm.prank(admin);

proxy.upgradeToAndCall(newLightClient, ""); //proxy address now points to the new
// implementation
vm.stopBroadcast();
return address(proxy);
}
}
41 changes: 41 additions & 0 deletions contracts/script/UpgradeSameLightClient.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

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

import { LightClient as LCV2 } from "../src/LightClient.sol";
import { LightClient as LC } from "../src/LightClient.sol";

contract UpgradeLightClientScript is Script {
/// @notice runs the upgrade
/// @param mostRecentlyDeployedProxy address of deployed proxy
/// @return address of the proxy
/// TODO get the most recent deployment from the devops tooling
function run(uint32 seedPhraseOffset, address mostRecentlyDeployedProxy)
external
returns (address)
{
string memory seedPhrase = vm.envString("MNEMONIC");
(address admin,) = deriveRememberKey(seedPhrase, seedPhraseOffset);
vm.startBroadcast(admin);
address proxy = upgradeLightClient(mostRecentlyDeployedProxy, address(new LCV2()));
return proxy;
}

/// @notice upgrades the light client contract by calling the upgrade function the
/// implementation contract via
/// the proxy
/// @param proxyAddress address of proxy
/// @param newLightClient address of new implementation
/// @return address of the proxy
function upgradeLightClient(address proxyAddress, address newLightClient)
public
returns (address)
{
LC proxy = LC(proxyAddress); //make the function call on the previous implementation
proxy.upgradeToAndCall(newLightClient, ""); //proxy address now points to the new
// implementation
vm.stopBroadcast();
return address(proxy);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"multisig": "0xc56fA6505d10bF322e01327e22479DE78C3Bf1cE",
"newContractName": "LightClient.sol",
"proposalId": "8d347333-7e10-417e-96fd-a2b785115c58",
"proxyAddress": "0xbC781a2BCcdac8F65EF10EA85D765CA240D1789b",
"responseUrl": "https://app.safe.global/transactions/tx?safe=sep:0xc56fA6505d10bF322e01327e22479DE78C3Bf1cE&id=0xad6fe9ec8b6275b81e3a894fb87408fb77e9ad0020e40506aa189024fe28fae8",
"salt": 13
}
88 changes: 88 additions & 0 deletions contracts/test/LightClientUpgradeSameContract.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import { Test } /*, console2*/ from "forge-std/Test.sol";
import { LightClient as LCV1 } from "../src/LightClient.sol";
import { LightClient as LCV2 } from "../src/LightClient.sol";
import { DeployLightClientContractScript } from "../script/LightClient.s.sol";
import { UpgradeLightClientScript } from "../script/UpgradeSameLightClient.s.sol";

contract LightClientUpgradeSameContractTest is Test {
LCV1 public lcV1Proxy;
LCV2 public lcV2Proxy;

DeployLightClientContractScript public deployer = new DeployLightClientContractScript();
UpgradeLightClientScript public upgrader = new UpgradeLightClientScript();

LCV1.LightClientState public stateV1;

address public admin;
address public proxy;

// deploy the first implementation with its proxy
function setUp() public {
(proxy, admin, stateV1) = deployer.run(10, 5);
lcV1Proxy = LCV1(proxy);
}

function testCorrectInitialization() public view {
assert(lcV1Proxy.blocksPerEpoch() == 10);
assert(lcV1Proxy.currentEpoch() == 0);

assertEq(abi.encode(lcV1Proxy.getGenesisState()), abi.encode(stateV1));

assertEq(abi.encode(lcV1Proxy.getFinalizedState()), abi.encode(stateV1));

bytes32 stakeTableComm = lcV1Proxy.computeStakeTableComm(stateV1);
assertEq(lcV1Proxy.votingStakeTableCommitment(), stakeTableComm);
assertEq(lcV1Proxy.frozenStakeTableCommitment(), stakeTableComm);
assertEq(lcV1Proxy.votingThreshold(), stateV1.threshold);
assertEq(lcV1Proxy.frozenThreshold(), stateV1.threshold);
}

// that the data remains the same after upgrading the implementation
function testUpgradeSameData() public {
// Upgrade LightClient and check that the genesis state is not changed and that the new
// field
// of the upgraded contract is set to 0
lcV2Proxy = LCV2(upgrader.run(0, proxy));

assertEq(lcV2Proxy.blocksPerEpoch(), 10);
assertEq(lcV2Proxy.currentEpoch(), 0);

LCV2.LightClientState memory expectedLightClientState = LCV2.LightClientState(
stateV1.viewNum,
stateV1.blockHeight,
stateV1.blockCommRoot,
stateV1.feeLedgerComm,
stateV1.stakeTableBlsKeyComm,
stateV1.stakeTableSchnorrKeyComm,
stateV1.stakeTableAmountComm,
stateV1.threshold
);

assertEq(abi.encode(lcV2Proxy.getFinalizedState()), abi.encode(expectedLightClientState));
}

// check that the proxy address remains the same
function testUpgradesSameProxyAddress() public {
(uint8 major, uint8 minor, uint8 patch) = lcV1Proxy.getVersion();
assertEq(major, 1);
assertEq(minor, 0);
assertEq(patch, 0);

//upgrade box
lcV2Proxy = LCV2(upgrader.run(0, proxy));
assertEq(address(lcV2Proxy), address(lcV1Proxy));
}

function testMaliciousUpgradeFails() public {
address attacker = makeAddr("attacker");

//attempted upgrade as attacker will revert
vm.prank(attacker);
vm.expectRevert();
lcV2Proxy = LCV2(upgrader.run(0, address(proxy)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { Test } /*, console2*/ from "forge-std/Test.sol";
import { LightClient as LCV1 } from "../src/LightClient.sol";
import { LightClientV2 as LCV2 } from "../test/LightClientV2.sol";
import { DeployLightClientContractScript } from "../script/LightClient.s.sol";
import { UpgradeLightClientScript } from "../script/UpgradeLightClient.s.sol";
import { UpgradeLightClientScript } from "../script/UpgradeLightClientToV2.s.sol";

contract LightClientUpgradeTest is Test {
contract LightClientUpgradeToV2Test is Test {
LCV1 public lcV1Proxy;
LCV2 public lcV2Proxy;

Expand Down Expand Up @@ -46,7 +46,7 @@ contract LightClientUpgradeTest is Test {
// Upgrade LightClient and check that the genesis state is not changed and that the new
// field
// of the upgraded contract is set to 0
lcV2Proxy = LCV2(upgrader.run(admin, proxy));
lcV2Proxy = LCV2(upgrader.run(0, proxy));

assertEq(lcV2Proxy.newField(), 0);
assertEq(lcV2Proxy.blocksPerEpoch(), 10);
Expand Down Expand Up @@ -75,7 +75,7 @@ contract LightClientUpgradeTest is Test {
assertEq(patch, 0);

//upgrade box
lcV2Proxy = LCV2(upgrader.run(admin, proxy));
lcV2Proxy = LCV2(upgrader.run(0, proxy));
assertEq(address(lcV2Proxy), address(lcV1Proxy));
(uint8 majorV2, uint8 minorV2, uint8 patchV2) = lcV2Proxy.getVersion();
assertEq(majorV2, 2);
Expand All @@ -87,7 +87,8 @@ contract LightClientUpgradeTest is Test {
address attacker = makeAddr("attacker");

//attempted upgrade as attacker will revert
vm.prank(attacker);
vm.expectRevert();
lcV2Proxy = LCV2(upgrader.run(attacker, address(proxy)));
lcV2Proxy = LCV2(upgrader.run(0, address(proxy)));
}
}
24 changes: 24 additions & 0 deletions contracts/test/LightClientV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ contract LightClientV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
/// @notice mapping to store light client states in order to simplify upgrades
mapping(uint32 index => LightClientState value) public states;

/// @notice the address of the prover that can call the newFinalizedState function when the
/// contract is
/// in permissioned prover mode. This address is address(0) when the contract is not in the
/// permissioned prover mode
address public permissionedProver;

/// @notice a flag that indicates when a permissioned provrer is needed
bool public permissionedProverEnabled;

/// @notice an array to store the L1 Block Heights where the finalizedState was updated
uint256[] public stateUpdateBlockNumbers;

/// @notice an array to store the HotShot Block Heights and their respective HotShot
/// commitments
HotShotCommitment[] public hotShotCommitments;

/// @notice new field for testing purposes
/// @dev In order to add a field to LightClientState struct one can: add a new contract variable
/// that has the new struct type, or put the struct inside a map.
Expand Down Expand Up @@ -84,6 +100,14 @@ contract LightClientV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
uint32 extraField; // New field for testing purposes
}

/// @notice Simplified HotShot commitment struct
/// @param blockHeight The block height of the latest finalized HotShot block
/// @param blockCommRoot The merkle root of historical block commitments (BN254::ScalarField)
struct HotShotCommitment {
uint64 blockHeight;
BN254.ScalarField blockCommRoot;
}

/// @notice Event that a new finalized state has been successfully verified and updated
event NewState(
uint64 indexed viewNum, uint64 indexed blockHeight, BN254.ScalarField blockCommRoot
Expand Down

0 comments on commit cd9ae47

Please sign in to comment.