diff --git a/script/pol/deployment/3_DeployPoL.s.sol b/script/pol/deployment/3_DeployPoL.s.sol index 1d313ba..e01f45d 100644 --- a/script/pol/deployment/3_DeployPoL.s.sol +++ b/script/pol/deployment/3_DeployPoL.s.sol @@ -74,22 +74,12 @@ contract DeployPoLScript is BaseScript, ConfigPOL, RBAC, AddressBook { distributor = polDeployer.distributor(); _checkDeploymentAddress("Distributor", address(distributor), _polAddresses.distributor); - // Give roles to the deployer - RBAC.AccountDescription memory deployer = RBAC.AccountDescription({ name: "deployer", addr: msg.sender }); - - // NOTE: the manager role on the distributor is not assigned to anyone, hence there is no need to revoke it. - RBAC.RoleDescription memory distributorManagerRole = RBAC.RoleDescription({ - contractName: "Distributor", - contractAddr: _polAddresses.distributor, - name: "MANAGER_ROLE", - role: distributor.MANAGER_ROLE() - }); - - _grantRole(distributorManagerRole, deployer); - rewardVaultFactory = polDeployer.rewardVaultFactory(); _checkDeploymentAddress("RewardVaultFactory", address(rewardVaultFactory), _polAddresses.rewardVaultFactory); + // Give roles to the deployer + RBAC.AccountDescription memory deployer = RBAC.AccountDescription({ name: "deployer", addr: msg.sender }); + RBAC.RoleDescription memory rewardVaultFactoryManagerRole = RBAC.RoleDescription({ contractName: "RewardVaultFactory", contractAddr: _polAddresses.rewardVaultFactory, diff --git a/script/pol/deployment/4_TransferPOLOwnership.s.sol b/script/pol/deployment/4_TransferPOLOwnership.s.sol index da004c6..dd85e95 100644 --- a/script/pol/deployment/4_TransferPOLOwnership.s.sol +++ b/script/pol/deployment/4_TransferPOLOwnership.s.sol @@ -12,14 +12,12 @@ contract TransferPOLOwnershipScript is RBAC, BaseScript, Storage, AddressBook { // Placeholder. Change before running the script. address internal constant NEW_OWNER = address(0); // TIMELOCK_ADDRESS; address internal constant VAULT_FACTORY_MANAGER = address(0); - address internal constant DISTRIBUTOR_MANAGER = address(0); address internal constant FEE_COLLECTOR_MANAGER = address(0); function run() public virtual broadcast { // Check if the new owner and managers are set require(NEW_OWNER != address(0), "NEW_OWNER must be set"); require(VAULT_FACTORY_MANAGER != address(0), "VAULT_FACTORY_MANAGER must be set"); - require(DISTRIBUTOR_MANAGER != address(0), "DISTRIBUTOR_MANAGER must be set"); require(FEE_COLLECTOR_MANAGER != address(0), "FEE_COLLECTOR_MANAGER must be set"); // create contracts instance from deployed addresses @@ -47,9 +45,6 @@ contract TransferPOLOwnershipScript is RBAC, BaseScript, Storage, AddressBook { RBAC.AccountDescription memory vaultFactoryManager = RBAC.AccountDescription({ name: "vaultFactoryManager", addr: VAULT_FACTORY_MANAGER }); - RBAC.AccountDescription memory distributorManager = - RBAC.AccountDescription({ name: "distributorManager", addr: DISTRIBUTOR_MANAGER }); - RBAC.AccountDescription memory deployer = RBAC.AccountDescription({ name: "deployer", addr: msg.sender }); // RewardVaultFactory @@ -101,15 +96,6 @@ contract TransferPOLOwnershipScript is RBAC, BaseScript, Storage, AddressBook { role: distributor.DEFAULT_ADMIN_ROLE() }); - // NOTE: the manager role on the distributor is not assigned to anyone, hence there is no need to revoke it. - RBAC.RoleDescription memory distributorManagerRole = RBAC.RoleDescription({ - contractName: "Distributor", - contractAddr: _polAddresses.distributor, - name: "MANAGER_ROLE", - role: distributor.MANAGER_ROLE() - }); - - _transferRole(distributorManagerRole, deployer, distributorManager); _transferRole(distributorAdminRole, deployer, governance); } diff --git a/src/pol/interfaces/IDistributor.sol b/src/pol/interfaces/IDistributor.sol index de71a1e..8704d63 100644 --- a/src/pol/interfaces/IDistributor.sol +++ b/src/pol/interfaces/IDistributor.sol @@ -15,27 +15,6 @@ interface IDistributor is IPOLErrors { address indexed oldDedicatedEmissionStreamManager, address indexed newDedicatedEmissionStreamManager ); - /** - * @notice Distribute the rewards to the reward allocation receivers. - * @dev Permissionless function to distribute rewards by providing the necessary Merkle proofs. Reverts if the - * proofs are invalid. - * @param nextTimestamp The timestamp of the next beacon block to distribute for. The EIP-4788 Beacon Roots - * contract is queried by this key, returning the parent beacon block root from the next timestamp. - * @param proposerIndex The proposer index of the beacon block. This should be the validator index corresponding - * to the pubkey in the validator registry in the beacon state. - * @param pubkey The validator pubkey of the proposer. - * @param proposerIndexProof The Merkle proof of the proposer index in the beacon block. - * @param pubkeyProof The Merkle proof of the validator pubkey of the proposer in the beacon block. - */ - function distributeFor( - uint64 nextTimestamp, - uint64 proposerIndex, - bytes calldata pubkey, - bytes32[] calldata proposerIndexProof, - bytes32[] calldata pubkeyProof - ) - external; - /// @notice Distribute the rewards to the reward allocation receivers according to BRIP-0004. /// @dev This will be called for block N at the top of block N+1. /// @dev Only system calls allowed i.e only the execution layer client can call this function. diff --git a/src/pol/rewards/Distributor.sol b/src/pol/rewards/Distributor.sol index df9c95b..8990391 100644 --- a/src/pol/rewards/Distributor.sol +++ b/src/pol/rewards/Distributor.sol @@ -33,20 +33,12 @@ contract Distributor is using Utils for bytes4; using Utils for address; - /// @notice The MANAGER role. - bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); - /// @dev Represents 100%. Chosen to be less granular. uint96 internal constant ONE_HUNDRED_PERCENT = 1e4; /// @dev Address controlled by the execution layer client and used to call `distributeFor` function. address private constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; - /// @dev Pectra11 hard fork timestamp. - /// @dev Bepolia: 1_754_496_000, 2025-08-06T16:00:00.000Z - /// @dev Mainnet: 1_756_915_200, 2025-09-03T16:00:00.000Z - uint64 private constant PECTRA11_HARD_FORK_TIMESTAMP = 1_756_915_200; - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -97,16 +89,14 @@ contract Distributor is function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { } - /// @dev This is necessary to call when the beacon chain hard forks (and specifically the underlying structure of - /// beacon block header is modified). - function setZeroValidatorPubkeyGIndex(uint64 _zeroValidatorPubkeyGIndex) public override onlyRole(MANAGER_ROLE) { - super.setZeroValidatorPubkeyGIndex(_zeroValidatorPubkeyGIndex); + /// @dev Locked — no longer used after Pectra11 hard fork. + function setZeroValidatorPubkeyGIndex(uint64) public pure override { + revert(); } - /// @dev This is necessary to call when the beacon chain hard forks (and specifically the underlying structure of - /// beacon block header is modified). - function setProposerIndexGIndex(uint64 _proposerIndexGIndex) public override onlyRole(MANAGER_ROLE) { - super.setProposerIndexGIndex(_proposerIndexGIndex); + /// @dev Locked — no longer used after Pectra11 hard fork. + function setProposerIndexGIndex(uint64) public pure override { + revert(); } /// @inheritdoc IDistributor @@ -123,34 +113,6 @@ contract Distributor is dedicatedEmissionStreamManager = IDedicatedEmissionStreamManager(_dedicatedEmissionStreamManager); } - /// @inheritdoc IDistributor - function distributeFor( - uint64 nextTimestamp, - uint64 proposerIndex, - bytes calldata pubkey, - bytes32[] calldata proposerIndexProof, - bytes32[] calldata pubkeyProof - ) - external - nonReentrant - { - // only allow permissionless distribution using proofs till hard fork timestamp - if (nextTimestamp >= PECTRA11_HARD_FORK_TIMESTAMP) { - OnlySystemCallAllowed.selector.revertWith(); - } - // Process the timestamp in the history buffer, reverting if already processed. - bytes32 beaconBlockRoot = _processTimestampInBuffer(nextTimestamp); - - // Verify the given proposer index is the true proposer index of the beacon block. - _verifyProposerIndexInBeaconBlock(beaconBlockRoot, proposerIndexProof, proposerIndex); - - // Verify the given pubkey is of a validator in the beacon block, at the given validator index. - _verifyValidatorPubkeyInBeaconBlock(beaconBlockRoot, pubkeyProof, pubkey, proposerIndex); - - // Distribute the rewards to the proposer validator. - _distributeFor(pubkey, nextTimestamp); - } - /// @inheritdoc IDistributor function distributeFor(bytes calldata pubkey) external onlySystemCall { _distributeFor(pubkey, uint64(block.timestamp)); diff --git a/test/mock/token/ReentrantERC20.sol b/test/mock/token/ReentrantERC20.sol index 2e2a925..f801f11 100644 --- a/test/mock/token/ReentrantERC20.sol +++ b/test/mock/token/ReentrantERC20.sol @@ -8,29 +8,12 @@ import { IDistributor } from "src/pol/interfaces/IDistributor.sol"; /// that try to reentrancy attack the distributor contract contract ReentrantERC20 is MockERC20 { address internal distributor; - uint64 internal timestamp; - uint64 internal proposerIndex; bytes internal pubkey; - bytes32[] internal proposerIndexProof; - bytes32[] internal pubkeyProof; bool internal makeExternalCall; - function setDistributeData( - address distributor_, - uint64 timestamp_, - uint64 proposerIndex_, - bytes calldata pubkey_, - bytes32[] calldata proposerIndexProof_, - bytes32[] calldata pubkeyProof_ - ) - external - { + function setDistributeData(address distributor_, bytes calldata pubkey_) external { distributor = distributor_; - timestamp = timestamp_; - proposerIndex = proposerIndex_; pubkey = pubkey_; - proposerIndexProof = proposerIndexProof_; - pubkeyProof = pubkeyProof_; } function setMakeExternalCall(bool flag) external { @@ -39,7 +22,7 @@ contract ReentrantERC20 is MockERC20 { function transfer(address recipient, uint256 amount) public override returns (bool) { if (makeExternalCall) { - IDistributor(distributor).distributeFor(timestamp, proposerIndex, pubkey, proposerIndexProof, pubkeyProof); + IDistributor(distributor).distributeFor(pubkey); } return super.transfer(recipient, amount); } diff --git a/test/pol/BGTIncentiveFeeCollector.t.sol b/test/pol/BGTIncentiveFeeCollector.t.sol index 3eb47a4..47bc208 100644 --- a/test/pol/BGTIncentiveFeeCollector.t.sol +++ b/test/pol/BGTIncentiveFeeCollector.t.sol @@ -44,6 +44,7 @@ contract BGTIncentiveFeeCollectorTest is DistributorTest { Salt public WBERA_STAKER_VAULT_SALT = Salt({ implementation: 0, proxy: 1 }); Salt public BGT_INCENTIVE_FEE_COLLECTOR_SALT = Salt({ implementation: 0, proxy: 1 }); + bytes32 internal managerRole; bytes32 internal pauserRole; address internal pauser = makeAddr("pauser"); @@ -77,6 +78,7 @@ contract BGTIncentiveFeeCollectorTest is DistributorTest { deal(address(wbera), address(this), 1 ether); pauserRole = incentiveFeeCollector.PAUSER_ROLE(); + managerRole = incentiveFeeCollector.MANAGER_ROLE(); vm.prank(governance); incentiveFeeCollector.grantRole(managerRole, manager); vm.prank(manager); diff --git a/test/pol/BeaconRootsHelper.t.sol b/test/pol/BeaconRootsHelper.t.sol index 4a598fe..25c794b 100644 --- a/test/pol/BeaconRootsHelper.t.sol +++ b/test/pol/BeaconRootsHelper.t.sol @@ -13,10 +13,10 @@ import { IRewardAllocation } from "src/pol/interfaces/IRewardAllocation.sol"; import "./POL.t.sol"; -/// @dev This test is for simulating the whole system against a mock BeraRoots contract. +/// @dev This test sets up the mock BeaconRoots contract and other infrastructure for POL distribution tests. +/// @dev The permissionless distributeFor(timestamp, proofs...) path was removed post-Pectra11. +/// Only the system-call distributeFor(pubkey) path remains. abstract contract BeaconRootsHelperTest is POLTest { - event AdvancedBlock(uint256 blockNum); - MockHoney internal honey; RewardVault internal vault; Mock4788BeaconRoots internal mockBeaconRoots; @@ -64,75 +64,4 @@ abstract contract BeaconRootsHelperTest is POLTest { beraChef.setDefaultRewardAllocation(IRewardAllocation.RewardAllocation(1, weights)); vm.stopPrank(); } - - function test_IsTimestampActionable_OutOfBuffer() public virtual { - // Should not be actionable as the timestamp is invalid. - mockBeaconRoots.setIsTimestampValid(false); - assertFalse(distributor.isTimestampActionable(DISTRIBUTE_FOR_TIMESTAMP)); - } - - function test_IsTimestampActionable_Processing() public virtual { - // Should be actionable as the timestamp is valid in buffer and not processed yet. - mockBeaconRoots.setIsTimestampValid(true); - assertTrue(distributor.isTimestampActionable(DISTRIBUTE_FOR_TIMESTAMP)); - - // Process the timestamp. - vm.expectEmit(true, false, false, true, address(distributor)); - emit BeaconRootsHelper.TimestampProcessed(DISTRIBUTE_FOR_TIMESTAMP); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - - // Should not be actionable as the timestamp is processed. - assertFalse(distributor.isTimestampActionable(DISTRIBUTE_FOR_TIMESTAMP)); - } - - /// @dev Should fail if attempted to process a timestamp out of buffer. - function test_ProcessTimestamp_OutOfBuffer() public virtual { - mockBeaconRoots.setIsTimestampValid(false); - vm.expectRevert(BeaconRoots.RootNotFound.selector); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - } - - function test_ProcessTimestamp_Processing() public virtual { - // Process the valid in buffer, unprocessed timestamp. - mockBeaconRoots.setIsTimestampValid(true); - vm.expectEmit(true, false, false, true, address(distributor)); - emit BeaconRootsHelper.TimestampProcessed(DISTRIBUTE_FOR_TIMESTAMP); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - - // Should fail if attempted to process the timestamp again. - vm.expectRevert(IPOLErrors.TimestampAlreadyProcessed.selector); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - - // Simulate moving forward, we try now to process a timestamp that should have replaced the same - // `timestamp_idx` in the `_processedTimestampsBuffer` array. - assertTrue(distributor.isTimestampActionable(DISTRIBUTE_FOR_TIMESTAMP + HISTORY_BUFFER_LENGTH)); - vm.expectEmit(true, false, false, true, address(distributor)); - emit BeaconRootsHelper.TimestampProcessed(DISTRIBUTE_FOR_TIMESTAMP + HISTORY_BUFFER_LENGTH); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP + HISTORY_BUFFER_LENGTH, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); - - // Should fail if attempted to process the timestamp again. - assertFalse(distributor.isTimestampActionable(DISTRIBUTE_FOR_TIMESTAMP + HISTORY_BUFFER_LENGTH)); - vm.expectRevert(IPOLErrors.TimestampAlreadyProcessed.selector); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP + HISTORY_BUFFER_LENGTH, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); - } } diff --git a/test/pol/DedicatedEmissionStreamManager.t.sol b/test/pol/DedicatedEmissionStreamManager.t.sol index cbca6c1..ee9d927 100644 --- a/test/pol/DedicatedEmissionStreamManager.t.sol +++ b/test/pol/DedicatedEmissionStreamManager.t.sol @@ -271,9 +271,8 @@ contract DedicatedEmissionStreamManagerTest is DistributorTest { IRewardAllocation.Weight[] memory rewardAllocation = dedicatedEmissionStreamManager.getRewardAllocation(); assertGt(rewardAllocation.length, 0); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); assertEq(bgt.allowance(address(distributor), address(vault)), TEST_BGT_PER_BLOCK); assertEq(bgt.allowance(address(distributor), graVault1), 0); @@ -291,10 +290,10 @@ contract DedicatedEmissionStreamManagerTest is DistributorTest { IRewardAllocation.Weight[] memory rewardAllocation = dedicatedEmissionStreamManager.getRewardAllocation(); assertGt(dedicatedEmissionStreamManager.emissionPerc(), 0); assertEq(rewardAllocation.length, 0); + vm.stopPrank(); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); assertEq(bgt.allowance(address(distributor), address(vault)), TEST_BGT_PER_BLOCK); assertEq(bgt.allowance(address(distributor), graVault1), 0); @@ -321,16 +320,15 @@ contract DedicatedEmissionStreamManagerTest is DistributorTest { uint256 expectedEmission = bgtToRewardAllocation * rewardAllocation[i].percentageNumerator / 10_000; emit IDedicatedEmissionStreamManager.NotifyEmission(rewardAllocation[i].receiver, expectedEmission); emit IDistributor.Distributed( - valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, rewardAllocation[i].receiver, expectedEmission + valData.pubkey, uint64(block.timestamp), rewardAllocation[i].receiver, expectedEmission ); } // vault receives 4.5 ether emit IDistributor.Distributed( - valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, address(vault), bgtToDefaultRewardAllocation - ); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof + valData.pubkey, uint64(block.timestamp), address(vault), bgtToDefaultRewardAllocation ); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); assertEq(bgt.allowance(address(distributor), address(vault)), bgtToDefaultRewardAllocation); for (uint256 i; i < rewardAllocation.length; ++i) { @@ -361,28 +359,25 @@ contract DedicatedEmissionStreamManagerTest is DistributorTest { uint256 expectedEmission = bgtToRewardAllocation * rewardAllocation[j].percentageNumerator / 10_000; emit IDedicatedEmissionStreamManager.NotifyEmission(rewardAllocation[j].receiver, expectedEmission); emit IDistributor.Distributed( - valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, rewardAllocation[j].receiver, expectedEmission + valData.pubkey, uint64(block.timestamp), rewardAllocation[j].receiver, expectedEmission ); } // vault receives 4.5 ether emit IDistributor.Distributed( - valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, address(vault), bgtToDefaultRewardAllocation + valData.pubkey, uint64(block.timestamp), address(vault), bgtToDefaultRewardAllocation ); } else { vm.expectEmit(true, true, true, true); - // vault receives 4.5 ether + // vault receives 5 ether emit IDistributor.Distributed( - valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP + i, address(vault), TEST_BGT_PER_BLOCK + valData.pubkey, uint64(block.timestamp), address(vault), TEST_BGT_PER_BLOCK ); } - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP + i, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); + // Advance timestamp so next call gets a different block.timestamp + vm.warp(block.timestamp + 1); } assertEq( @@ -422,13 +417,10 @@ contract DedicatedEmissionStreamManagerTest is DistributorTest { // -- graVault2 -> 0 // -- vault -> 5 ether for (uint8 i; i < 3; ++i) { - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP + i, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); + // Advance timestamp so next call gets a different block.timestamp + vm.warp(block.timestamp + 1); } assertEq( @@ -458,13 +450,8 @@ contract DedicatedEmissionStreamManagerTest is DistributorTest { // -- graVault1 -> 0.25 ether // -- graVault2 -> 0.25 ether // -- vault -> 4.5 ether - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP + 3, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); assertEq(bgt.allowance(address(distributor), address(vault)), vaultPreviousAllowance + 4.5 ether); assertEq(bgt.allowance(address(distributor), graVault1), graVault1PreviousDebt + 0.25 ether); @@ -483,9 +470,8 @@ contract DedicatedEmissionStreamManagerTest is DistributorTest { vm.expectCall( address(vault), abi.encodeWithSelector(IRewardVault.notifyRewardAmount.selector, valData.pubkey, 0), 0 ); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); assertEq(bgt.allowance(address(distributor), address(vault)), 0); assertEq(bgt.allowance(address(distributor), graVault1), TEST_BGT_PER_BLOCK / 2); diff --git a/test/pol/DistributeForGasUsage.t.sol b/test/pol/DistributeForGasUsage.t.sol index 5d10afb..888ff42 100644 --- a/test/pol/DistributeForGasUsage.t.sol +++ b/test/pol/DistributeForGasUsage.t.sol @@ -14,9 +14,10 @@ import { BeaconRootsHelperTest } from "./BeaconRootsHelper.t.sol"; import { MockERC20 } from "@mock/token/MockERC20.sol"; contract DistributeForGasUsageTest is BeaconRootsHelperTest { - address internal manager = makeAddr("manager"); bytes32 internal defaultAdminRole; - bytes32 internal managerRole; + + /// @dev The system address used by the execution layer client. + address internal constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; uint256 internal constant NUMBER_OF_WEIGHTS = 10; uint256 internal constant NUMBER_OF_INCENTIVE_TOKENS = 2; @@ -31,13 +32,9 @@ contract DistributeForGasUsageTest is BeaconRootsHelperTest { super.setUp(); defaultAdminRole = distributor.DEFAULT_ADMIN_ROLE(); - managerRole = distributor.MANAGER_ROLE(); - - vm.prank(governance); - distributor.grantRole(managerRole, manager); } - /// @dev Distribute using the default reward allocation if none is set. + /// @dev Distribute using the default reward allocation via system call. function test_Distribute(uint256 randomNumber) public { randomNumber; _helper_SetDefaultRewardAllocationWithIncentiveTokens(NUMBER_OF_WEIGHTS); @@ -50,61 +47,8 @@ contract DistributeForGasUsageTest is BeaconRootsHelperTest { bytes memory data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); vm.expectCall(address(bgt), data, 1); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - } - - /// @dev Test the `multicall` function for distributeFor. - function test_DistributeMulticall() public { - _helper_SetDefaultRewardAllocationWithIncentiveTokens(NUMBER_OF_WEIGHTS); - // expect 3 calls to process the rewards - bytes memory data = - abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, true)); - vm.expectCall(address(blockRewardController), data, 1); - data = abi.encodeCall( - IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP + 1, true) - ); - vm.expectCall(address(blockRewardController), data, 1); - data = abi.encodeCall( - IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP + 2, true) - ); - vm.expectCall(address(blockRewardController), data, 1); - // expect 3 calls to mint the BGT to the distributor - data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); - vm.expectCall(address(bgt), data, 3); - - // call distributeFor 3 times in a single multicall - bytes[] memory callData = new bytes[](3); - - // Function selector for distributeFor(uint64,uint64,bytes,bytes32[],bytes32[]) - bytes4 selector = bytes4(keccak256("distributeFor(uint64,uint64,bytes,bytes32[],bytes32[])")); - - callData[0] = abi.encodeWithSelector( - selector, - DISTRIBUTE_FOR_TIMESTAMP, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); - callData[1] = abi.encodeWithSelector( - selector, - DISTRIBUTE_FOR_TIMESTAMP + 1, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); - callData[2] = abi.encodeWithSelector( - selector, - DISTRIBUTE_FOR_TIMESTAMP + 2, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); - distributor.multicall(callData); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); } function _helper_CreateStakingToken() internal returns (address) { diff --git a/test/pol/Distributor.t.sol b/test/pol/Distributor.t.sol index 0e6a7f5..fe79eca 100644 --- a/test/pol/Distributor.t.sol +++ b/test/pol/Distributor.t.sol @@ -25,7 +25,9 @@ import { MockERC20 } from "@mock/token/MockERC20.sol"; contract DistributorTest is BeaconRootsHelperTest { address internal manager = makeAddr("manager"); bytes32 internal defaultAdminRole; - bytes32 internal managerRole; + + /// @dev The system address used by the execution layer client. + address internal constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; /// @dev A function invoked before each test case is run. function setUp() public virtual override { @@ -33,11 +35,6 @@ contract DistributorTest is BeaconRootsHelperTest { super.setUp(); defaultAdminRole = distributor.DEFAULT_ADMIN_ROLE(); - managerRole = distributor.MANAGER_ROLE(); - - vm.startPrank(governance); - distributor.grantRole(managerRole, manager); - vm.stopPrank(); } /// @dev Ensure that the contract is owned by the governance. @@ -50,22 +47,16 @@ contract DistributorTest is BeaconRootsHelperTest { vm.expectRevert(); distributor.revokeRole(defaultAdminRole, governance); - address rnd = makeAddr("address"); - vm.expectRevert(); - distributor.grantRole(managerRole, rnd); - address newImpl = address(new Distributor()); vm.expectRevert(); distributor.upgradeToAndCall(newImpl, bytes("")); } - /// @dev Should fail if not the manager - function test_FailIfNotManager() public virtual { - vm.expectRevert(); - distributor.setZeroValidatorPubkeyGIndex(0); - - vm.prank(manager); - distributor.setZeroValidatorPubkeyGIndex(0); + /// @dev Distribute rewards via the system-call path. Used by downstream tests as a setup helper. + function test_Distribute() public virtual { + helper_SetDefaultRewardAllocation(); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); } /// @dev Should upgrade to a new implementation @@ -91,226 +82,139 @@ contract DistributorTest is BeaconRootsHelperTest { ); } - /// @dev Test when the reward rate is zero. - function test_ZeroRewards() public { - vm.startPrank(governance); - blockRewardController.setRewardRate(0); - blockRewardController.setMinBoostedRewardRate(0); - vm.stopPrank(); - - // expect a call to process the rewards - bytes memory data = - abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, false)); - vm.expectCall(address(blockRewardController), data, 1); - // expect no call to mint BGT - data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); - vm.expectCall(address(bgt), data, 0); - - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - assertEq(bgt.allowance(address(distributor), address(vault)), 0); - } - - /// @dev Test that in genesis no bgts are left unallocated in the distributor. - function test_DistributeDuringGenesisNoBgtWaste() public { - vm.startPrank(governance); - blockRewardController.setRewardRate(1e18); - blockRewardController.setMinBoostedRewardRate(1e18); - vm.stopPrank(); - - BlockRewardController brc = BlockRewardController(address(distributor.blockRewardController())); - address valOperator = IBeaconDeposit(brc.beaconDepositContract()).getOperator(valData.pubkey); - - uint256 distributorBgtBefore = bgt.balanceOf(address(distributor)); - uint256 valOperatorBgtBefore = bgt.balanceOf(valOperator); - - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - - assertEq(bgt.allowance(address(distributor), address(vault)), 0); - // distributor should have same bgts as before - assertEq(bgt.balanceOf(address(distributor)), distributorBgtBefore); - // validator operator should receive base rate as well in genesis - assertEq(bgt.balanceOf(valOperator), valOperatorBgtBefore + blockRewardController.baseRate()); - } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SYSTEM CALL TESTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - /// @dev Distribute using the default reward allocation if none is set. - function test_Distribute() public { + /// @dev Test that the new distributeFor function with only pubkey parameter works when called by system address. + function test_DistributeForSystemCall() public { helper_SetDefaultRewardAllocation(); + // expect a call to process the rewards bytes memory data = - abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, true)); + abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, uint64(block.timestamp), true)); vm.expectCall(address(blockRewardController), data, 1); + // expect a call to mint the BGT to the distributor data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); vm.expectCall(address(bgt), data, 1); - // expect single call to check if ready then activate the queued reward allocation - // although it wont activate the queued reward allocation since it nothing is queued. + + // expect a call to activate the queued reward allocation data = abi.encodeCall(IBeraChef.activateReadyQueuedRewardAllocation, (valData.pubkey)); vm.expectCall(address(beraChef), data, 1); vm.expectEmit(true, true, true, true); - emit IDistributor.Distributed(valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, address(vault), TEST_BGT_PER_BLOCK); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); + emit IDistributor.Distributed(valData.pubkey, uint64(block.timestamp), address(vault), TEST_BGT_PER_BLOCK); + + // Call as system address + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); - // check that the default reward allocation was used - // `getActiveRewardAllocation` should return default reward allocation as there was no active reward allocation - // queued by the validator - // the default reward allocation was set with `1` as startBlock in RootHelperTest. - assertEq(beraChef.getActiveRewardAllocation(valData.pubkey).startBlock, 1); assertEq(bgt.allowance(address(distributor), address(vault)), TEST_BGT_PER_BLOCK); } - /// @dev Test the `multicall` function for distributeFor. - function test_DistributeMulticall() public { + /// @dev Test that the new distributeFor function fails when called by non-system address. + function test_DistributeForSystemCall_FailIfNotSystemAddress() public { helper_SetDefaultRewardAllocation(); - // expect 3 calls to process the rewards - bytes memory data = - abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, true)); - vm.expectCall(address(blockRewardController), data, 1); - data = abi.encodeCall( - IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP + 1, true) - ); - vm.expectCall(address(blockRewardController), data, 1); - data = abi.encodeCall( - IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP + 2, true) - ); - vm.expectCall(address(blockRewardController), data, 1); - // expect 3 calls to mint the BGT to the distributor - data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); - vm.expectCall(address(bgt), data, 3); - // expect 3 calls to check if ready then activate the queued reward allocation - // although it wont activate the queued reward allocation since it nothing is queued. - data = abi.encodeCall(IBeraChef.activateReadyQueuedRewardAllocation, (valData.pubkey)); - vm.expectCall(address(beraChef), data, 3); - - // call distributeFor 3 times in a single multicall - bytes[] memory callData = new bytes[](3); - bytes4 selector = bytes4(keccak256("distributeFor(uint64,uint64,bytes,bytes32[],bytes32[])")); - callData[0] = abi.encodeWithSelector( - selector, - DISTRIBUTE_FOR_TIMESTAMP, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); - callData[1] = abi.encodeWithSelector( - selector, - DISTRIBUTE_FOR_TIMESTAMP + 1, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); - callData[2] = abi.encodeWithSelector( - selector, - DISTRIBUTE_FOR_TIMESTAMP + 2, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); - distributor.multicall(callData); + // Try to call as a regular address + vm.expectRevert(IPOLErrors.NotSystemAddress.selector); + distributor.distributeFor(valData.pubkey); - // check that all BGT were distributed - assertEq(beraChef.getActiveRewardAllocation(valData.pubkey).startBlock, 1); - assertEq(bgt.allowance(address(distributor), address(vault)), 3 * TEST_BGT_PER_BLOCK); + // Try to call as governance + vm.prank(governance); + vm.expectRevert(IPOLErrors.NotSystemAddress.selector); + distributor.distributeFor(valData.pubkey); } - /// @dev Activate the queued reward allocation if it is ready and distribute the rewards. - function test_DistributeAndActivateQueuedRewardAllocation() public { + /// @dev Test that the new distributeFor function works correctly with + /// same block queued reward allocation activation. + function test_DistributeForSystemCall_WithSameBlockQueuedRewardAllocationActivation() public { helper_SetDefaultRewardAllocation(); - IRewardAllocation.Weight[] memory weights = new IRewardAllocation.Weight[](1); - weights[0] = IRewardAllocation.Weight(address(vault), 10_000); - uint64 startBlock = uint64(block.number + 2); - vm.prank(operator); - beraChef.queueNewRewardAllocation(valData.pubkey, startBlock, weights); + address stakingToken = address(new MockERC20()); + address vault2 = factory.createRewardVault(stakingToken); + vm.prank(governance); + beraChef.setVaultWhitelistedStatus(vault2, true, ""); - // Distribute the rewards. - vm.roll(startBlock); - vm.prank(manager); + IRewardAllocation.Weight[] memory weights = new IRewardAllocation.Weight[](2); + weights[0] = IRewardAllocation.Weight(address(vault), 5000); + weights[1] = IRewardAllocation.Weight(vault2, 5000); + + vm.prank(operator); + beraChef.queueNewRewardAllocation(valData.pubkey, uint64(block.number), weights); // expect a call to process the rewards bytes memory data = - abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, true)); + abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, uint64(block.timestamp), true)); vm.expectCall(address(blockRewardController), data, 1); + // expect a call to mint the BGT to the distributor data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); vm.expectCall(address(bgt), data, 1); + // expect a call to activate the queued reward allocation data = abi.encodeCall(IBeraChef.activateReadyQueuedRewardAllocation, (valData.pubkey)); vm.expectCall(address(beraChef), data, 1); vm.expectEmit(true, true, true, true); - emit IDistributor.Distributed(valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, address(vault), TEST_BGT_PER_BLOCK); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); + emit IDistributor.Distributed(valData.pubkey, uint64(block.timestamp), address(vault), TEST_BGT_PER_BLOCK / 2); + emit IDistributor.Distributed(valData.pubkey, uint64(block.timestamp), vault2, TEST_BGT_PER_BLOCK / 2); + + // Call as system address + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); // check that the queued reward allocation was activated - assertEq(beraChef.getActiveRewardAllocation(valData.pubkey).startBlock, startBlock); - assertEq(bgt.allowance(address(distributor), address(vault)), TEST_BGT_PER_BLOCK); + assertEq(beraChef.getActiveRewardAllocation(valData.pubkey).startBlock, uint64(block.number)); + assertEq(bgt.allowance(address(distributor), address(vault)), TEST_BGT_PER_BLOCK / 2); + assertEq(bgt.allowance(address(distributor), vault2), TEST_BGT_PER_BLOCK / 2); } - function test_DistributeForNonReentrant() public { - ReentrantERC20 reentrantERC20 = new ReentrantERC20(); - - reentrantERC20.setMakeExternalCall(true); - reentrantERC20.setDistributeData( - address(distributor), - DISTRIBUTE_FOR_TIMESTAMP, - valData.index, - valData.pubkey, - valData.proposerIndexProof, - valData.pubkeyProof - ); + /// @dev Test when the reward rate is zero via system call. + function test_SystemCall_ZeroRewards() public { + vm.startPrank(governance); + blockRewardController.setRewardRate(0); + blockRewardController.setMinBoostedRewardRate(0); + vm.stopPrank(); - _helper_addIncentives(address(reentrantERC20), 100 ether, 100 * 1e18); + // expect a call to process the rewards + bytes memory data = + abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, uint64(block.timestamp), false)); + vm.expectCall(address(blockRewardController), data, 1); + // expect no call to mint BGT + data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); + vm.expectCall(address(bgt), data, 0); - // Inside this test there're already some assert which check that core functions like - // 'processRewards' and 'mint' are called only once - test_DistributeAndActivateQueuedRewardAllocation(); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); + assertEq(bgt.allowance(address(distributor), address(vault)), 0); } - function _helper_addIncentives(address token, uint256 amount, uint256 _incentiveRate) internal { - _helper_WhitelistIncentiveToken(token); - // mint dai and approve vault to use the tokens on behalf of the user - ReentrantERC20(token).mint(address(this), type(uint256).max); - ReentrantERC20(token).approve(address(vault), type(uint256).max); - - vault.addIncentive(token, amount, _incentiveRate); - - // check the dai incentive data - (uint256 minIncentiveRate, uint256 incentiveRate, uint256 amountRemaining,) = vault.incentives(token); - assertEq(minIncentiveRate, 100 * 1e18); - assertEq(incentiveRate, _incentiveRate); - assertEq(amountRemaining, amount); - } + /// @dev Test that in genesis no bgts are left unallocated in the distributor via system call. + function test_SystemCall_DistributeDuringGenesisNoBgtWaste() public { + vm.startPrank(governance); + blockRewardController.setRewardRate(1e18); + blockRewardController.setMinBoostedRewardRate(1e18); + vm.stopPrank(); - function _helper_WhitelistIncentiveToken(address token) public { - uint256 count = vault.getWhitelistedTokensCount(); + BlockRewardController brc = BlockRewardController(address(distributor.blockRewardController())); + address valOperator = IBeaconDeposit(brc.beaconDepositContract()).getOperator(valData.pubkey); - // Whitelist the token - vm.prank(governance); - vault.whitelistIncentiveToken(token, 100 * 1e18, address(this)); + uint256 distributorBgtBefore = bgt.balanceOf(address(distributor)); + uint256 valOperatorBgtBefore = bgt.balanceOf(valOperator); - // Verify the token was whitelisted - (uint256 minIncentiveRate, uint256 incentiveRate,,) = vault.incentives(token); - assertEq(minIncentiveRate, 100 * 1e18); - assertEq(incentiveRate, 100 * 1e18); + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); - // Verify the token was added to the list of whitelisted tokens - assertEq(vault.getWhitelistedTokensCount(), count + 1); - assertEq(vault.whitelistedTokens(count), token); + assertEq(bgt.allowance(address(distributor), address(vault)), 0); + // distributor should have same bgts as before + assertEq(bgt.balanceOf(address(distributor)), distributorBgtBefore); + // validator operator should receive base rate as well in genesis + assertEq(bgt.balanceOf(valOperator), valOperatorBgtBefore + blockRewardController.baseRate()); } - function testFuzz_DistributeDoesNotLeaveDust(uint256 weight) public { + /// @dev Test dust-free distribution via system call with two vaults. + function testFuzz_SystemCall_DistributeDoesNotLeaveDust(uint256 weight) public { helper_SetDefaultRewardAllocation(); uint256 MAX_WEIGHT = 10_000; // 100% weight = _bound(weight, 1, MAX_WEIGHT - 1); @@ -329,14 +233,14 @@ contract DistributorTest is BeaconRootsHelperTest { // Distribute the rewards. vm.roll(startBlock); - vm.prank(manager); // BGT balance before distribute uint256 vaultAllowanceBefore = bgt.allowance(address(distributor), address(vault)); uint256 vault2AllowanceBefore = bgt.allowance(address(distributor), vault2); - distributor.distributeFor( - DISTRIBUTE_FOR_TIMESTAMP, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); + + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); + uint256 vaultRewards; uint256 vault2Rewards; { @@ -348,12 +252,11 @@ contract DistributorTest is BeaconRootsHelperTest { // Cal this to know the exact total amount of rewards distributed vm.prank(address(distributor)); - uint256 rewardDistributed = - blockRewardController.processRewards(valData.pubkey, DISTRIBUTE_FOR_TIMESTAMP, true); + uint256 rewardDistributed = blockRewardController.processRewards(valData.pubkey, uint64(block.timestamp), true); assertEq(vaultRewards + vault2Rewards, rewardDistributed); } - function testFuzz_DistributeDoesNotLeaveDust( + function testFuzz_SystemCall_DistributeDoesNotLeaveDust( uint256 weight, uint256 rewardRate, uint256 minReward, @@ -376,116 +279,6 @@ contract DistributorTest is BeaconRootsHelperTest { vm.deal(address(bgt), address(bgt).balance + rewardRate * multiplier / 1e18); // add max bgt minted in a block - testFuzz_DistributeDoesNotLeaveDust(weight); - } - - /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ - /* SYSTEM CALL TESTS */ - /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - - /// @dev Test that the new distributeFor function with only pubkey parameter works when called by system address. - function test_DistributeForSystemCall() public { - helper_SetDefaultRewardAllocation(); - - // expect a call to process the rewards - bytes memory data = - abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, uint64(block.timestamp), true)); - vm.expectCall(address(blockRewardController), data, 1); - - // expect a call to mint the BGT to the distributor - data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); - vm.expectCall(address(bgt), data, 1); - - // expect a call to activate the queued reward allocation - data = abi.encodeCall(IBeraChef.activateReadyQueuedRewardAllocation, (valData.pubkey)); - vm.expectCall(address(beraChef), data, 1); - - vm.expectEmit(true, true, true, true); - emit IDistributor.Distributed(valData.pubkey, uint64(block.timestamp), address(vault), TEST_BGT_PER_BLOCK); - - // Call as system address - vm.prank(0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE); - distributor.distributeFor(valData.pubkey); - - assertEq(bgt.allowance(address(distributor), address(vault)), TEST_BGT_PER_BLOCK); - } - - /// @dev Test that the new distributeFor function fails when called by non-system address. - function test_DistributeForSystemCall_FailIfNotSystemAddress() public { - helper_SetDefaultRewardAllocation(); - // Try to call as a regular address - vm.expectRevert(IPOLErrors.NotSystemAddress.selector); - distributor.distributeFor(valData.pubkey); - - // Try to call as governance - vm.prank(governance); - vm.expectRevert(IPOLErrors.NotSystemAddress.selector); - distributor.distributeFor(valData.pubkey); - } - - /// @dev Test that the new distributeFor function works correctly with - /// same block queued reward allocation activation. - function test_DistributeForSystemCall_WithSameBlockQueuedRewardAllocationActivation() public { - helper_SetDefaultRewardAllocation(); - - address stakingToken = address(new MockERC20()); - address vault2 = factory.createRewardVault(stakingToken); - vm.prank(governance); - beraChef.setVaultWhitelistedStatus(vault2, true, ""); - - IRewardAllocation.Weight[] memory weights = new IRewardAllocation.Weight[](2); - weights[0] = IRewardAllocation.Weight(address(vault), 5000); - weights[1] = IRewardAllocation.Weight(vault2, 5000); - - vm.prank(operator); - beraChef.queueNewRewardAllocation(valData.pubkey, uint64(block.number), weights); - - // expect a call to process the rewards - bytes memory data = - abi.encodeCall(IBlockRewardController.processRewards, (valData.pubkey, uint64(block.timestamp), true)); - vm.expectCall(address(blockRewardController), data, 1); - - // expect a call to mint the BGT to the distributor - data = abi.encodeCall(IBGT.mint, (address(distributor), TEST_BGT_PER_BLOCK)); - vm.expectCall(address(bgt), data, 1); - - // expect a call to activate the queued reward allocation - data = abi.encodeCall(IBeraChef.activateReadyQueuedRewardAllocation, (valData.pubkey)); - vm.expectCall(address(beraChef), data, 1); - - vm.expectEmit(true, true, true, true); - emit IDistributor.Distributed(valData.pubkey, uint64(block.timestamp), address(vault), TEST_BGT_PER_BLOCK / 2); - emit IDistributor.Distributed(valData.pubkey, uint64(block.timestamp), vault2, TEST_BGT_PER_BLOCK / 2); - - // Call as system address - vm.prank(0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE); - distributor.distributeFor(valData.pubkey); - - // check that the queued reward allocation was activated - assertEq(beraChef.getActiveRewardAllocation(valData.pubkey).startBlock, uint64(block.number)); - assertEq(bgt.allowance(address(distributor), address(vault)), TEST_BGT_PER_BLOCK / 2); - assertEq(bgt.allowance(address(distributor), vault2), TEST_BGT_PER_BLOCK / 2); - } - - /// @dev Test that permissionless distributeFor function fails after Pectra11 hard fork. - function test_PermissionlessDistributeFor_AfterHardFork() public { - // Set timestamp to after Pectra11 hard fork - uint64 postHardForkTimestamp = 1_756_915_201; // PECTRA11_HARD_FORK_TIMESTAMP + 1 - testFuzz_PermissionlessDistributeFor_AfterHardFork(postHardForkTimestamp); - } - - /// @dev Test that permissionless distributeFor function fails starting from Pectra11 hard fork. - function testFuzz_PermissionlessDistributeFor_AfterHardFork(uint64 timestamp) public { - helper_SetDefaultRewardAllocation(); - // start from Pectra11 hard fork timestamp - timestamp = uint64(bound(timestamp, 1_756_915_201, type(uint64).max)); - - // expect the function to revert with OnlySystemCallAllowed error - vm.expectRevert(IPOLErrors.OnlySystemCallAllowed.selector); - distributor.distributeFor( - timestamp, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - // Verify no BGT was distributed - assertEq(bgt.allowance(address(distributor), address(vault)), 0); + testFuzz_SystemCall_DistributeDoesNotLeaveDust(weight); } } diff --git a/test/pol/e2e/POLE2EFuzz.t.sol b/test/pol/e2e/POLE2EFuzz.t.sol index 0f5b830..773e268 100644 --- a/test/pol/e2e/POLE2EFuzz.t.sol +++ b/test/pol/e2e/POLE2EFuzz.t.sol @@ -40,10 +40,8 @@ contract POLE2EFuzz is POLGasSimulationSimple { deal(address(bgt), address(bgt).balance + TEST_BGT_PER_BLOCK); // simulate native token distribution - distributor.distributeFor( - lastProcessedTimestamp, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - lastProcessedTimestamp++; + vm.prank(0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE); + distributor.distributeFor(valData.pubkey); // Verification verifyWeights(numVaults, weights); diff --git a/test/pol/e2e/POLGasSim.t.sol b/test/pol/e2e/POLGasSim.t.sol index fab0c1f..7a8d015 100644 --- a/test/pol/e2e/POLGasSim.t.sol +++ b/test/pol/e2e/POLGasSim.t.sol @@ -23,7 +23,9 @@ contract POLGasSimulationSimple is GovernanceBaseTest { bytes internal signature; // Signature corresponding to the proof uint256 internal signerPrivateKey = 0xabc123; // Private key for simulated signer, for test purposes only address internal signer; // Address of the signer - uint64 internal lastProcessedTimestamp = DISTRIBUTE_FOR_TIMESTAMP; // Last processed timestamp + + /// @dev The system address used by the execution layer client. + address internal constant SYSTEM_ADDRESS = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; /// @dev Sets up the environment for each test case. This includes deploying and initializing /// governance-related contracts and configuring the initial state required for subsequent tests. @@ -147,10 +149,8 @@ contract POLGasSimulationSimple is GovernanceBaseTest { require(ECDSA.recover(_proof, _signature) == signer, "POLGasSimulationSimple: Invalid signature"); deal(address(bgt), address(bgt).balance + 100 ether); // simulate native token distribution - distributor.distributeFor( - lastProcessedTimestamp, valData.index, valData.pubkey, valData.proposerIndexProof, valData.pubkeyProof - ); - lastProcessedTimestamp++; + vm.prank(SYSTEM_ADDRESS); + distributor.distributeFor(valData.pubkey); return (validatorAddress, extractedBlockNumber); }