Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;

import {EOADeployer} from "zeus-templates/templates/EOADeployer.sol";
import "../Env.sol";
import "../TestUtils.sol";

/**
* @title DeployRewardsCoordinatorImpl
* @notice Deploy new RewardsCoordinator implementation with Rewards v2.2 support.
* This adds support for:
* - Unique stake rewards submissions (rewards linear to allocated unique stake)
* - Total stake rewards submissions (rewards linear to total stake)
* - Updated MAX_REWARDS_DURATION to 730 days (63072000 seconds)
*/
contract DeployRewardsCoordinatorImpl is EOADeployer {
using Env for *;
using TestUtils for *;

/// forgefmt: disable-next-item
function _runAsEOA() internal override {
// Only execute on source chains with version 1.9.0
if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) {
return;
}

vm.startBroadcast();

// Update the MAX_REWARDS_DURATION environment variable before deployment
// 63072000s = 730 days = 2 years
zUpdateUint32("REWARDS_COORDINATOR_MAX_REWARDS_DURATION", 63072000);

// Deploy RewardsCoordinator implementation with the updated MAX_REWARDS_DURATION
deployImpl({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can use the CoreContractsDeployer lib: deployRewardsCoordinator

name: type(RewardsCoordinator).name,
deployedTo: address(
new RewardsCoordinator(
IRewardsCoordinatorTypes.RewardsCoordinatorConstructorParams({
delegationManager: Env.proxy.delegationManager(),
strategyManager: Env.proxy.strategyManager(),
allocationManager: Env.proxy.allocationManager(),
pauserRegistry: Env.impl.pauserRegistry(),
permissionController: Env.proxy.permissionController(),
CALCULATION_INTERVAL_SECONDS: Env.CALCULATION_INTERVAL_SECONDS(),
MAX_REWARDS_DURATION: Env.MAX_REWARDS_DURATION(), // Using updated env value
MAX_RETROACTIVE_LENGTH: Env.MAX_RETROACTIVE_LENGTH(),
MAX_FUTURE_LENGTH: Env.MAX_FUTURE_LENGTH(),
GENESIS_REWARDS_TIMESTAMP: Env.GENESIS_REWARDS_TIMESTAMP()
})
)
)
});

vm.stopBroadcast();
}

function testScript() public virtual {
if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) {
return;
}

// Deploy the new RewardsCoordinator implementation
runAsEOA();

_validateNewImplAddress();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can use the implementation validations in TestUtils.sol. Specifically:

        // Run tests
        TestUtils.validateProxyAdmins();
        TestUtils.validateImplConstructors();
        TestUtils.validateImplsNotInitializable();

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should keep upgrade specific assertions: _validateZeusEnvUpdated, _validateNewFunctionality, and _validateStorageLayout

_validateProxyAdmin();
_validateImplConstructor();
_validateZeusEnvUpdated();
_validateImplInitialized();
_validateNewFunctionality();
_validateStorageLayout();
}

/// @dev Validate that the new RewardsCoordinator impl address is distinct from the current one
function _validateNewImplAddress() internal view {
address currentImpl = TestUtils._getProxyImpl(address(Env.proxy.rewardsCoordinator()));
address newImpl = address(Env.impl.rewardsCoordinator());

assertFalse(currentImpl == newImpl, "RewardsCoordinator impl should be different from current implementation");
}

/// @dev Validate that the RewardsCoordinator proxy is still owned by the correct ProxyAdmin
function _validateProxyAdmin() internal view {
address pa = Env.proxyAdmin();

assertTrue(
TestUtils._getProxyAdmin(address(Env.proxy.rewardsCoordinator())) == pa,
"RewardsCoordinator proxyAdmin incorrect"
);
}

/// @dev Validate the immutables set in the new RewardsCoordinator implementation constructor
function _validateImplConstructor() internal view {
RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();

// Validate core dependencies
assertTrue(
address(rewardsCoordinatorImpl.delegationManager()) == address(Env.proxy.delegationManager()),
"RewardsCoordinator delegationManager mismatch"
);
assertTrue(
address(rewardsCoordinatorImpl.strategyManager()) == address(Env.proxy.strategyManager()),
"RewardsCoordinator strategyManager mismatch"
);
assertTrue(
address(rewardsCoordinatorImpl.allocationManager()) == address(Env.proxy.allocationManager()),
"RewardsCoordinator allocationManager mismatch"
);
assertTrue(
address(rewardsCoordinatorImpl.pauserRegistry()) == address(Env.impl.pauserRegistry()),
"RewardsCoordinator pauserRegistry mismatch"
);
assertTrue(
address(rewardsCoordinatorImpl.permissionController()) == address(Env.proxy.permissionController()),
"RewardsCoordinator permissionController mismatch"
);

// Validate reward parameters
assertEq(
rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(),
Env.CALCULATION_INTERVAL_SECONDS(),
"CALCULATION_INTERVAL_SECONDS mismatch"
);
assertEq(
rewardsCoordinatorImpl.MAX_REWARDS_DURATION(),
63_072_000,
"MAX_REWARDS_DURATION should be updated to 730 days (63072000 seconds)"
);
assertEq(
rewardsCoordinatorImpl.MAX_RETROACTIVE_LENGTH(),
Env.MAX_RETROACTIVE_LENGTH(),
"MAX_RETROACTIVE_LENGTH mismatch"
);
assertEq(rewardsCoordinatorImpl.MAX_FUTURE_LENGTH(), Env.MAX_FUTURE_LENGTH(), "MAX_FUTURE_LENGTH mismatch");
assertEq(
rewardsCoordinatorImpl.GENESIS_REWARDS_TIMESTAMP(),
Env.GENESIS_REWARDS_TIMESTAMP(),
"GENESIS_REWARDS_TIMESTAMP mismatch"
);
}

/// @dev Validate that the zeus environment variable has been updated correctly
function _validateZeusEnvUpdated() internal view {
RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();

// Validate that the zeus env MAX_REWARDS_DURATION matches what was deployed
assertEq(
rewardsCoordinatorImpl.MAX_REWARDS_DURATION(),
Env.MAX_REWARDS_DURATION(),
"Deployed MAX_REWARDS_DURATION should match zeus env value"
);

// Also validate it equals the expected value
assertEq(
Env.MAX_REWARDS_DURATION(),
63_072_000,
"Zeus env MAX_REWARDS_DURATION should be updated to 730 days (63072000 seconds)"
);
}

/// @dev Validate that the new implementation cannot be initialized (should revert)
function _validateImplInitialized() internal {
bytes memory errInit = "Initializable: contract is already initialized";

RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();

vm.expectRevert(errInit);
rewardsCoordinatorImpl.initialize(
address(0), // initialOwner
0, // initialPausedStatus
address(0), // rewardsUpdater
0, // activationDelay
0 // defaultSplitBips
);
}

/// @dev Validate new Rewards v2.2 functionality
function _validateNewFunctionality() internal view {
RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();

// The new functions exist (this will fail to compile if they don't exist)
// Just checking that the contract has the expected interface
bytes4 createUniqueStakeSelector = rewardsCoordinatorImpl.createUniqueStakeRewardsSubmission.selector;
bytes4 createTotalStakeSelector = rewardsCoordinatorImpl.createTotalStakeRewardsSubmission.selector;

// Verify the selectors are non-zero (functions exist)
assertTrue(createUniqueStakeSelector != bytes4(0), "createUniqueStakeRewardsSubmission function should exist");
assertTrue(createTotalStakeSelector != bytes4(0), "createTotalStakeRewardsSubmission function should exist");
}

/// @dev Validate storage layout changes
function _validateStorageLayout() internal view {
// The storage gap was reduced from 35 to 33 slots to accommodate the new mappings:
// - isUniqueStakeRewardsSubmissionHash (1 slot)
// - isTotalStakeRewardsSubmissionHash (1 slot)
// This validation ensures the contract is compiled with the expected storage layout

// We can't directly access the storage gap, but we can ensure the contract
// compiles and deploys successfully, which validates the storage layout is correct
RewardsCoordinator rewardsCoordinatorImpl = Env.impl.rewardsCoordinator();

// Verify we can access the existing public mappings
// This validates that storage layout hasn't been corrupted

// Check that we can call view functions that access storage
address testAvs = address(0x1234);
bytes32 testHash = keccak256("test");

// These calls should not revert, validating storage is accessible
bool isAVS = rewardsCoordinatorImpl.isAVSRewardsSubmissionHash(testAvs, testHash);
bool isOperatorDirectedAVS =
rewardsCoordinatorImpl.isOperatorDirectedAVSRewardsSubmissionHash(testAvs, testHash);
bool isOperatorDirectedOperatorSet =
rewardsCoordinatorImpl.isOperatorDirectedOperatorSetRewardsSubmissionHash(testAvs, testHash);
bool isUniqueStake = rewardsCoordinatorImpl.isUniqueStakeRewardsSubmissionHash(testAvs, testHash);
bool isTotalStake = rewardsCoordinatorImpl.isTotalStakeRewardsSubmissionHash(testAvs, testHash);

// All should be false for a random hash
assertFalse(isAVS, "Random hash should not be a rewards submission");
assertFalse(isOperatorDirectedAVS, "Random hash should not be operator directed");
assertFalse(isOperatorDirectedOperatorSet, "Random hash should not be operator set");
assertFalse(isUniqueStake, "Random hash should not be unique stake");
assertFalse(isTotalStake, "Random hash should not be total stake");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;

import {DeployRewardsCoordinatorImpl} from "./1-deployRewardsCoordinatorImpl.s.sol";
import "../Env.sol";
import "../TestUtils.sol";

import {MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol";
import {MultisigCall, Encode} from "zeus-templates/utils/Encode.sol";

import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";

/**
* @title QueueRewardsCoordinatorUpgrade
* @notice Queue the RewardsCoordinator upgrade transaction in the Timelock via the Operations Multisig.
* This queues the upgrade to add Rewards v2.2 support:
* - Unique stake rewards (linear to allocated unique stake)
* - Total stake rewards (linear to total stake)
* - Updated MAX_REWARDS_DURATION to 730 days (63072000 seconds)
*/
contract QueueRewardsCoordinatorUpgrade is MultisigBuilder, DeployRewardsCoordinatorImpl {
using Env for *;
using Encode for *;
using TestUtils for *;

function _runAsMultisig() internal virtual override prank(Env.opsMultisig()) {
if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) {
return;
}

bytes memory calldata_to_executor = _getCalldataToExecutor();

TimelockController timelock = Env.timelockController();
timelock.schedule({
target: Env.executorMultisig(),
value: 0,
data: calldata_to_executor,
predecessor: 0,
salt: 0,
delay: timelock.getMinDelay()
});
}

/// @dev Get the calldata to be sent from the timelock to the executor
function _getCalldataToExecutor() internal returns (bytes memory) {
/// forgefmt: disable-next-item
MultisigCall[] storage executorCalls = Encode.newMultisigCalls().append({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can use QueueUpgradeHelper. Example:

        MultisigCall[] storage executorCalls = Encode.newMultisigCalls();

        /// permissions
        executorCalls.upgradeRewardsCoordinator();

to: Env.proxyAdmin(),
data: Encode.proxyAdmin.upgrade({
proxy: address(Env.proxy.rewardsCoordinator()),
impl: address(Env.impl.rewardsCoordinator())
})
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also need a queue an update to the ProtocolRegistry via ship. Since the names and addresses and config are the same, fine to just set a semver and leave the first 3 params empty.


return Encode.gnosisSafe.execTransaction({
from: address(Env.timelockController()),
to: Env.multiSendCallOnly(),
op: Encode.Operation.DelegateCall,
data: Encode.multiSend(executorCalls)
});
}

function testScript() public virtual override {
if (!(Env.isSourceChain() && Env._strEq(Env.envVersion(), "1.9.0"))) {
return;
}

// 1 - Deploy. The new RewardsCoordinator implementation has been deployed
runAsEOA();

TimelockController timelock = Env.timelockController();
bytes memory calldata_to_executor = _getCalldataToExecutor();
bytes32 txHash = timelock.hashOperation({
target: Env.executorMultisig(),
value: 0,
data: calldata_to_executor,
predecessor: 0,
salt: 0
});

// Ensure transaction is not already queued
assertFalse(timelock.isOperationPending(txHash), "Transaction should not be queued yet");
assertFalse(timelock.isOperationReady(txHash), "Transaction should not be ready");
assertFalse(timelock.isOperationDone(txHash), "Transaction should not be complete");

// 2 - Queue the transaction
_runAsMultisig();
_unsafeResetHasPranked(); // reset hasPranked so we can use it again

// Verify transaction is queued
assertTrue(timelock.isOperationPending(txHash), "Transaction should be queued");
assertFalse(timelock.isOperationReady(txHash), "Transaction should NOT be ready immediately");
assertFalse(timelock.isOperationDone(txHash), "Transaction should NOT be complete");

// Validate that timelock delay is properly configured
uint256 minDelay = timelock.getMinDelay();
assertTrue(minDelay > 0, "Timelock delay should be greater than 0");

// Validate proxy state before upgrade
_validatePreUpgradeState();
}

/// @dev Validate the state before the upgrade
function _validatePreUpgradeState() internal view {
RewardsCoordinator rewardsCoordinator = Env.proxy.rewardsCoordinator();

// Validate current implementation is different from new implementation
address currentImpl = TestUtils._getProxyImpl(address(rewardsCoordinator));
address newImpl = address(Env.impl.rewardsCoordinator());
assertTrue(currentImpl != newImpl, "Current and new implementations should be different");

// Validate current MAX_REWARDS_DURATION is different from new value
// Note: We access this through the proxy, which still points to the old implementation
uint32 currentMaxRewardsDuration = rewardsCoordinator.MAX_REWARDS_DURATION();
assertTrue(currentMaxRewardsDuration != 63_072_000, "Current MAX_REWARDS_DURATION should not be 730 days yet");

// Validate that we're upgrading from the correct version
// We can't directly call them since they don't exist, but we can verify the upgrade is needed
// by checking that we're indeed on the right version
assertEq(Env.envVersion(), "1.9.0", "Should be on version 1.9.0 before upgrade");
}
}
Loading