From 3a3c0c1600836424db674fbf14d772f2e1817545 Mon Sep 17 00:00:00 2001 From: fredwes <6827305+fredwes@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:59:19 -0400 Subject: [PATCH 1/5] fix: emission manager claim max --- Makefile | 5 +- script/SeamEmissionManagerV2Deploy.s.sol | 29 ++++ src/SeamEmissionManager.sol | 2 +- src/SeamEmissionManagerV2.sol | 40 +++++ src/library/Constants.sol | 2 + test/fork/SeamEmissionManagerV2.t.sol | 49 ++++++ test/unit/SeamEmissionManagerV2.t.sol | 185 +++++++++++++++++++++++ 7 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 script/SeamEmissionManagerV2Deploy.s.sol create mode 100644 src/SeamEmissionManagerV2.sol create mode 100644 test/fork/SeamEmissionManagerV2.t.sol create mode 100644 test/unit/SeamEmissionManagerV2.t.sol diff --git a/Makefile b/Makefile index 0e26a9e..32fdda2 100644 --- a/Makefile +++ b/Makefile @@ -57,4 +57,7 @@ deploy-governorv2-implementation-base-mainnet :; forge script script/SeamGoverno deploy-governorv2-implementation-tenderly :; forge script script/SeamGovernorUpgradeV2.s.sol:SeamGovernorUpgradeV2 --force --rpc-url tenderly --slow --broadcast -vvvv --verify --verifier-url ${TENDERLY_FORK_VERIFIER_URL} --etherscan-api-key ${TENDERLY_ACCESS_KEY} deploy-staked-token-implementation-base-mainnet :; forge script script/StakedTokenImplementation.s.sol:StakedTokenImplementation --force --rpc-url base --slow --broadcast --verify --delay 5 --verifier-url ${VERIFIER_URL} -vvvv -deploy-staked-token-implementation-tenderly :; forge script script/StakedTokenImplementation.s.sol:StakedTokenImplementation --force --rpc-url tenderly --slow --broadcast -vvvv --verify --verifier-url ${TENDERLY_FORK_VERIFIER_URL} --etherscan-api-key ${TENDERLY_ACCESS_KEY} \ No newline at end of file +deploy-staked-token-implementation-tenderly :; forge script script/StakedTokenImplementation.s.sol:StakedTokenImplementation --force --rpc-url tenderly --slow --broadcast -vvvv --verify --verifier-url ${TENDERLY_FORK_VERIFIER_URL} --etherscan-api-key ${TENDERLY_ACCESS_KEY} + +deploy-emission-manager-v2-base-mainnet :; forge script script/SeamEmissionManagerV2Deploy.s.sol:SeamEmissionManagerV2Deploy --force --rpc-url base --slow --broadcast --verify --delay 5 --verifier-url ${VERIFIER_URL} -vvvv +deploy-emission-manager-v2-tenderly :; forge script script/SeamEmissionManagerV2Deploy.s.sol:SeamEmissionManagerV2Deploy --force --rpc-url tenderly --slow --broadcast -vvvv --verify --verifier-url ${TENDERLY_FORK_VERIFIER_URL} --etherscan-api-key ${TENDERLY_ACCESS_KEY} \ No newline at end of file diff --git a/script/SeamEmissionManagerV2Deploy.s.sol b/script/SeamEmissionManagerV2Deploy.s.sol new file mode 100644 index 0000000..bef3b5b --- /dev/null +++ b/script/SeamEmissionManagerV2Deploy.s.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {SeamEmissionManagerV2} from "../src/SeamEmissionManagerV2.sol"; +import {Constants} from "../src/library/Constants.sol"; + +contract SeamEmissionManagerV2Deploy is Script { + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployerAddress = vm.addr(deployerPrivateKey); + + console.log("Deployer address: ", deployerAddress); + console.log("Deployer balance: ", deployerAddress.balance); + console.log("BlockNumber: ", block.number); + console.log("ChainId: ", block.chainid); + + console.log("Deploying..."); + + vm.startBroadcast(deployerPrivateKey); + + SeamEmissionManagerV2 implementation = new SeamEmissionManagerV2(); + + console.log("Deployed SeamEmissionManagerV2 implementation: ", address(implementation)); + + vm.stopBroadcast(); + } +} diff --git a/src/SeamEmissionManager.sol b/src/SeamEmissionManager.sol index a229ec9..3028da1 100644 --- a/src/SeamEmissionManager.sol +++ b/src/SeamEmissionManager.sol @@ -83,7 +83,7 @@ contract SeamEmissionManager is ISeamEmissionManager, Initializable, AccessContr } /// @inheritdoc ISeamEmissionManager - function claim(address receiver) external onlyRole(CLAIMER_ROLE) { + function claim(address receiver) external virtual onlyRole(CLAIMER_ROLE) { Storage.Layout storage $ = Storage.layout(); uint64 emissionStartTimestamp = $.emissionStartTimestamp; diff --git a/src/SeamEmissionManagerV2.sol b/src/SeamEmissionManagerV2.sol new file mode 100644 index 0000000..0e9936b --- /dev/null +++ b/src/SeamEmissionManagerV2.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {SeamEmissionManager} from "./SeamEmissionManager.sol"; +import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; +import {SeamEmissionManagerStorage as Storage} from "./storage/SeamEmissionManagerStorage.sol"; + +/// @title SeamEmissionManagerV2 +/// @author Seamless Protocol +/// @notice This contract is responsible for managing SEAM token emission. +contract SeamEmissionManagerV2 is SeamEmissionManager { + /// @inheritdoc SeamEmissionManager + function claim(address receiver) external override onlyRole(CLAIMER_ROLE) { + Storage.Layout storage $ = Storage.layout(); + + uint64 emissionStartTimestamp = $.emissionStartTimestamp; + if (emissionStartTimestamp > block.timestamp) { + revert EmissionsNotStarted(emissionStartTimestamp); + } + + uint256 emissionPerSecond = $.emissionPerSecond; + uint64 lastClaimedTimestamp = $.lastClaimedTimestamp; + uint64 currentTimestamp = uint64(block.timestamp); + uint256 emissionAmount = (currentTimestamp - lastClaimedTimestamp) * emissionPerSecond; + + // Check contract's SEAM balance and adjust emission amount if needed + // When emission amount exceeds balance, emission rate will not result + // in any more emissions until balance is increased (emission rate is not + // applied retroactively for time elapsed while no balance was available) + uint256 seamBalance = $.seam.balanceOf(address(this)); + if (emissionAmount > seamBalance) { + emissionAmount = seamBalance; + } + + SafeERC20.safeTransfer($.seam, receiver, emissionAmount); + $.lastClaimedTimestamp = currentTimestamp; + + emit Claim(receiver, emissionAmount); + } +} diff --git a/src/library/Constants.sol b/src/library/Constants.sol index 3579ebf..3f1f3df 100644 --- a/src/library/Constants.sol +++ b/src/library/Constants.sol @@ -40,6 +40,8 @@ library Constants { address public constant GOVERNOR_LONG_ADDRESS = 0x04faA2826DbB38a7A4E9a5E3dB26b9E389E761B6; uint256 public constant SEAM_EMISSION_PER_SECOND = 0.000000001 ether; + address public constant SEAM_EMISSION_MANAGER1_ADDRESS = 0x57460DC21bf1574b8e6E00D372b8Ca5Ec41b3955; + address public constant SEAM_EMISSION_MANAGER2_ADDRESS = 0x785c979EE8709060b3f71aEf4f2C09229DB90778; address public constant INCENTIVES_CONTROLLER_ADDRESS = 0x91Ac2FfF8CBeF5859eAA6DdA661feBd533cD3780; diff --git a/test/fork/SeamEmissionManagerV2.t.sol b/test/fork/SeamEmissionManagerV2.t.sol new file mode 100644 index 0000000..4a12124 --- /dev/null +++ b/test/fork/SeamEmissionManagerV2.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {IERC20Errors} from "openzeppelin-contracts/interfaces/draft-IERC6093.sol"; +import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import {SeamEmissionManager} from "../../src/SeamEmissionManager.sol"; +import {SeamEmissionManagerV2} from "../../src/SeamEmissionManagerV2.sol"; +import {Constants} from "../../src/library/Constants.sol"; + +contract SeamEmissionManagerV2ForkTest is Test { + IERC20 immutable SEAM = IERC20(Constants.SEAM_ADDRESS); + SeamEmissionManager emissionManager1 = SeamEmissionManager(Constants.SEAM_EMISSION_MANAGER1_ADDRESS); + SeamEmissionManager emissionManager2 = SeamEmissionManager(Constants.SEAM_EMISSION_MANAGER2_ADDRESS); + + function setUp() public { + vm.createSelectFork(vm.envString("FORK_URL"), 31645245); + } + + function testUpgrade() public { + address newImplementation = address(new SeamEmissionManagerV2()); + + vm.startPrank(Constants.SHORT_TIMELOCK_ADDRESS); + + // Check that emission manager 1 reverts with insufficient balance. Since the vesting period is over + vm.expectPartialRevert(IERC20Errors.ERC20InsufficientBalance.selector); + emissionManager1.claim(address(this)); + + // Check that emission manager 2 claims successfully since the vesting period is not over + emissionManager2.claim(address(this)); + + vm.stopPrank(); + + vm.startPrank(Constants.LONG_TIMELOCK_ADDRESS); + emissionManager1.upgradeToAndCall(address(newImplementation), ""); + emissionManager2.upgradeToAndCall(address(newImplementation), ""); + vm.stopPrank(); + + vm.startPrank(Constants.SHORT_TIMELOCK_ADDRESS); + emissionManager1.claim(address(this)); + emissionManager2.claim(address(this)); + + // Check that emission manager 1 claims successfully the full balance since the vesting period is over + assertEq(SEAM.balanceOf(address(emissionManager1)), 0); + + vm.stopPrank(); + } +} diff --git a/test/unit/SeamEmissionManagerV2.t.sol b/test/unit/SeamEmissionManagerV2.t.sol new file mode 100644 index 0000000..1857aef --- /dev/null +++ b/test/unit/SeamEmissionManagerV2.t.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {ERC20Mock} from "openzeppelin-contracts/mocks/token/ERC20Mock.sol"; +import {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {SeamEmissionManagerV2} from "src/SeamEmissionManagerV2.sol"; +import {SeamEmissionManager} from "src/SeamEmissionManager.sol"; +import {ISeamEmissionManager} from "src/interfaces/ISeamEmissionManager.sol"; +import {IAccessControl} from "openzeppelin-contracts/access/IAccessControl.sol"; +import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; + +contract SeamEmissionManagerV2Test is Test { + address immutable seam = address(new ERC20Mock()); + uint256 immutable emissionPerSecond = 1 ether; + + SeamEmissionManagerV2 emissionManager; + + function setUp() public { + SeamEmissionManagerV2 implementation = new SeamEmissionManagerV2(); + ERC1967Proxy proxy = new ERC1967Proxy( + address(implementation), + abi.encodeWithSelector( + SeamEmissionManager.initialize.selector, + seam, + emissionPerSecond, + address(this), + address(this), + block.timestamp + ) + ); + emissionManager = SeamEmissionManagerV2(address(proxy)); + } + + function test_SetUp() public { + assertEq(emissionManager.getSeam(), seam); + assertEq(emissionManager.getEmissionPerSecond(), emissionPerSecond); + assertEq(emissionManager.getEmissionStartTimestamp(), block.timestamp); + assertEq(emissionManager.getLastClaimedTimestamp(), block.timestamp); + assertTrue(emissionManager.hasRole(emissionManager.DEFAULT_ADMIN_ROLE(), address(this))); + assertTrue(emissionManager.hasRole(emissionManager.CLAIMER_ROLE(), address(this))); + } + + function test_SetEmissionStartTimestamp() public { + uint64 emissionStartTimestamp = uint64(block.timestamp) + 1; + emissionManager.setEmissionStartTimestamp(emissionStartTimestamp); + assertEq(emissionManager.getEmissionStartTimestamp(), emissionStartTimestamp); + assertEq(emissionManager.getLastClaimedTimestamp(), 0); + } + + function testFuzz_SetEmissionStartTimestamp(uint64 emissionStartTimestamp) public { + emissionStartTimestamp = uint64(bound(emissionStartTimestamp, uint64(block.timestamp) + 1, type(uint64).max)); + emissionManager.setEmissionStartTimestamp(emissionStartTimestamp); + assertEq(emissionManager.getEmissionStartTimestamp(), emissionStartTimestamp); + assertEq(emissionManager.getLastClaimedTimestamp(), 0); + } + + function testFuzz_SetEmissionStartTimestamp_RevertIf_NotDefaultAdmin(address caller, uint48 emissionStartTimestamp) + public + { + vm.assume(caller != address(this)); + vm.startPrank(caller); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, caller, emissionManager.DEFAULT_ADMIN_ROLE() + ) + ); + emissionManager.setEmissionStartTimestamp(emissionStartTimestamp); + vm.stopPrank(); + } + + function test_SetEmissionPerSecond() public { + uint256 newEmissionPerSecond = 2 ether; + emissionManager.setEmissionPerSecond(newEmissionPerSecond); + assertEq(emissionManager.getEmissionPerSecond(), newEmissionPerSecond); + } + + function testFuzz_SetEmissionPerSecond(uint256 newEmissionPerSecond) public { + emissionManager.setEmissionPerSecond(newEmissionPerSecond); + assertEq(emissionManager.getEmissionPerSecond(), newEmissionPerSecond); + } + + function testFuzz_SetEmissionPerSecond_RevertIf_NotDefaultAdmin(address caller, uint256 newEmissionPerSecond) + public + { + vm.assume(caller != address(this)); + vm.startPrank(caller); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, caller, emissionManager.DEFAULT_ADMIN_ROLE() + ) + ); + emissionManager.setEmissionPerSecond(newEmissionPerSecond); + vm.stopPrank(); + } + + function test_Claim() public { + deal(seam, address(emissionManager), type(uint256).max); + + uint256 receiverBalanceBefore = IERC20(seam).balanceOf(address(this)); + uint256 emissionManagerBalanceBefore = IERC20(seam).balanceOf(address(emissionManager)); + + vm.warp(block.timestamp + 5000); + emissionManager.claim(address(this)); + + assertEq(IERC20(seam).balanceOf(address(this)), receiverBalanceBefore + emissionPerSecond * 5000); + assertEq( + IERC20(seam).balanceOf(address(emissionManager)), emissionManagerBalanceBefore - emissionPerSecond * 5000 + ); + } + + function test_Claim_RevertIf_NotStarted() public { + deal(seam, address(emissionManager), type(uint256).max); + + emissionManager.setEmissionStartTimestamp(uint64(block.timestamp) + 1); + + vm.expectRevert(abi.encodeWithSelector(ISeamEmissionManager.EmissionsNotStarted.selector, block.timestamp + 1)); + emissionManager.claim(address(this)); + } + + function test_Claim_InsufficientBalance() public { + uint256 timeElapsed = 5000; + uint256 partialAmount = emissionPerSecond * timeElapsed / 2; // Only half of the needed amount + + // Set a specific token balance that's less than what would be required + deal(seam, address(emissionManager), partialAmount); + + uint256 receiverBalanceBefore = IERC20(seam).balanceOf(address(this)); + + vm.warp(block.timestamp + timeElapsed); + emissionManager.claim(address(this)); + + // Check that only the available balance was transferred + assertEq(IERC20(seam).balanceOf(address(this)), receiverBalanceBefore + partialAmount); + assertEq(IERC20(seam).balanceOf(address(emissionManager)), 0); + } + + function testFuzz_Claim(address receiver, uint256 timeElapsed) public { + vm.assume(receiver != address(0)); + timeElapsed = bound(timeElapsed, 0, type(uint32).max / emissionPerSecond); + deal(seam, address(emissionManager), type(uint256).max); + + uint256 receiverBalanceBefore = IERC20(seam).balanceOf(receiver); + uint256 emissionManagerBalanceBefore = IERC20(seam).balanceOf(address(emissionManager)); + + vm.warp(block.timestamp + timeElapsed); + emissionManager.claim(receiver); + + assertEq(IERC20(seam).balanceOf(receiver), receiverBalanceBefore + emissionPerSecond * timeElapsed); + assertEq( + IERC20(seam).balanceOf(address(emissionManager)), + emissionManagerBalanceBefore - emissionPerSecond * timeElapsed + ); + } + + function testFuzz_Claim_InsufficientBalance(uint256 availableBalance) public { + address receiver = makeAddr("receiver"); + uint32 timeElapsed = uint32(type(uint32).max); + uint256 requiredAmount = emissionPerSecond * timeElapsed; + availableBalance = bound(availableBalance, 1, requiredAmount - 1); + + deal(seam, address(emissionManager), availableBalance); + + uint256 receiverBalanceBefore = IERC20(seam).balanceOf(receiver); + + vm.warp(block.timestamp + timeElapsed); + emissionManager.claim(receiver); + + // Check that only the available balance was transferred + assertEq(IERC20(seam).balanceOf(receiver), receiverBalanceBefore + availableBalance); + assertEq(IERC20(seam).balanceOf(address(emissionManager)), 0); + } + + function testFuzz_Claim_RevertIf_NotClaimer(address caller) public { + vm.assume(caller != address(this)); + vm.startPrank(caller); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, caller, emissionManager.CLAIMER_ROLE() + ) + ); + emissionManager.claim(address(this)); + vm.stopPrank(); + } +} From 31ae39610c102ae06ef01c09847707eb4ea5d0ab Mon Sep 17 00:00:00 2001 From: fredwes <6827305+fredwes@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:04:00 -0400 Subject: [PATCH 2/5] chore: fmt --- test/unit/SeamEmissionManagerV2.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/SeamEmissionManagerV2.t.sol b/test/unit/SeamEmissionManagerV2.t.sol index 1857aef..565bd47 100644 --- a/test/unit/SeamEmissionManagerV2.t.sol +++ b/test/unit/SeamEmissionManagerV2.t.sol @@ -121,7 +121,7 @@ contract SeamEmissionManagerV2Test is Test { function test_Claim_InsufficientBalance() public { uint256 timeElapsed = 5000; uint256 partialAmount = emissionPerSecond * timeElapsed / 2; // Only half of the needed amount - + // Set a specific token balance that's less than what would be required deal(seam, address(emissionManager), partialAmount); @@ -158,11 +158,11 @@ contract SeamEmissionManagerV2Test is Test { uint32 timeElapsed = uint32(type(uint32).max); uint256 requiredAmount = emissionPerSecond * timeElapsed; availableBalance = bound(availableBalance, 1, requiredAmount - 1); - + deal(seam, address(emissionManager), availableBalance); uint256 receiverBalanceBefore = IERC20(seam).balanceOf(receiver); - + vm.warp(block.timestamp + timeElapsed); emissionManager.claim(receiver); From 6f9f9d3133c2cda06c06c3491838b73bf9d402cb Mon Sep 17 00:00:00 2001 From: fredwes <6827305+fredwes@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:21:54 -0400 Subject: [PATCH 3/5] chore: new commit hash for ci re-run --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef8702e..36e85dd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: check: strategy: fail-fast: true - name: Foundry project + name: Foundry project runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From bbb3cf87dd6d81c2556ab8a1501731379e7da4ea Mon Sep 17 00:00:00 2001 From: fredwes <6827305+fredwes@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:22:04 -0400 Subject: [PATCH 4/5] chore: new commit hash for ci re-run --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36e85dd..ef8702e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: check: strategy: fail-fast: true - name: Foundry project + name: Foundry project runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 79a18f64dd2e1c560d9fec5fe41e4b02865a6954 Mon Sep 17 00:00:00 2001 From: fredwes <6827305+fredwes@users.noreply.github.com> Date: Mon, 16 Jun 2025 14:45:48 -0400 Subject: [PATCH 5/5] test: add more fork test asserts --- test/fork/SeamEmissionManagerV2.t.sol | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/fork/SeamEmissionManagerV2.t.sol b/test/fork/SeamEmissionManagerV2.t.sol index 4a12124..bad6732 100644 --- a/test/fork/SeamEmissionManagerV2.t.sol +++ b/test/fork/SeamEmissionManagerV2.t.sol @@ -32,11 +32,33 @@ contract SeamEmissionManagerV2ForkTest is Test { vm.stopPrank(); + // Store values before upgrade + address seamBefore1 = emissionManager1.getSeam(); + uint64 emissionStartTimestampBefore1 = emissionManager1.getEmissionStartTimestamp(); + uint64 lastClaimedTimestampBefore1 = emissionManager1.getLastClaimedTimestamp(); + uint256 emissionPerSecondBefore1 = emissionManager1.getEmissionPerSecond(); + + address seamBefore2 = emissionManager2.getSeam(); + uint64 emissionStartTimestampBefore2 = emissionManager2.getEmissionStartTimestamp(); + uint64 lastClaimedTimestampBefore2 = emissionManager2.getLastClaimedTimestamp(); + uint256 emissionPerSecondBefore2 = emissionManager2.getEmissionPerSecond(); + vm.startPrank(Constants.LONG_TIMELOCK_ADDRESS); emissionManager1.upgradeToAndCall(address(newImplementation), ""); emissionManager2.upgradeToAndCall(address(newImplementation), ""); vm.stopPrank(); + // Validate values after upgrade match values before upgrade + assertEq(emissionManager1.getSeam(), seamBefore1); + assertEq(emissionManager1.getEmissionStartTimestamp(), emissionStartTimestampBefore1); + assertEq(emissionManager1.getLastClaimedTimestamp(), lastClaimedTimestampBefore1); + assertEq(emissionManager1.getEmissionPerSecond(), emissionPerSecondBefore1); + + assertEq(emissionManager2.getSeam(), seamBefore2); + assertEq(emissionManager2.getEmissionStartTimestamp(), emissionStartTimestampBefore2); + assertEq(emissionManager2.getLastClaimedTimestamp(), lastClaimedTimestampBefore2); + assertEq(emissionManager2.getEmissionPerSecond(), emissionPerSecondBefore2); + vm.startPrank(Constants.SHORT_TIMELOCK_ADDRESS); emissionManager1.claim(address(this)); emissionManager2.claim(address(this));