From 6331d4c776202ced20b3ce7366d9489160b74742 Mon Sep 17 00:00:00 2001 From: adu Date: Wed, 9 Oct 2024 10:14:38 +0800 Subject: [PATCH] fix: tests and scripts --- script/12_RedeployClientChainGateway.s.sol | 6 +- script/1_Prerequisities.s.sol | 3 +- script/2_DeployBoth.s.sol | 3 +- script/7_DeployBootstrap.s.sol | 3 +- script/BaseScript.sol | 9 +- script/TestPrecompileErrorFixed.s.sol | 3 +- src/core/BaseRestakingController.sol | 16 +-- src/core/Bootstrap.sol | 16 ++- src/core/ClientChainGateway.sol | 3 +- src/core/ExoCapsule.sol | 2 +- src/core/ExocoreGateway.sol | 5 +- src/core/RewardVault.sol | 13 ++- src/core/Vault.sol | 5 +- src/interfaces/IBaseRestakingController.sol | 4 +- src/interfaces/IRewardVault.sol | 5 +- src/interfaces/precompiles/IReward.sol | 9 +- src/libraries/ActionAttributes.sol | 3 +- src/storage/RewardVaultStorage.sol | 4 +- src/storage/VaultStorage.sol | 2 +- test/foundry/ExocoreDeployer.t.sol | 11 +- test/foundry/Governance.t.sol | 3 +- test/foundry/WithdrawReward.t.sol | 112 +++++++++++++++++--- test/foundry/unit/Bootstrap.t.sol | 6 +- test/foundry/unit/ClientChainGateway.t.sol | 5 +- test/foundry/unit/ExocoreGateway.t.sol | 6 +- test/mocks/ExocoreGatewayMock.sol | 3 +- test/mocks/RewardMock.sol | 11 +- 27 files changed, 194 insertions(+), 77 deletions(-) diff --git a/script/12_RedeployClientChainGateway.s.sol b/script/12_RedeployClientChainGateway.s.sol index b03d0333..0e58543d 100644 --- a/script/12_RedeployClientChainGateway.s.sol +++ b/script/12_RedeployClientChainGateway.s.sol @@ -7,8 +7,9 @@ import {Bootstrap} from "../src/core/Bootstrap.sol"; import {ClientChainGateway} from "../src/core/ClientChainGateway.sol"; import "../src/core/ExoCapsule.sol"; -import {Vault} from "../src/core/Vault.sol"; + import {RewardVault} from "../src/core/RewardVault.sol"; +import {Vault} from "../src/core/Vault.sol"; import "../src/utils/BeaconProxyBytecode.sol"; import {CustomProxyAdmin} from "../src/utils/CustomProxyAdmin.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; @@ -36,7 +37,8 @@ contract RedeployClientChainGateway is BaseScript { require(address(beaconOracle) != address(0), "beacon oracle should not be empty"); vaultBeacon = UpgradeableBeacon(stdJson.readAddress(prerequisiteContracts, ".clientChain.vaultBeacon")); require(address(vaultBeacon) != address(0), "vault beacon should not be empty"); - rewardVaultBeacon = UpgradeableBeacon(stdJson.readAddress(prerequisiteContracts, ".clientChain.rewardVaultBeacon")); + rewardVaultBeacon = + UpgradeableBeacon(stdJson.readAddress(prerequisiteContracts, ".clientChain.rewardVaultBeacon")); require(address(rewardVaultBeacon) != address(0), "reward vault beacon should not be empty"); capsuleBeacon = UpgradeableBeacon(stdJson.readAddress(prerequisiteContracts, ".clientChain.capsuleBeacon")); require(address(capsuleBeacon) != address(0), "capsule beacon should not be empty"); diff --git a/script/1_Prerequisities.s.sol b/script/1_Prerequisities.s.sol index de40594a..ce46c9ad 100644 --- a/script/1_Prerequisities.s.sol +++ b/script/1_Prerequisities.s.sol @@ -7,9 +7,10 @@ import {ERC20PresetFixedSupply} from "@openzeppelin/contracts/token/ERC20/preset import "forge-std/Script.sol"; import "test/mocks/AssetsMock.sol"; -import "test/mocks/RewardMock.sol"; + import "test/mocks/DelegationMock.sol"; import {NonShortCircuitEndpointV2Mock} from "test/mocks/NonShortCircuitEndpointV2Mock.sol"; +import "test/mocks/RewardMock.sol"; contract PrerequisitiesScript is BaseScript { diff --git a/script/2_DeployBoth.s.sol b/script/2_DeployBoth.s.sol index ed9d34ca..8e595a5e 100644 --- a/script/2_DeployBoth.s.sol +++ b/script/2_DeployBoth.s.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.19; import "../src/core/ClientChainGateway.sol"; import "../src/core/ExoCapsule.sol"; import "../src/core/ExocoreGateway.sol"; -import {Vault} from "../src/core/Vault.sol"; + import {RewardVault} from "../src/core/RewardVault.sol"; +import {Vault} from "../src/core/Vault.sol"; import "../src/utils/BeaconProxyBytecode.sol"; import "../src/utils/CustomProxyAdmin.sol"; import {ExocoreGatewayMock} from "../test/mocks/ExocoreGatewayMock.sol"; diff --git a/script/7_DeployBootstrap.s.sol b/script/7_DeployBootstrap.s.sol index 8e037b15..d28df780 100644 --- a/script/7_DeployBootstrap.s.sol +++ b/script/7_DeployBootstrap.s.sol @@ -7,8 +7,9 @@ import {Bootstrap} from "../src/core/Bootstrap.sol"; import {ClientChainGateway} from "../src/core/ClientChainGateway.sol"; import "../src/core/ExoCapsule.sol"; -import {Vault} from "../src/core/Vault.sol"; + import {RewardVault} from "../src/core/RewardVault.sol"; +import {Vault} from "../src/core/Vault.sol"; import "../src/utils/BeaconProxyBytecode.sol"; import {CustomProxyAdmin} from "../src/utils/CustomProxyAdmin.sol"; diff --git a/script/BaseScript.sol b/script/BaseScript.sol index 464d337b..2d251a56 100644 --- a/script/BaseScript.sol +++ b/script/BaseScript.sol @@ -3,14 +3,16 @@ pragma solidity ^0.8.19; import "../src/interfaces/IClientChainGateway.sol"; import "../src/interfaces/IExoCapsule.sol"; import "../src/interfaces/IExocoreGateway.sol"; -import "../src/interfaces/IVault.sol"; + import "../src/interfaces/IRewardVault.sol"; +import "../src/interfaces/IVault.sol"; import "../src/utils/BeaconProxyBytecode.sol"; import "../src/utils/CustomProxyAdmin.sol"; import "../src/interfaces/precompiles/IAssets.sol"; -import "../src/interfaces/precompiles/IReward.sol"; + import "../src/interfaces/precompiles/IDelegation.sol"; +import "../src/interfaces/precompiles/IReward.sol"; import "@beacon-oracle/contracts/src/EigenLayerBeaconOracle.sol"; import "@layerzero-v2/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol"; @@ -20,8 +22,9 @@ import "forge-std/Script.sol"; import {StdCheats} from "forge-std/StdCheats.sol"; import "../test/mocks/AssetsMock.sol"; -import "../test/mocks/RewardMock.sol"; + import "../test/mocks/DelegationMock.sol"; +import "../test/mocks/RewardMock.sol"; contract BaseScript is Script, StdCheats { diff --git a/script/TestPrecompileErrorFixed.s.sol b/script/TestPrecompileErrorFixed.s.sol index 112fe058..a119899b 100644 --- a/script/TestPrecompileErrorFixed.s.sol +++ b/script/TestPrecompileErrorFixed.s.sol @@ -6,8 +6,9 @@ import "../src/interfaces/IExocoreGateway.sol"; import "../src/interfaces/IVault.sol"; import "../src/interfaces/precompiles/IAssets.sol"; -import "../src/interfaces/precompiles/IReward.sol"; + import "../src/interfaces/precompiles/IDelegation.sol"; +import "../src/interfaces/precompiles/IReward.sol"; import {Action, GatewayStorage} from "../src/storage/GatewayStorage.sol"; import {NonShortCircuitEndpointV2Mock} from "../test/mocks/NonShortCircuitEndpointV2Mock.sol"; diff --git a/src/core/BaseRestakingController.sol b/src/core/BaseRestakingController.sol index 5bc8a481..5e490d92 100644 --- a/src/core/BaseRestakingController.sol +++ b/src/core/BaseRestakingController.sol @@ -82,19 +82,23 @@ abstract contract BaseRestakingController is _processRequest(Action.REQUEST_UNDELEGATE_FROM, actionArgs, bytes("")); } - function submitReward(address token, address avs, uint256 amount) - external + function submitReward(address token, address avs, uint256 amount) + external payable isValidAmount(amount) whenNotPaused nonReentrant { + // deposit reward to reward vault + rewardVault.deposit(token, msg.sender, avs, amount); + // send request to exocore, and this would not expect a response since deposit is supposed to be must success by + // protocol bytes memory actionArgs = abi.encodePacked(bytes32(bytes20(token)), bytes32(bytes20(avs)), amount); _processRequest(Action.REQUEST_SUBMIT_REWARD, actionArgs, bytes("")); } - function claimRewardFromExocore(address token, uint256 amount) - external + function claimRewardFromExocore(address token, uint256 amount) + external payable isValidAmount(amount) whenNotPaused @@ -105,8 +109,8 @@ abstract contract BaseRestakingController is _processRequest(Action.REQUEST_CLAIM_REWARD, actionArgs, encodedRequest); } - function withdrawReward(address token, address recipient, uint256 amount) - external + function withdrawReward(address token, address recipient, uint256 amount) + external isValidAmount(amount) whenNotPaused nonReentrant diff --git a/src/core/Bootstrap.sol b/src/core/Bootstrap.sol index 043d9a07..ff2bd9c1 100644 --- a/src/core/Bootstrap.sol +++ b/src/core/Bootstrap.sol @@ -435,7 +435,13 @@ contract Bootstrap is /// @inheritdoc IBaseRestakingController /// @dev This is not yet supported. - function submitReward(address token, address avs, uint256 rewardAmount) external payable override beforeLocked whenNotPaused { + function submitReward(address, address, uint256) + external + payable + override + beforeLocked + whenNotPaused + { revert Errors.NotYetSupported(); } @@ -447,7 +453,13 @@ contract Bootstrap is /// @inheritdoc IBaseRestakingController /// @dev This is not yet supported. - function withdrawReward(address token, address recipient, uint256 rewardAmount) external view override beforeLocked whenNotPaused { + function withdrawReward(address, address, uint256) + external + view + override + beforeLocked + whenNotPaused + { revert Errors.NotYetSupported(); } diff --git a/src/core/ClientChainGateway.sol b/src/core/ClientChainGateway.sol index 3960da81..99b156d0 100644 --- a/src/core/ClientChainGateway.sol +++ b/src/core/ClientChainGateway.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.19; import {IClientChainGateway} from "../interfaces/IClientChainGateway.sol"; + +import {IRewardVault} from "../interfaces/IRewardVault.sol"; import {ITokenWhitelister} from "../interfaces/ITokenWhitelister.sol"; import {IVault} from "../interfaces/IVault.sol"; -import {IRewardVault} from "../interfaces/IRewardVault.sol"; import {OAppCoreUpgradeable} from "../lzApp/OAppCoreUpgradeable.sol"; import {OAppReceiverUpgradeable} from "../lzApp/OAppReceiverUpgradeable.sol"; import {MessagingFee, OAppSenderUpgradeable} from "../lzApp/OAppSenderUpgradeable.sol"; diff --git a/src/core/ExoCapsule.sol b/src/core/ExoCapsule.sol index 5346200f..5e364d1f 100644 --- a/src/core/ExoCapsule.sol +++ b/src/core/ExoCapsule.sol @@ -23,7 +23,7 @@ contract ExoCapsule is ReentrancyGuardUpgradeable, ExoCapsuleStorage, IExoCapsul using ValidatorContainer for bytes32[]; using WithdrawalContainer for bytes32[]; - /// @notice Emitted when the ETH principal balance is unlocked. + /// @notice Emitted when the ETH principal balance is unlocked. /// @param owner The address of the capsule owner. /// @param unlockedAmount The amount added to the withdrawable balance. event ETHPrincipalUnlocked(address owner, uint256 unlockedAmount); diff --git a/src/core/ExocoreGateway.sol b/src/core/ExocoreGateway.sol index ecef17ee..c99f6545 100644 --- a/src/core/ExocoreGateway.sol +++ b/src/core/ExocoreGateway.sol @@ -6,8 +6,9 @@ import {IExocoreGateway} from "../interfaces/IExocoreGateway.sol"; import {Action} from "../storage/GatewayStorage.sol"; import {ASSETS_CONTRACT} from "../interfaces/precompiles/IAssets.sol"; -import {REWARD_CONTRACT} from "../interfaces/precompiles/IReward.sol"; + import {DELEGATION_CONTRACT} from "../interfaces/precompiles/IDelegation.sol"; +import {REWARD_CONTRACT} from "../interfaces/precompiles/IReward.sol"; import { MessagingFee, @@ -413,7 +414,7 @@ contract ExocoreGateway is } else { (success,) = REWARD_CONTRACT.claimReward(srcChainId, token, avsOrWithdrawer, amount); } - emit RewardOperation(success, isSubmitReward, bytes32(token), bytes32(avsOrWithdrawer), amount); + emit RewardOperation(isSubmitReward, success, bytes32(token), bytes32(avsOrWithdrawer), amount); response = isSubmitReward ? bytes("") : abi.encodePacked(lzNonce, success); } diff --git a/src/core/RewardVault.sol b/src/core/RewardVault.sol index be95f4f1..074325ee 100644 --- a/src/core/RewardVault.sol +++ b/src/core/RewardVault.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +import {IRewardVault} from "../interfaces/IRewardVault.sol"; import {Errors} from "../libraries/Errors.sol"; import {RewardVaultStorage} from "../storage/RewardVaultStorage.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {IRewardVault} from "../interfaces/IRewardVault.sol"; contract RewardVault is RewardVaultStorage, Initializable, IRewardVault { @@ -27,8 +27,9 @@ contract RewardVault is RewardVaultStorage, Initializable, IRewardVault { gateway = gateway_; } - function deposit(address token, address avs, uint256 amount) external onlyGateway { - IERC20(token).safeTransferFrom(msg.sender, address(this), amount); + // slither-disable-next-line arbitrary-send-erc20 + function deposit(address token, address depositor, address avs, uint256 amount) external onlyGateway { + IERC20(token).safeTransferFrom(depositor, address(this), amount); totalDepositedRewards[token][avs] += amount; emit RewardDeposited(token, avs, amount); @@ -44,10 +45,7 @@ contract RewardVault is RewardVaultStorage, Initializable, IRewardVault { emit RewardWithdrawn(token, withdrawer, recipient, amount); } - function unlockReward(address token, address withdrawer, uint256 amount) - external - onlyGateway - { + function unlockReward(address token, address withdrawer, uint256 amount) external onlyGateway { withdrawableBalances[token][withdrawer] += amount; emit RewardUnlocked(token, withdrawer, amount); @@ -60,4 +58,5 @@ contract RewardVault is RewardVaultStorage, Initializable, IRewardVault { function getTotalDepositedRewards(address token, address avs) external view returns (uint256) { return totalDepositedRewards[token][avs]; } + } diff --git a/src/core/Vault.sol b/src/core/Vault.sol index 911c5d2b..758f940f 100644 --- a/src/core/Vault.sol +++ b/src/core/Vault.sol @@ -98,10 +98,7 @@ contract Vault is Initializable, VaultStorage, IVault { } /// @inheritdoc IVault - function unlockPrincipal(address user, uint256 amount) - external - onlyGateway - { + function unlockPrincipal(address user, uint256 amount) external onlyGateway { uint256 totalDeposited = totalDepositedPrincipalAmount[user]; if (amount > totalDeposited) { revert Errors.VaultPrincipalExceedsTotalDeposit(); diff --git a/src/interfaces/IBaseRestakingController.sol b/src/interfaces/IBaseRestakingController.sol index 8165dfd3..148d860e 100644 --- a/src/interfaces/IBaseRestakingController.sol +++ b/src/interfaces/IBaseRestakingController.sol @@ -28,7 +28,7 @@ interface IBaseRestakingController { /// @notice Submits reward to the reward module on behalf of the AVS /// @param token The address of the specific token that the user wants to submit as a reward. - /// @param rewardAmount The amount of reward tokens that the user wants to submit. + /// @param rewardAmount The amount of reward tokens that the user wants to submit. function submitReward(address token, address avs, uint256 rewardAmount) external payable; /// @notice Claims reward tokens from Exocore. @@ -39,7 +39,7 @@ interface IBaseRestakingController { /// @notice Withdraws reward tokens from vault to the recipient. /// @param token The address of the specific token that the user wants to withdraw as a reward. /// @param recipient The address of the recipient of the reward tokens. - /// @param rewardAmount The amount of reward tokens that the user wants to withdraw. + /// @param rewardAmount The amount of reward tokens that the user wants to withdraw. function withdrawReward(address token, address recipient, uint256 rewardAmount) external; } diff --git a/src/interfaces/IRewardVault.sol b/src/interfaces/IRewardVault.sol index 73d5250b..33cfdb32 100644 --- a/src/interfaces/IRewardVault.sol +++ b/src/interfaces/IRewardVault.sol @@ -15,7 +15,7 @@ interface IRewardVault { * @param avs The avs ID to which the token is deposited. * @param amount The amount of the token to be deposited. */ - function deposit(address token, address avs, uint256 amount) external; + function deposit(address token, address depositor, address avs, uint256 amount) external; /** * @notice Withdraws a token from the reward vault. @@ -49,4 +49,5 @@ interface IRewardVault { * @return The total deposited rewards of the token for the avs. */ function getTotalDepositedRewards(address token, address avs) external view returns (uint256); -} \ No newline at end of file + +} diff --git a/src/interfaces/precompiles/IReward.sol b/src/interfaces/precompiles/IReward.sol index 0c8c480c..6ae600dd 100644 --- a/src/interfaces/precompiles/IReward.sol +++ b/src/interfaces/precompiles/IReward.sol @@ -22,12 +22,9 @@ interface IReward { /// @param assetsAddress The client chain asset Address /// @param avsId The contract address of the AVS /// @param amount The reward amount - function submitReward( - uint32 clientChainLzId, - bytes calldata assetsAddress, - bytes calldata avsId, - uint256 amount - ) external returns (bool success, uint256 latestAssetState); + function submitReward(uint32 clientChainLzId, bytes calldata assetsAddress, bytes calldata avsId, uint256 amount) + external + returns (bool success, uint256 latestAssetState); /// TRANSACTIONS /// @dev ClaimReward To the staker, that will change the state in reward module diff --git a/src/libraries/ActionAttributes.sol b/src/libraries/ActionAttributes.sol index b8bed42f..7fa19f69 100644 --- a/src/libraries/ActionAttributes.sol +++ b/src/libraries/ActionAttributes.sol @@ -40,7 +40,8 @@ library ActionAttributes { } else if (action == Action.REQUEST_CLAIM_REWARD) { attributes = REWARD | WITHDRAWAL; messageLength = ASSET_OPERATION_LENGTH; - } else if (action == Action.REQUEST_SUBMIT_REWARD) { // New action + } else if (action == Action.REQUEST_SUBMIT_REWARD) { + // New action attributes = REWARD; messageLength = ASSET_OPERATION_LENGTH; } else if (action == Action.REQUEST_DELEGATE_TO || action == Action.REQUEST_UNDELEGATE_FROM) { diff --git a/src/storage/RewardVaultStorage.sol b/src/storage/RewardVaultStorage.sol index 08d65ace..b2b4d9be 100644 --- a/src/storage/RewardVaultStorage.sol +++ b/src/storage/RewardVaultStorage.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - contract RewardVaultStorage { // Address of the gateway contract @@ -21,4 +18,5 @@ contract RewardVaultStorage { event RewardDeposited(address indexed token, address indexed avs, uint256 amount); event RewardUnlocked(address indexed token, address indexed staker, uint256 amount); event RewardWithdrawn(address indexed token, address indexed staker, address indexed recipient, uint256 amount); + } diff --git a/src/storage/VaultStorage.sol b/src/storage/VaultStorage.sol index 094bbe94..646d64be 100644 --- a/src/storage/VaultStorage.sol +++ b/src/storage/VaultStorage.sol @@ -32,7 +32,7 @@ contract VaultStorage { /// @notice Emitted when a user's principal balance is deposited. /// @param depositor The address of the depositor. - /// @param amount The amount of the principal balance deposited. + /// @param amount The amount of the principal balance deposited. event PrincipalDeposited(address depositor, uint256 amount); /// @notice Emitted when a user's principal balance is unlocked for withdrawal. diff --git a/test/foundry/ExocoreDeployer.t.sol b/test/foundry/ExocoreDeployer.t.sol index 4164d566..a416fbe1 100644 --- a/test/foundry/ExocoreDeployer.t.sol +++ b/test/foundry/ExocoreDeployer.t.sol @@ -18,21 +18,24 @@ import "../../src/core/ClientChainGateway.sol"; import "../../src/core/ExoCapsule.sol"; import "../../src/core/ExocoreGateway.sol"; -import {Vault} from "../../src/core/Vault.sol"; + import {RewardVault} from "../../src/core/RewardVault.sol"; +import {Vault} from "../../src/core/Vault.sol"; import {Action, GatewayStorage} from "../../src/storage/GatewayStorage.sol"; -import {IVault} from "../../src/interfaces/IVault.sol"; import {IRewardVault} from "../../src/interfaces/IRewardVault.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; import "../../src/interfaces/precompiles/IAssets.sol"; -import "../../src/interfaces/precompiles/IReward.sol"; + import "../../src/interfaces/precompiles/IDelegation.sol"; +import "../../src/interfaces/precompiles/IReward.sol"; import "../mocks/AssetsMock.sol"; -import "../mocks/RewardMock.sol"; + import "../mocks/DelegationMock.sol"; import {NonShortCircuitEndpointV2Mock} from "../mocks/NonShortCircuitEndpointV2Mock.sol"; +import "../mocks/RewardMock.sol"; import "src/core/ExoCapsule.sol"; import "src/utils/BeaconProxyBytecode.sol"; diff --git a/test/foundry/Governance.t.sol b/test/foundry/Governance.t.sol index 5381dac1..ea7ca693 100644 --- a/test/foundry/Governance.t.sol +++ b/test/foundry/Governance.t.sol @@ -24,8 +24,9 @@ import "src/core/ClientChainGateway.sol"; import "src/storage/ClientChainGatewayStorage.sol"; import "src/core/ExoCapsule.sol"; -import {Vault} from "src/core/Vault.sol"; + import {RewardVault} from "src/core/RewardVault.sol"; +import {Vault} from "src/core/Vault.sol"; import {IRewardVault} from "src/interfaces/IRewardVault.sol"; import {NonShortCircuitEndpointV2Mock} from "../mocks/NonShortCircuitEndpointV2Mock.sol"; diff --git a/test/foundry/WithdrawReward.t.sol b/test/foundry/WithdrawReward.t.sol index 27718f08..7fa6416e 100644 --- a/test/foundry/WithdrawReward.t.sol +++ b/test/foundry/WithdrawReward.t.sol @@ -1,7 +1,10 @@ pragma solidity ^0.8.19; import "../../src/core/ExocoreGateway.sol"; + +import "../../src/interfaces/precompiles/IReward.sol"; import {Action, GatewayStorage} from "../../src/storage/GatewayStorage.sol"; +import "../mocks/RewardMock.sol"; import "./ExocoreDeployer.t.sol"; import "@layerzero-v2/protocol/contracts/libs/AddressCast.sol"; @@ -13,23 +16,108 @@ contract WithdrawRewardTest is ExocoreDeployer { using AddressCast for address; - event ClaimRewardResult(bool indexed success, bytes32 indexed token, bytes32 indexed withdrawer, uint256 amount); + event RewardOperation( + bool isSubmitReward, + bool indexed success, + bytes32 indexed token, + bytes32 indexed avsOrWithdrawer, + uint256 amount + ); event Transfer(address indexed from, address indexed to, uint256 amount); uint256 constant DEFAULT_ENDPOINT_CALL_GAS_LIMIT = 200_000; - function test_WithdrawRewardByLayerZero() public { - Player memory withdrawer = players[0]; - Player memory relayer = players[1]; + function test_SubmitClaimRewardByLayerZero() public { + Player memory avsDepositor = players[0]; + Player memory staker = players[1]; + Player memory relayer = players[2]; + address avs = address(0xaabb); + + // fund the avs depositor some restake token so that it can deposit reward to reward vault + vm.startPrank(exocoreValidatorSet.addr); + restakeToken.transfer(avsDepositor.addr, 1_000_000); + vm.stopPrank(); - deal(withdrawer.addr, 1e22); - deal(address(clientGateway), 1e22); + // fund the staker and exocore gateway for gas fee + deal(staker.addr, 1e22); deal(address(exocoreGateway), 1e22); - uint256 withdrawAmount = 1000; + + // the amount of deposit, distribute, and withdraw + uint256 depositAmount = 1000; + uint256 distributeAmount = 500; + uint256 withdrawAmount = 100; // before withdraw we should add whitelist tokens test_AddWhitelistTokens(); + _testSubmitReward(avsDepositor, relayer, avs, depositAmount); + RewardMock(REWARD_PRECOMPILE_ADDRESS).distributeReward( + clientChainId, + _addressToBytes(address(restakeToken)), + _addressToBytes(avs), + _addressToBytes(staker.addr), + distributeAmount + ); + _testClaimReward(staker, relayer, withdrawAmount); + } + + function _testSubmitReward(Player memory depositor, Player memory relayer, address avs, uint256 amount) internal { + // -- submit reward workflow -- + + // first user call client chain gateway to submit reward on behalf of AVS + + // depositor needs to approve the restake token to the client gateway + vm.startPrank(depositor.addr); + restakeToken.approve(address(clientGateway), amount); + vm.stopPrank(); + + // estimate l0 relay fee that the user should pay + bytes memory submitRewardRequestPayload = abi.encodePacked( + Action.REQUEST_SUBMIT_REWARD, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(avs)), amount + ); + uint256 requestNativeFee = clientGateway.quote(submitRewardRequestPayload); + bytes32 requestId = generateUID(outboundNonces[clientChainId], true); + + // client chain layerzero endpoint should emit the message packet including submit reward payload. + vm.expectEmit(true, true, true, true, address(clientChainLzEndpoint)); + emit NewPacket( + exocoreChainId, + address(clientGateway), + address(exocoreGateway).toBytes32(), + outboundNonces[clientChainId], + submitRewardRequestPayload + ); + + // client chain gateway should emit MessageSent event + vm.expectEmit(true, true, true, true, address(clientGateway)); + emit MessageSent(Action.REQUEST_SUBMIT_REWARD, requestId, outboundNonces[clientChainId]++, requestNativeFee); + + vm.startPrank(depositor.addr); + clientGateway.submitReward{value: requestNativeFee}(address(restakeToken), avs, amount); + vm.stopPrank(); + + // second layerzero relayers should watch the request message packet and relay the message to destination + // endpoint + + // exocore gateway should emit RewardOperation event + vm.expectEmit(true, true, true, true, address(exocoreGateway)); + emit RewardOperation(true, true, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(avs)), amount); + + vm.expectEmit(address(exocoreGateway)); + emit MessageExecuted(Action.REQUEST_SUBMIT_REWARD, inboundNonces[exocoreChainId]++); + + vm.startPrank(relayer.addr); + exocoreLzEndpoint.lzReceive( + Origin(clientChainId, address(clientGateway).toBytes32(), inboundNonces[exocoreChainId] - 1), + address(exocoreGateway), + requestId, + submitRewardRequestPayload, + bytes("") + ); + vm.stopPrank(); + } + + function _testClaimReward(Player memory withdrawer, Player memory relayer, uint256 amount) internal { // -- withdraw reward workflow -- // first user call client chain gateway to withdraw @@ -39,7 +127,7 @@ contract WithdrawRewardTest is ExocoreDeployer { Action.REQUEST_CLAIM_REWARD, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(withdrawer.addr)), - withdrawAmount + amount ); uint256 requestNativeFee = clientGateway.quote(withdrawRequestPayload); bytes32 requestId = generateUID(outboundNonces[clientChainId], true); @@ -57,7 +145,7 @@ contract WithdrawRewardTest is ExocoreDeployer { emit MessageSent(Action.REQUEST_CLAIM_REWARD, requestId, outboundNonces[clientChainId]++, requestNativeFee); vm.startPrank(withdrawer.addr); - clientGateway.claimRewardFromExocore{value: requestNativeFee}(address(restakeToken), withdrawAmount); + clientGateway.claimRewardFromExocore{value: requestNativeFee}(address(restakeToken), amount); vm.stopPrank(); // second layerzero relayers should watch the request message packet and relay the message to destination @@ -68,10 +156,10 @@ contract WithdrawRewardTest is ExocoreDeployer { uint256 responseNativeFee = exocoreGateway.quote(clientChainId, withdrawResponsePayload); bytes32 responseId = generateUID(outboundNonces[exocoreChainId], false); - // exocore gateway should emit WithdrawRewardResult event + // exocore gateway should emit RewardOperation event vm.expectEmit(true, true, true, true, address(exocoreGateway)); - emit ClaimRewardResult( - true, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(withdrawer.addr)), withdrawAmount + emit RewardOperation( + false, true, bytes32(bytes20(address(restakeToken))), bytes32(bytes20(withdrawer.addr)), amount ); vm.expectEmit(true, true, true, true, address(exocoreLzEndpoint)); diff --git a/test/foundry/unit/Bootstrap.t.sol b/test/foundry/unit/Bootstrap.t.sol index fe0425cf..fa2ee032 100644 --- a/test/foundry/unit/Bootstrap.t.sol +++ b/test/foundry/unit/Bootstrap.t.sol @@ -11,9 +11,11 @@ import {IValidatorRegistry} from "src/interfaces/IValidatorRegistry.sol"; import {NonShortCircuitEndpointV2Mock} from "../../mocks/NonShortCircuitEndpointV2Mock.sol"; import {MyToken} from "./MyToken.sol"; -import {IVault} from "src/interfaces/IVault.sol"; -import {IRewardVault} from "src/interfaces/IRewardVault.sol"; + import {RewardVault} from "src/core/RewardVault.sol"; +import {IRewardVault} from "src/interfaces/IRewardVault.sol"; +import {IVault} from "src/interfaces/IVault.sol"; + import {Origin} from "src/lzApp/OAppReceiverUpgradeable.sol"; import {BootstrapStorage} from "src/storage/BootstrapStorage.sol"; import {Action, GatewayStorage} from "src/storage/GatewayStorage.sol"; diff --git a/test/foundry/unit/ClientChainGateway.t.sol b/test/foundry/unit/ClientChainGateway.t.sol index 1b4fccf3..181a7a91 100644 --- a/test/foundry/unit/ClientChainGateway.t.sol +++ b/test/foundry/unit/ClientChainGateway.t.sol @@ -28,10 +28,11 @@ import {Vault} from "src/core/Vault.sol"; import {Action, GatewayStorage} from "src/storage/GatewayStorage.sol"; import {NonShortCircuitEndpointV2Mock} from "../../mocks/NonShortCircuitEndpointV2Mock.sol"; + +import {RewardVault} from "src/core/RewardVault.sol"; import "src/interfaces/IExoCapsule.sol"; -import "src/interfaces/IVault.sol"; import {IRewardVault} from "src/interfaces/IRewardVault.sol"; -import {RewardVault} from "src/core/RewardVault.sol"; +import "src/interfaces/IVault.sol"; import {Errors} from "src/libraries/Errors.sol"; import "src/utils/BeaconProxyBytecode.sol"; diff --git a/test/foundry/unit/ExocoreGateway.t.sol b/test/foundry/unit/ExocoreGateway.t.sol index 7f96d6f6..3ad8eab2 100644 --- a/test/foundry/unit/ExocoreGateway.t.sol +++ b/test/foundry/unit/ExocoreGateway.t.sol @@ -2,13 +2,15 @@ pragma solidity ^0.8.19; import {NonShortCircuitEndpointV2Mock} from "../../mocks/NonShortCircuitEndpointV2Mock.sol"; import "src/interfaces/precompiles/IAssets.sol"; -import "src/interfaces/precompiles/IReward.sol"; + import "src/interfaces/precompiles/IDelegation.sol"; +import "src/interfaces/precompiles/IReward.sol"; import "src/libraries/Errors.sol"; import "test/mocks/AssetsMock.sol"; -import "test/mocks/RewardMock.sol"; + import "test/mocks/DelegationMock.sol"; +import "test/mocks/RewardMock.sol"; import "@layerzero-v2/protocol/contracts/libs/AddressCast.sol"; import "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/GUID.sol"; diff --git a/test/mocks/ExocoreGatewayMock.sol b/test/mocks/ExocoreGatewayMock.sol index ae3ae643..0a872055 100644 --- a/test/mocks/ExocoreGatewayMock.sol +++ b/test/mocks/ExocoreGatewayMock.sol @@ -4,8 +4,9 @@ import {IExocoreGateway} from "src/interfaces/IExocoreGateway.sol"; import {Action} from "src/storage/GatewayStorage.sol"; import {IAssets} from "src/interfaces/precompiles/IAssets.sol"; -import {IReward} from "src/interfaces/precompiles/IReward.sol"; + import {IDelegation} from "src/interfaces/precompiles/IDelegation.sol"; +import {IReward} from "src/interfaces/precompiles/IReward.sol"; import { MessagingFee, diff --git a/test/mocks/RewardMock.sol b/test/mocks/RewardMock.sol index 72d9d616..4dee1b19 100644 --- a/test/mocks/RewardMock.sol +++ b/test/mocks/RewardMock.sol @@ -7,12 +7,10 @@ contract RewardMock is IReward { mapping(uint32 => mapping(bytes => mapping(bytes => uint256))) public rewardsOfAVS; mapping(uint32 => mapping(bytes => mapping(bytes => uint256))) public rewardsOfStaker; - function submitReward( - uint32 clientChainLzId, - bytes calldata assetsAddress, - bytes calldata avsId, - uint256 amount - ) external returns (bool success, uint256 latestAssetState) { + function submitReward(uint32 clientChainLzId, bytes calldata assetsAddress, bytes calldata avsId, uint256 amount) + external + returns (bool success, uint256 latestAssetState) + { require(assetsAddress.length == 32, "invalid asset address"); require(avsId.length == 32, "invalid avsId"); rewardsOfAVS[clientChainLzId][assetsAddress][avsId] += amount; @@ -47,4 +45,5 @@ contract RewardMock is IReward { rewardsOfStaker[clientChainLzId][assetsAddress][staker] += amount; return (true, rewardsOfAVS[clientChainLzId][assetsAddress][avsId]); } + }