From 12a6da769b03ea0b788bbf125167020ea857ec50 Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Tue, 15 Apr 2025 14:55:10 +0900 Subject: [PATCH 1/2] feat: add withdrawFee function to EntropyGovernance contract --- .../contracts/entropy/EntropyGovernance.sol | 23 +++ .../forge-test/EntropyAuthorized.t.sol | 134 ++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/target_chains/ethereum/contracts/contracts/entropy/EntropyGovernance.sol b/target_chains/ethereum/contracts/contracts/entropy/EntropyGovernance.sol index 56fbeccdac..993e195b1e 100644 --- a/target_chains/ethereum/contracts/contracts/entropy/EntropyGovernance.sol +++ b/target_chains/ethereum/contracts/contracts/entropy/EntropyGovernance.sol @@ -17,6 +17,7 @@ abstract contract EntropyGovernance is EntropyState { event NewAdminProposed(address oldAdmin, address newAdmin); event NewAdminAccepted(address oldAdmin, address newAdmin); + event FeeWithdrawn(address targetAddress, uint amount); /** * @dev Returns the address of the proposed admin. @@ -92,5 +93,27 @@ abstract contract EntropyGovernance is EntropyState { emit DefaultProviderSet(oldDefaultProvider, newDefaultProvider); } + /** + * @dev Withdraw accumulated Pyth fees to a target address + * + * Calls {_authoriseAdminAction}. + * + * Emits a {FeeWithdrawn} event. + */ + function withdrawFee(address targetAddress, uint128 amount) external { + require(targetAddress != address(0), "targetAddress is zero address"); + _authoriseAdminAction(); + + if (amount > _state.accruedPythFeesInWei) + revert EntropyErrors.InsufficientFee(); + + _state.accruedPythFeesInWei -= amount; + + (bool success, ) = targetAddress.call{value: amount}(""); + require(success, "Failed to withdraw fees"); + + emit FeeWithdrawn(targetAddress, amount); + } + function _authoriseAdminAction() internal virtual; } diff --git a/target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol b/target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol index d6b46be752..3548bd4f52 100644 --- a/target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol +++ b/target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol @@ -174,4 +174,138 @@ contract EntropyAuthorized is Test, EntropyTestUtils { vm.expectRevert(EntropyErrors.Unauthorized.selector); random.acceptAdmin(); } + + function testWithdrawFeeByAdmin() public { + // Register provider1 first + bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); + vm.prank(provider1); + random.register(0, hashChain[0], hex"0100", 100, ""); + + // First accrue some fees through requests + vm.prank(admin); + random.setPythFee(10); + + // Make a few requests to accrue fees + bytes32 userCommitment = random.constructUserCommitment( + bytes32(uint256(42)) + ); + vm.deal(address(this), 50); + for (uint i = 0; i < 5; i++) { + random.request{value: 10}(provider1, userCommitment, false); + } + assertEq(random.getAccruedPythFees(), 50); + + address targetAddress = address(123); + uint128 withdrawAmount = 30; + + vm.prank(admin); + random.withdrawFee(targetAddress, withdrawAmount); + + assertEq(random.getAccruedPythFees(), 20); + assertEq(targetAddress.balance, withdrawAmount); + } + + function testWithdrawFeeByOwner() public { + // Register provider1 first + bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); + vm.prank(provider1); + random.register(0, hashChain[0], hex"0100", 100, ""); + + // First accrue some fees through requests + vm.prank(admin); + random.setPythFee(10); + + // Make a few requests to accrue fees + bytes32 userCommitment = random.constructUserCommitment( + bytes32(uint256(42)) + ); + vm.deal(address(this), 50); + for (uint i = 0; i < 5; i++) { + random.request{value: 10}(provider1, userCommitment, false); + } + assertEq(random.getAccruedPythFees(), 50); + + address targetAddress = address(123); + uint128 withdrawAmount = 30; + + vm.prank(owner); + random.withdrawFee(targetAddress, withdrawAmount); + + assertEq(random.getAccruedPythFees(), 20); + assertEq(targetAddress.balance, withdrawAmount); + } + + function testWithdrawFeeByUnauthorized() public { + // Register provider1 first + bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); + vm.prank(provider1); + random.register(0, hashChain[0], hex"0100", 100, ""); + + // First accrue some fees through requests + vm.prank(admin); + random.setPythFee(10); + + // Make a few requests to accrue fees + bytes32 userCommitment = random.constructUserCommitment( + bytes32(uint256(42)) + ); + vm.deal(address(this), 50); + for (uint i = 0; i < 5; i++) { + random.request{value: 10}(provider1, userCommitment, false); + } + + vm.prank(admin2); + vm.expectRevert(EntropyErrors.Unauthorized.selector); + random.withdrawFee(address(123), 30); + } + + function testWithdrawFeeInsufficientBalance() public { + // Register provider1 first + bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); + vm.prank(provider1); + random.register(0, hashChain[0], hex"0100", 100, ""); + + // First accrue some fees through requests + vm.prank(admin); + random.setPythFee(10); + + // Make a few requests to accrue fees + bytes32 userCommitment = random.constructUserCommitment( + bytes32(uint256(42)) + ); + vm.deal(address(this), 50); + for (uint i = 0; i < 5; i++) { + random.request{value: 10}(provider1, userCommitment, false); + } + assertEq(random.getAccruedPythFees(), 50); + + vm.prank(admin); + vm.expectRevert(EntropyErrors.InsufficientFee.selector); + random.withdrawFee(address(123), 60); + } + + function testWithdrawFeeToZeroAddress() public { + // Register provider1 first + bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); + vm.prank(provider1); + random.register(0, hashChain[0], hex"0100", 100, ""); + + // First accrue some fees through requests + vm.prank(admin); + random.setPythFee(10); + + // Make a few requests to accrue fees + bytes32 userCommitment = random.constructUserCommitment( + bytes32(uint256(42)) + ); + vm.deal(address(this), 50); + for (uint i = 0; i < 5; i++) { + random.request{value: 10}(provider1, userCommitment, false); + } + assertEq(random.getAccruedPythFees(), 50); + + vm.prank(admin); + vm.expectRevert("targetAddress is zero address"); + random.withdrawFee(address(0), 30); + } } From 551abdb5ea55c53561d40c0e181a8e54c1c55036 Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Thu, 17 Apr 2025 09:58:01 +0900 Subject: [PATCH 2/2] feat: refactor fee withdrawal tests to use setup helper function --- .../forge-test/EntropyAuthorized.t.sol | 110 +++++------------- 1 file changed, 27 insertions(+), 83 deletions(-) diff --git a/target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol b/target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol index 3548bd4f52..416c2959c0 100644 --- a/target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol +++ b/target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol @@ -175,25 +175,36 @@ contract EntropyAuthorized is Test, EntropyTestUtils { random.acceptAdmin(); } - function testWithdrawFeeByAdmin() public { - // Register provider1 first + // Helper function to setup contract with fees + function setupContractWithFees( + uint128 feeAmount, + uint numRequests + ) internal returns (uint128 totalFees) { + // Register provider1 bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); vm.prank(provider1); random.register(0, hashChain[0], hex"0100", 100, ""); - // First accrue some fees through requests + // Set Pyth fee vm.prank(admin); - random.setPythFee(10); + random.setPythFee(feeAmount); - // Make a few requests to accrue fees + // Make requests to accrue fees bytes32 userCommitment = random.constructUserCommitment( bytes32(uint256(42)) ); - vm.deal(address(this), 50); - for (uint i = 0; i < 5; i++) { - random.request{value: 10}(provider1, userCommitment, false); + vm.deal(address(this), feeAmount * numRequests); + for (uint i = 0; i < numRequests; i++) { + random.request{value: feeAmount}(provider1, userCommitment, false); } - assertEq(random.getAccruedPythFees(), 50); + + totalFees = uint128(feeAmount * numRequests); + assertEq(random.getAccruedPythFees(), totalFees); + return totalFees; + } + + function testWithdrawFeeByAdmin() public { + uint128 totalFees = setupContractWithFees(10, 5); address targetAddress = address(123); uint128 withdrawAmount = 30; @@ -201,29 +212,12 @@ contract EntropyAuthorized is Test, EntropyTestUtils { vm.prank(admin); random.withdrawFee(targetAddress, withdrawAmount); - assertEq(random.getAccruedPythFees(), 20); + assertEq(random.getAccruedPythFees(), totalFees - withdrawAmount); assertEq(targetAddress.balance, withdrawAmount); } function testWithdrawFeeByOwner() public { - // Register provider1 first - bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); - vm.prank(provider1); - random.register(0, hashChain[0], hex"0100", 100, ""); - - // First accrue some fees through requests - vm.prank(admin); - random.setPythFee(10); - - // Make a few requests to accrue fees - bytes32 userCommitment = random.constructUserCommitment( - bytes32(uint256(42)) - ); - vm.deal(address(this), 50); - for (uint i = 0; i < 5; i++) { - random.request{value: 10}(provider1, userCommitment, false); - } - assertEq(random.getAccruedPythFees(), 50); + uint128 totalFees = setupContractWithFees(10, 5); address targetAddress = address(123); uint128 withdrawAmount = 30; @@ -231,28 +225,12 @@ contract EntropyAuthorized is Test, EntropyTestUtils { vm.prank(owner); random.withdrawFee(targetAddress, withdrawAmount); - assertEq(random.getAccruedPythFees(), 20); + assertEq(random.getAccruedPythFees(), totalFees - withdrawAmount); assertEq(targetAddress.balance, withdrawAmount); } function testWithdrawFeeByUnauthorized() public { - // Register provider1 first - bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); - vm.prank(provider1); - random.register(0, hashChain[0], hex"0100", 100, ""); - - // First accrue some fees through requests - vm.prank(admin); - random.setPythFee(10); - - // Make a few requests to accrue fees - bytes32 userCommitment = random.constructUserCommitment( - bytes32(uint256(42)) - ); - vm.deal(address(this), 50); - for (uint i = 0; i < 5; i++) { - random.request{value: 10}(provider1, userCommitment, false); - } + setupContractWithFees(10, 5); vm.prank(admin2); vm.expectRevert(EntropyErrors.Unauthorized.selector); @@ -260,49 +238,15 @@ contract EntropyAuthorized is Test, EntropyTestUtils { } function testWithdrawFeeInsufficientBalance() public { - // Register provider1 first - bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); - vm.prank(provider1); - random.register(0, hashChain[0], hex"0100", 100, ""); - - // First accrue some fees through requests - vm.prank(admin); - random.setPythFee(10); - - // Make a few requests to accrue fees - bytes32 userCommitment = random.constructUserCommitment( - bytes32(uint256(42)) - ); - vm.deal(address(this), 50); - for (uint i = 0; i < 5; i++) { - random.request{value: 10}(provider1, userCommitment, false); - } - assertEq(random.getAccruedPythFees(), 50); + uint128 totalFees = setupContractWithFees(10, 5); vm.prank(admin); vm.expectRevert(EntropyErrors.InsufficientFee.selector); - random.withdrawFee(address(123), 60); + random.withdrawFee(address(123), totalFees + 10); } function testWithdrawFeeToZeroAddress() public { - // Register provider1 first - bytes32[] memory hashChain = generateHashChain(provider1, 0, 100); - vm.prank(provider1); - random.register(0, hashChain[0], hex"0100", 100, ""); - - // First accrue some fees through requests - vm.prank(admin); - random.setPythFee(10); - - // Make a few requests to accrue fees - bytes32 userCommitment = random.constructUserCommitment( - bytes32(uint256(42)) - ); - vm.deal(address(this), 50); - for (uint i = 0; i < 5; i++) { - random.request{value: 10}(provider1, userCommitment, false); - } - assertEq(random.getAccruedPythFees(), 50); + setupContractWithFees(10, 5); vm.prank(admin); vm.expectRevert("targetAddress is zero address");