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

1706 improve versioning for the light client contract #1800

Merged
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
910c0e8
switched to using contract inheritance for new versions of the LC con…
alysiahuggins Jul 30, 2024
ae91d22
Merge branch 'main' into 1706-improve-versioning-for-the-light-client…
alysiahuggins Jul 30, 2024
c0e2b16
Merge branch 'main' into 1706-improve-versioning-for-the-light-client…
alysiahuggins Jul 30, 2024
60a6db4
Merge branch 'main' into 1706-improve-versioning-for-the-light-client…
alysiahuggins Jul 30, 2024
cdbe464
Merge branch 'main' into 1706-improve-versioning-for-the-light-client…
alysiahuggins Jul 31, 2024
8021a52
Merge branch 'main' into 1706-improve-versioning-for-the-light-client…
alysiahuggins Aug 7, 2024
35a6dd9
added initialize methods for each version and a third version of the …
alysiahuggins Aug 7, 2024
4a0b8ca
added documentation about smart contract upgrades to read me and modi…
alysiahuggins Aug 7, 2024
d0bee37
Merge branch 'main' into 1706-improve-versioning-for-the-light-client…
alysiahuggins Aug 7, 2024
ec52e14
Merge branch 'main' into 1706-improve-versioning-for-the-light-client…
alysiahuggins Aug 13, 2024
09f3732
adding more context to the docs with example that include the LC cont…
alysiahuggins Aug 20, 2024
105e26a
add more deployment notes
alysiahuggins Aug 20, 2024
50dec69
use the reinitializer modifier to safeguard against more that one re-…
alysiahuggins Aug 20, 2024
7c3569c
improving comments
alysiahuggins Aug 20, 2024
b30b52d
Merge branch 'main' into 1706-improve-versioning-for-the-light-client…
alysiahuggins Aug 20, 2024
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
27 changes: 17 additions & 10 deletions contracts/src/LightClient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,14 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {
function getVersion()
public
pure
virtual
returns (uint8 majorVersion, uint8 minorVersion, uint8 patchVersion)
{
return (1, 0, 0);
}

/// @notice only the owner can authorize an upgrade
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {
function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {
emit Upgrade(newImplementation);
}

Expand Down Expand Up @@ -237,7 +238,7 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {
function newFinalizedState(
LightClientState memory newState,
IPlonkVerifier.PlonkProof memory proof
) external {
) external virtual {
//revert if we're in permissionedProver mode and the permissioned prover has not been set
if (permissionedProverEnabled && msg.sender != permissionedProver) {
if (permissionedProver == address(0)) {
Expand Down Expand Up @@ -287,12 +288,12 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {
}

/// @dev Simple getter function for the genesis state
function getGenesisState() public view returns (LightClientState memory) {
function getGenesisState() public view virtual returns (LightClientState memory) {
return states[genesisState];
}

/// @dev Simple getter function for the finalized state
function getFinalizedState() public view returns (LightClientState memory) {
function getFinalizedState() public view virtual returns (LightClientState memory) {
return states[finalizedState];
}

Expand Down Expand Up @@ -322,7 +323,7 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {

/// @notice Advance to the next epoch (without any precondition check!)
/// @dev This meant to be invoked only internally after appropriate precondition checks are done
function _advanceEpoch() private {
function _advanceEpoch() internal virtual {
bytes32 newStakeTableComm = computeStakeTableComm(states[finalizedState]);
votingStakeTableCommitment = frozenStakeTableCommitment;
frozenStakeTableCommitment = newStakeTableComm;
Expand All @@ -335,7 +336,12 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {
}

/// @notice Given the light client state, compute the short commitment of the stake table
function computeStakeTableComm(LightClientState memory state) public pure returns (bytes32) {
function computeStakeTableComm(LightClientState memory state)
public
pure
virtual
returns (bytes32)
{
return keccak256(
abi.encodePacked(
state.stakeTableBlsKeyComm,
Expand All @@ -349,7 +355,7 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {
/// non-zero address provided
/// @dev this function can also be used to update the permissioned prover once it's a different
/// address
function setPermissionedProver(address prover) public onlyOwner {
function setPermissionedProver(address prover) public virtual onlyOwner {
if (prover == address(0)) {
revert InvalidAddress();
}
Expand All @@ -363,7 +369,7 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {

/// @notice set the permissionedProverMode to false and set the permissionedProver to address(0)
/// @dev if it was already disabled (permissioneProverMode == false), then revert with
function disablePermissionedProverMode() public onlyOwner {
function disablePermissionedProverMode() public virtual onlyOwner {
if (permissionedProverEnabled) {
permissionedProver = address(0);
permissionedProverEnabled = false;
Expand Down Expand Up @@ -419,7 +425,7 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {
}

/// @notice get the number of L1 block updates
function getStateUpdateBlockNumbersCount() public view returns (uint256) {
function getStateUpdateBlockNumbersCount() public view virtual returns (uint256) {
return stateUpdateBlockNumbers.length;
}

Expand All @@ -429,6 +435,7 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {
function getHotShotCommitment(uint256 hotShotBlockHeight)
public
view
virtual
returns (HotShotCommitment memory)
{
uint256 commitmentsHeight = hotShotCommitments.length;
Expand All @@ -447,7 +454,7 @@ contract LightClient is Initializable, OwnableUpgradeable, UUPSUpgradeable {
}

/// @notice get the number of HotShot block commitments
function getHotShotBlockCommitmentsCount() public view returns (uint256) {
function getHotShotBlockCommitmentsCount() public view virtual returns (uint256) {
return hotShotCommitments.length;
}
}
94 changes: 0 additions & 94 deletions contracts/test/LightClientUpgradeToV2.t.sol

This file was deleted.

168 changes: 168 additions & 0 deletions contracts/test/LightClientUpgradeToVx.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// 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 { LightClientV2 as LCV2 } from "../test/LightClientV2.sol";
import { LightClientV3 as LCV3 } from "../test/LightClientV3.sol";
import { DeployLightClientContractScript } from "../script/LightClient.s.sol";
import { UpgradeLightClientScript } from "./UpgradeLightClientToV2.s.sol";
import { UpgradeLightClientScript as ULCV3 } from "./UpgradeLightClientToV3.s.sol";
import { OwnableUpgradeable } from
"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract LightClientUpgradeToVxTest is Test {
LCV1 public lcV1Proxy;
LCV2 public lcV2Proxy;
LCV3 public lcV3Proxy;

DeployLightClientContractScript public deployer = new DeployLightClientContractScript();
UpgradeLightClientScript public upgraderV2 = new UpgradeLightClientScript();
ULCV3 public upgraderV3 = new ULCV3();

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
Copy link
Contributor

@Sneh1999 Sneh1999 Aug 12, 2024

Choose a reason for hiding this comment

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

nit: test that the data remains the same after upgrading the implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorted here 7c3569c

function testUpgradeSameDataV1ToV2() 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
uint256 myNewField = 123;
lcV2Proxy = LCV2(upgraderV2.run(proxy, myNewField, admin));

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

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

LCV2.ExtendedLightClientState memory expectedExtendedLightClientState =
LCV2.ExtendedLightClientState(0);

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

// that the data remains the same after upgrading the implementation
Copy link
Contributor

@Sneh1999 Sneh1999 Aug 12, 2024

Choose a reason for hiding this comment

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

nit: test that the data remains the same after upgrading the implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorted here 7c3569c

function testUpgradeSameDataV2ToV3() 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
uint256 myNewField = 123;
uint256 myNewFieldV3 = 456;
lcV2Proxy = LCV2(upgraderV2.run(proxy, myNewField, admin));

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

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

LCV2.ExtendedLightClientState memory expectedExtendedLightClientState =
LCV2.ExtendedLightClientState(0);

assertEq(abi.encode(lcV2Proxy.getFinalizedState()), abi.encode(expectedLightClientState));
assertEq(
abi.encode(lcV2Proxy.getExtendedFinalizedState()),
abi.encode(expectedExtendedLightClientState)
);

// upgrade to v3
lcV3Proxy = LCV3(upgraderV3.run(proxy, myNewFieldV3, admin));

assertEq(lcV3Proxy.newField(), myNewField);
assertEq(lcV3Proxy.anotherField(), myNewFieldV3);
assertEq(lcV3Proxy.blocksPerEpoch(), 10);
assertEq(lcV3Proxy.currentEpoch(), 0);

assertEq(abi.encode(lcV3Proxy.getFinalizedState()), abi.encode(expectedLightClientState));
assertEq(
abi.encode(lcV3Proxy.getExtendedFinalizedState()),
abi.encode(expectedExtendedLightClientState)
);
}

// 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(upgraderV2.run(proxy, 123, admin));
assertEq(address(lcV2Proxy), address(lcV1Proxy));
(uint8 majorV2, uint8 minorV2, uint8 patchV2) = lcV2Proxy.getVersion();
assertEq(majorV2, 2);
assertEq(minorV2, 0);
assertEq(patchV2, 0);
}

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

//attempted upgrade as attacker will revert
vm.expectRevert(
abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, attacker)
);

lcV2Proxy = LCV2(upgraderV2.run(address(proxy), 123, attacker));
}

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

//attempted upgrade as attacker will revert

vm.expectRevert(
abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, attacker)
);
lcV3Proxy = LCV3(upgraderV3.run(address(proxy), 456, attacker));
}
}
Loading
Loading