diff --git a/contracts/0.8.9/oracle/IWithdrawalVault.sol b/contracts/0.8.9/oracle/IWithdrawalVault.sol deleted file mode 100644 index 7df3e01c5..000000000 --- a/contracts/0.8.9/oracle/IWithdrawalVault.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity 0.8.9; - -interface IWithdrawalVault { - function addFullWithdrawalRequests(bytes[] calldata pubkeys) external; -} \ No newline at end of file diff --git a/contracts/0.8.9/oracle/ValidatorsExitBus.sol b/contracts/0.8.9/oracle/ValidatorsExitBus.sol index d914b98d9..8a722aa11 100644 --- a/contracts/0.8.9/oracle/ValidatorsExitBus.sol +++ b/contracts/0.8.9/oracle/ValidatorsExitBus.sol @@ -4,15 +4,22 @@ pragma solidity 0.8.9; import { AccessControlEnumerable } from "../utils/access/AccessControlEnumerable.sol"; import { UnstructuredStorage } from "../lib/UnstructuredStorage.sol"; -import { IWithdrawalVault } from "./IWithdrawalVault.sol"; +import { ILidoLocator } from "../../common/interfaces/ILidoLocator.sol"; + +interface IWithdrawalVault { + function addFullWithdrawalRequests(bytes[] calldata pubkeys) external payable; + + function getWithdrawalRequestFee() external view returns (uint256); +} + contract ValidatorsExitBus is AccessControlEnumerable { using UnstructuredStorage for bytes32; /// @dev Errors - // error DuplicateExitRequest(); error KeyWasNotUnpacked(uint256 keyIndex, uint256 lastUnpackedKeyIndex); error ZeroAddress(); + error FeeNotEnough(uint256 minFeePerRequest, uint256 requestCount, uint256 msgValue); /// Part of report data struct ExitRequestData { @@ -71,27 +78,33 @@ contract ValidatorsExitBus is AccessControlEnumerable { bytes32 internal constant EXIT_REQUESTS_HASHES_POSITION = keccak256("lido.ValidatorsExitBus.reportHashes"); - /// @dev Storage slot: address withdrawalVaultContract - bytes32 internal constant WITHDRAWAL_VAULT_CONTRACT_POSITION = - keccak256("lido.ValidatorsExitBus.withdrawalVaultContract"); + bytes32 private constant LOCATOR_CONTRACT_POSITION = keccak256("lido.ValidatorsExitBus.locatorContract"); - // ILidoLocator internal immutable LOCATOR; - - // TODO: read WV via locator - function _initialize_v2(address withdrawalVaultAddr) internal { - _setWithdrawalVault(withdrawalVaultAddr); + function _initialize_v2(address locatorAddr) internal { + _setLocatorAddress(locatorAddr); } - function _setWithdrawalVault(address addr) internal { + function _setLocatorAddress(address addr) internal { if (addr == address(0)) revert ZeroAddress(); - WITHDRAWAL_VAULT_CONTRACT_POSITION.setStorageAddress(addr); + LOCATOR_CONTRACT_POSITION.setStorageAddress(addr); } function triggerExitHashVerify(ExitRequestData calldata exitRequestData, uint256[] calldata keyIndexes) external payable { bytes32 dataHash = keccak256(abi.encode(exitRequestData)); RequestStatus storage requestStatus = _storageExitRequestsHashes()[dataHash]; + address locatorAddr = LOCATOR_CONTRACT_POSITION.getStorageAddress(); + address withdrawalVaultAddr = ILidoLocator(locatorAddr).withdrawalVault(); + uint256 fee = IWithdrawalVault(withdrawalVaultAddr).getWithdrawalRequestFee(); + uint requestsFee = keyIndexes.length * fee; + + if (msg.value < requestsFee) { + revert FeeNotEnough(fee, keyIndexes.length, msg.value); + } + + uint256 refund = msg.value - requestsFee; + uint256 lastDeliveredKeyIndex = requestStatus.deliveredItemsCount - 1; uint256 offset; @@ -118,8 +131,13 @@ contract ValidatorsExitBus is AccessControlEnumerable { } - address withdrawalVaultAddr = WITHDRAWAL_VAULT_CONTRACT_POSITION.getStorageAddress(); IWithdrawalVault(withdrawalVaultAddr).addFullWithdrawalRequests(pubkeys); + + if (refund > 0) { + (bool success, ) = msg.sender.call{value: refund}(""); + require(success, "Refund failed"); + } + } /// Storage helpers diff --git a/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol b/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol index 8754839eb..bc65df934 100644 --- a/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol +++ b/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol @@ -110,10 +110,9 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil, ValidatorsExitBus _initialize(consensusContract, consensusVersion, lastProcessingRefSlot); } - // TODO: replace with locator - function finalizeUpgrade_v2(address withdrawalVaultAddress) external { + function finalizeUpgrade_v2() external { _updateContractVersion(2); - _initialize_v2(withdrawalVaultAddress); + _initialize_v2(address(LOCATOR)); } /// @notice Resume accepting validator exit requests @@ -420,7 +419,6 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil, ValidatorsExitBus function _storeOracleExitRequestHash(bytes32 exitRequestHash, ReportData calldata report, uint256 contractVersion) internal { mapping(bytes32 => RequestStatus) storage hashes = _storageExitRequestsHashes(); - // if (hashes[hash].itemsCount > 0 ) revert DuplicateExitRequest(); RequestStatus storage request = hashes[exitRequestHash]; request.totalItemsCount = report.exitRequestData.requestsCount; diff --git a/test/0.8.9/contracts/WithdrawalValut_MockForVebo.sol b/test/0.8.9/contracts/WithdrawalValut_MockForVebo.sol index 9d60ad048..97ca6e601 100644 --- a/test/0.8.9/contracts/WithdrawalValut_MockForVebo.sol +++ b/test/0.8.9/contracts/WithdrawalValut_MockForVebo.sol @@ -7,4 +7,8 @@ contract WithdrawalVault__MockForVebo { function addFullWithdrawalRequests(bytes[] calldata pubkeys) external { emit AddFullWithdrawalRequestsCalled(pubkeys); } + + function getWithdrawalRequestFee() external view returns (uint256) { + return 1; + } } \ No newline at end of file diff --git a/test/0.8.9/oracle/validator-exit-bus-oracle.triggerExitHashVerify.test.ts b/test/0.8.9/oracle/validator-exit-bus-oracle.triggerExitHashVerify.test.ts index a8e701b77..d9dc51c8a 100644 --- a/test/0.8.9/oracle/validator-exit-bus-oracle.triggerExitHashVerify.test.ts +++ b/test/0.8.9/oracle/validator-exit-bus-oracle.triggerExitHashVerify.test.ts @@ -39,7 +39,6 @@ describe("ValidatorsExitBusOracle.sol:triggerExitHashVerify", () => { let member1: HardhatEthersSigner; let member2: HardhatEthersSigner; let member3: HardhatEthersSigner; - let stranger: HardhatEthersSigner; const LAST_PROCESSING_REF_SLOT = 1; @@ -108,7 +107,7 @@ describe("ValidatorsExitBusOracle.sol:triggerExitHashVerify", () => { }; before(async () => { - [admin, member1, member2, member3, stranger] = await ethers.getSigners(); + [admin, member1, member2, member3] = await ethers.getSigners(); await deploy(); }); @@ -229,10 +228,16 @@ describe("ValidatorsExitBusOracle.sol:triggerExitHashVerify", () => { }); it("someone submitted exit report data and triggered exit", async () => { - const tx = await oracle.triggerExitHashVerify(reportFields.exitRequestData, [0, 1, 2]); + const tx = await oracle.triggerExitHashVerify(reportFields.exitRequestData, [0, 1, 2], { value: 3 }); await expect(tx) .to.emit(withdrawalVault, "AddFullWithdrawalRequestsCalled") .withArgs([PUBKEYS[0], PUBKEYS[1], PUBKEYS[2]]); }); + + it("someone submitted exit report data and triggered exit again", async () => { + const tx = await oracle.triggerExitHashVerify(reportFields.exitRequestData, [0, 1], { value: 2 }); + + await expect(tx).to.emit(withdrawalVault, "AddFullWithdrawalRequestsCalled").withArgs([PUBKEYS[0], PUBKEYS[1]]); + }); }); diff --git a/test/deploy/validatorExitBusOracle.ts b/test/deploy/validatorExitBusOracle.ts index 7acca3e91..fd95c7bd9 100644 --- a/test/deploy/validatorExitBusOracle.ts +++ b/test/deploy/validatorExitBusOracle.ts @@ -67,9 +67,12 @@ export async function deployVEBO( const { ao, lido } = await deployMockAccountingOracle(secondsPerSlot, genesisTime); + const withdrawalVault = await deployWithdrawalVault(); + await updateLidoLocatorImplementation(locatorAddr, { lido: await lido.getAddress(), accountingOracle: await ao.getAddress(), + withdrawalVault, }); const oracleReportSanityChecker = await deployOracleReportSanityCheckerForExitBus(locatorAddr, admin); @@ -81,8 +84,6 @@ export async function deployVEBO( await consensus.setTime(genesisTime + initialEpoch * slotsPerEpoch * secondsPerSlot); - const withdrawalVault = await deployWithdrawalVault(); - return { locatorAddr, oracle, @@ -107,7 +108,6 @@ export async function initVEBO({ admin, oracle, consensus, - withdrawalVault, dataSubmitter = undefined, consensusVersion = CONSENSUS_VERSION, lastProcessingRefSlot = 0, @@ -115,7 +115,7 @@ export async function initVEBO({ }: VEBOConfig) { const initTx = await oracle.initialize(admin, await consensus.getAddress(), consensusVersion, lastProcessingRefSlot); - await oracle.finalizeUpgrade_v2(withdrawalVault); + await oracle.finalizeUpgrade_v2(); await oracle.grantRole(await oracle.MANAGE_CONSENSUS_CONTRACT_ROLE(), admin); await oracle.grantRole(await oracle.MANAGE_CONSENSUS_VERSION_ROLE(), admin);