From 1876adce2235c850207258f61071729da9bf3dc9 Mon Sep 17 00:00:00 2001 From: Alexander Biryukov Date: Mon, 23 Sep 2024 16:42:40 +0400 Subject: [PATCH 1/5] add DeoracleizedFeeDistributor --- .../feeDistributor/BaseFeeDistributor.sol | 102 +++++++++++------- .../DeoracleizedFeeDistributor.sol | 64 +++++++++++ .../feeDistributor/FeeDistributorErrors.sol | 26 +---- foundry.toml | 1 + package.json | 3 +- 5 files changed, 137 insertions(+), 59 deletions(-) create mode 100644 contracts/feeDistributor/DeoracleizedFeeDistributor.sol diff --git a/contracts/feeDistributor/BaseFeeDistributor.sol b/contracts/feeDistributor/BaseFeeDistributor.sol index 7f66f7b..289f45a 100644 --- a/contracts/feeDistributor/BaseFeeDistributor.sol +++ b/contracts/feeDistributor/BaseFeeDistributor.sol @@ -16,8 +16,14 @@ import "../lib/P2pAddressLib.sol"; import "./Erc4337Account.sol"; /// @title Common logic for all FeeDistributor types -abstract contract BaseFeeDistributor is Erc4337Account, OwnableTokenRecoverer, OwnableWithOperator, ReentrancyGuard, ERC165, IFeeDistributor { - +abstract contract BaseFeeDistributor is + Erc4337Account, + OwnableTokenRecoverer, + OwnableWithOperator, + ReentrancyGuard, + ERC165, + IFeeDistributor +{ /// @notice FeeDistributorFactory address IFeeDistributorFactory internal immutable i_factory; @@ -51,11 +57,13 @@ abstract contract BaseFeeDistributor is Erc4337Account, OwnableTokenRecoverer, O /// @dev Set values that are constant, common for all the clients, known at the initial deploy time. /// @param _factory address of FeeDistributorFactory /// @param _service address of the service (P2P) fee recipient - constructor( - address _factory, - address payable _service - ) { - if (!ERC165Checker.supportsInterface(_factory, type(IFeeDistributorFactory).interfaceId)) { + constructor(address _factory, address payable _service) { + if ( + !ERC165Checker.supportsInterface( + _factory, + type(IFeeDistributorFactory).interfaceId + ) + ) { revert FeeDistributor__NotFactory(_factory); } if (_service == address(0)) { @@ -80,39 +88,29 @@ abstract contract BaseFeeDistributor is Erc4337Account, OwnableTokenRecoverer, O revert FeeDistributor__ZeroAddressClient(); } if (_clientConfig.recipient == i_service) { - revert FeeDistributor__ClientAddressEqualsService(_clientConfig.recipient); + revert FeeDistributor__ClientAddressEqualsService( + _clientConfig.recipient + ); } if (s_clientConfig.recipient != address(0)) { revert FeeDistributor__ClientAlreadySet(s_clientConfig.recipient); } - if (_clientConfig.basisPoints >= 10000) { - revert FeeDistributor__InvalidClientBasisPoints(_clientConfig.basisPoints); - } - if (_referrerConfig.recipient != address(0)) {// if there is a referrer + if (_referrerConfig.recipient != address(0)) { + // if there is a referrer if (_referrerConfig.recipient == i_service) { - revert FeeDistributor__ReferrerAddressEqualsService(_referrerConfig.recipient); + revert FeeDistributor__ReferrerAddressEqualsService( + _referrerConfig.recipient + ); } if (_referrerConfig.recipient == _clientConfig.recipient) { - revert FeeDistributor__ReferrerAddressEqualsClient(_referrerConfig.recipient); - } - if (_referrerConfig.basisPoints == 0) { - revert FeeDistributor__ZeroReferrerBasisPointsForNonZeroReferrer(); - } - if (_clientConfig.basisPoints + _referrerConfig.basisPoints > 10000) { - revert FeeDistributor__ClientPlusReferralBasisPointsExceed10000( - _clientConfig.basisPoints, - _referrerConfig.basisPoints + revert FeeDistributor__ReferrerAddressEqualsClient( + _referrerConfig.recipient ); } // set referrer config s_referrerConfig = _referrerConfig; - - } else {// if there is no referrer - if (_referrerConfig.basisPoints != 0) { - revert FeeDistributor__ReferrerBasisPointsMustBeZeroIfAddressIsZero(_referrerConfig.basisPoints); - } } // set client config @@ -125,14 +123,25 @@ abstract contract BaseFeeDistributor is Erc4337Account, OwnableTokenRecoverer, O _referrerConfig.basisPoints ); - bool clientCanReceiveEther = P2pAddressLib._sendValue(_clientConfig.recipient, 0); + bool clientCanReceiveEther = P2pAddressLib._sendValue( + _clientConfig.recipient, + 0 + ); if (!clientCanReceiveEther) { - revert FeeDistributor__ClientCannotReceiveEther(_clientConfig.recipient); + revert FeeDistributor__ClientCannotReceiveEther( + _clientConfig.recipient + ); } - if (_referrerConfig.recipient != address(0)) {// if there is a referrer - bool referrerCanReceiveEther = P2pAddressLib._sendValue(_referrerConfig.recipient, 0); + if (_referrerConfig.recipient != address(0)) { + // if there is a referrer + bool referrerCanReceiveEther = P2pAddressLib._sendValue( + _referrerConfig.recipient, + 0 + ); if (!referrerCanReceiveEther) { - revert FeeDistributor__ReferrerCannotReceiveEther(_referrerConfig.recipient); + revert FeeDistributor__ReferrerCannotReceiveEther( + _referrerConfig.recipient + ); } } } @@ -156,7 +165,12 @@ abstract contract BaseFeeDistributor is Erc4337Account, OwnableTokenRecoverer, O } /// @inheritdoc IFeeDistributor - function client() public view override(Erc4337Account, IFeeDistributor) returns (address) { + function client() + public + view + override(Erc4337Account, IFeeDistributor) + returns (address) + { return s_clientConfig.recipient; } @@ -176,17 +190,31 @@ abstract contract BaseFeeDistributor is Erc4337Account, OwnableTokenRecoverer, O } /// @inheritdoc ERC165 - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IFeeDistributor).interfaceId || super.supportsInterface(interfaceId); + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IFeeDistributor).interfaceId || + super.supportsInterface(interfaceId); } /// @inheritdoc IOwnable - function owner() public view override(Erc4337Account, OwnableBase, Ownable, IOwnable) returns (address) { + function owner() + public + view + override(Erc4337Account, OwnableBase, Ownable, IOwnable) + returns (address) + { return i_factory.owner(); } /// @inheritdoc IOwnableWithOperator - function operator() public view override(Erc4337Account, OwnableWithOperator) returns (address) { + function operator() + public + view + override(Erc4337Account, OwnableWithOperator) + returns (address) + { return super.operator(); } } diff --git a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol new file mode 100644 index 0000000..8f4cebd --- /dev/null +++ b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2024 P2P Validator +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import "../@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "../@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "../@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import "../feeDistributorFactory/IFeeDistributorFactory.sol"; +import "../assetRecovering/OwnableTokenRecoverer.sol"; +import "./IFeeDistributor.sol"; +import "../structs/P2pStructs.sol"; +import "./BaseFeeDistributor.sol"; + +/// @title FeeDistributor +contract DeoracleizedFeeDistributor is BaseFeeDistributor { + /// @dev Set values that are constant, common for all the clients, known at the initial deploy time. + /// @param _factory address of FeeDistributorFactory + /// @param _service address of the service (P2P) fee recipient + constructor( + address _factory, + address payable _service + ) BaseFeeDistributor(_factory, _service) {} + + /// @notice Withdraw + function withdraw( + uint256 _clientAmount, + uint256 _serviceAmount, + uint256 _referrerAmount + ) external nonReentrant { + i_factory.checkOperatorOrOwner(msg.sender); + + if (s_clientConfig.recipient == address(0)) { + revert FeeDistributor__ClientNotSet(); + } + + if (address(this).balance == 0) { + // revert if there is no ether to withdraw + revert FeeDistributor__NothingToWithdraw(); + } + + if (s_referrerConfig.recipient != address(0)) { + // if there is a referrer + + // Send ETH to referrer. Ignore the possible yet unlikely revert in the receive function. + P2pAddressLib._sendValue( + s_referrerConfig.recipient, + _referrerAmount + ); + } + + // Send ETH to service. Ignore the possible yet unlikely revert in the receive function. + P2pAddressLib._sendValue(i_service, _serviceAmount); + + // Send ETH to client. Ignore the possible yet unlikely revert in the receive function. + P2pAddressLib._sendValue(s_clientConfig.recipient, _clientAmount); + + emit FeeDistributor__Withdrawn( + _serviceAmount, + _clientAmount, + _referrerAmount + ); + } +} diff --git a/contracts/feeDistributor/FeeDistributorErrors.sol b/contracts/feeDistributor/FeeDistributorErrors.sol index 5e17768..d4c8437 100644 --- a/contracts/feeDistributor/FeeDistributorErrors.sol +++ b/contracts/feeDistributor/FeeDistributorErrors.sol @@ -19,18 +19,6 @@ error FeeDistributor__ClientAddressEqualsService(address _passedAddress); /// @notice Client address should be an actual client address, not zero. error FeeDistributor__ZeroAddressClient(); -/// @notice Client basis points should be >= 0 and <= 10000 -/// @param _clientBasisPoints passed incorrect client basis points -error FeeDistributor__InvalidClientBasisPoints(uint96 _clientBasisPoints); - -/// @notice Referrer basis points should be > 0 if the referrer exists -error FeeDistributor__ZeroReferrerBasisPointsForNonZeroReferrer(); - -/// @notice The sum of (Client basis points + Referral basis points) should be >= 0 and <= 10000 -/// @param _clientBasisPoints passed client basis points -/// @param _referralBasisPoints passed referral basis points -error FeeDistributor__ClientPlusReferralBasisPointsExceed10000(uint96 _clientBasisPoints, uint96 _referralBasisPoints); - /// @notice Referrer address should be different from service address. /// @param _passedAddress passed referrer address that equals to the service address error FeeDistributor__ReferrerAddressEqualsService(address _passedAddress); @@ -42,7 +30,10 @@ error FeeDistributor__ReferrerAddressEqualsClient(address _passedAddress); /// @notice Only factory can call `initialize`. /// @param _msgSender sender address. /// @param _actualFactory the actual factory address that can call `initialize`. -error FeeDistributor__NotFactoryCalled(address _msgSender, IFeeDistributorFactory _actualFactory); +error FeeDistributor__NotFactoryCalled( + address _msgSender, + IFeeDistributorFactory _actualFactory +); /// @notice `initialize` should only be called once. /// @param _existingClient address of the client with which the contact has already been initialized. @@ -52,10 +43,6 @@ error FeeDistributor__ClientAlreadySet(address _existingClient); /// @dev The client address is supposed to be set by the factory. error FeeDistributor__ClientNotSet(); -/// @notice basisPoints of the referrer must be zero if referrer address is empty. -/// @param _referrerBasisPoints basisPoints of the referrer. -error FeeDistributor__ReferrerBasisPointsMustBeZeroIfAddressIsZero(uint96 _referrerBasisPoints); - /// @notice service should be able to receive ether. /// @param _service address of the service. error FeeDistributor__ServiceCannotReceiveEther(address _service); @@ -79,10 +66,7 @@ error FeeDistributor__CallerNotClient(address _caller, address _client); /// @notice Throws in case there was some ether left after `withdraw` and it has failed to recover. /// @param _to destination address for ether. /// @param _amount how much wei the destination address should have received, but didn't. -error FeeDistributor__EtherRecoveryFailed( - address _to, - uint256 _amount -); +error FeeDistributor__EtherRecoveryFailed(address _to, uint256 _amount); /// @notice ETH receiver should not be a zero address error FeeDistributor__ZeroAddressEthReceiver(); diff --git a/foundry.toml b/foundry.toml index c44021c..da2776b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,6 +9,7 @@ evm_version = 'cancun' via_ir = true optimizer = true optimizer-runs = 200 +gas_limit = "18446744073709551615" rpc_endpoints = { mainnet = "https://rpc.ankr.com/eth", holesky = "https://rpc.ankr.com/eth_holesky" } diff --git a/package.json b/package.json index 8024f54..8c0f87a 100644 --- a/package.json +++ b/package.json @@ -48,5 +48,6 @@ }, "dependencies": { "@google-cloud/bigquery": "6.2.0" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } From 37aa2cbf1a240527412ffc752dc4112db6f87474 Mon Sep 17 00:00:00 2001 From: Alexander Biryukov Date: Tue, 24 Sep 2024 16:42:20 +0400 Subject: [PATCH 2/5] test Deoracleized --- .../DeoracleizedFeeDistributor.sol | 41 ++- .../feeDistributor/FeeDistributorErrors.sol | 10 + test/foundry/Deoracleized.t.sol | 273 ++++++++++++++++++ 3 files changed, 313 insertions(+), 11 deletions(-) create mode 100644 test/foundry/Deoracleized.t.sol diff --git a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol index 8f4cebd..c9d4585 100644 --- a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol +++ b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol @@ -39,21 +39,40 @@ contract DeoracleizedFeeDistributor is BaseFeeDistributor { revert FeeDistributor__NothingToWithdraw(); } - if (s_referrerConfig.recipient != address(0)) { - // if there is a referrer + if ( + _clientAmount + _serviceAmount + _referrerAmount > + address(this).balance + ) { + revert FeeDistributor__AmountsExceedBalance(); + } + + if (_clientAmount == _serviceAmount == _referrerAmount == 0) { + revert FeeDistributor__AmountsAreZero(); + } - // Send ETH to referrer. Ignore the possible yet unlikely revert in the receive function. - P2pAddressLib._sendValue( - s_referrerConfig.recipient, - _referrerAmount - ); + if (_referrerAmount > 0) { + if (s_referrerConfig.recipient != address(0)) { + // if there is a referrer + + // Send ETH to referrer. Ignore the possible yet unlikely revert in the receive function. + P2pAddressLib._sendValue( + s_referrerConfig.recipient, + _referrerAmount + ); + } else { + revert FeeDistributor__ReferrerNotSet(); + } } - // Send ETH to service. Ignore the possible yet unlikely revert in the receive function. - P2pAddressLib._sendValue(i_service, _serviceAmount); + if (_serviceAmount > 0) { + // Send ETH to service. Ignore the possible yet unlikely revert in the receive function. + P2pAddressLib._sendValue(i_service, _serviceAmount); + } - // Send ETH to client. Ignore the possible yet unlikely revert in the receive function. - P2pAddressLib._sendValue(s_clientConfig.recipient, _clientAmount); + if (_clientAmount > 0) { + // Send ETH to client. Ignore the possible yet unlikely revert in the receive function. + P2pAddressLib._sendValue(s_clientConfig.recipient, _clientAmount); + } emit FeeDistributor__Withdrawn( _serviceAmount, diff --git a/contracts/feeDistributor/FeeDistributorErrors.sol b/contracts/feeDistributor/FeeDistributorErrors.sol index d4c8437..1d6d661 100644 --- a/contracts/feeDistributor/FeeDistributorErrors.sol +++ b/contracts/feeDistributor/FeeDistributorErrors.sol @@ -43,6 +43,16 @@ error FeeDistributor__ClientAlreadySet(address _existingClient); /// @dev The client address is supposed to be set by the factory. error FeeDistributor__ClientNotSet(); +/// @notice Cannot call `withdraw` if the referrer address is not set yet. +/// @dev The referrer address is supposed to be set by the factory. +error FeeDistributor__ReferrerNotSet(); + +/// @notice Sum of client, service and referrer amounts exceeds balance. +error FeeDistributor__AmountsExceedBalance(); + +/// @notice All amounts are zero. +error FeeDistributor__AmountsAreZero(); + /// @notice service should be able to receive ether. /// @param _service address of the service. error FeeDistributor__ServiceCannotReceiveEther(address _service); diff --git a/test/foundry/Deoracleized.t.sol b/test/foundry/Deoracleized.t.sol new file mode 100644 index 0000000..6ca61aa --- /dev/null +++ b/test/foundry/Deoracleized.t.sol @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2024 P2P Validator +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import "forge-std/Test.sol"; +import "forge-std/Vm.sol"; +import "forge-std/console.sol"; +import "forge-std/console2.sol"; +import "../../contracts/p2pEth2Depositor/P2pOrgUnlimitedEthDepositor.sol"; +import "../../contracts/feeDistributorFactory/FeeDistributorFactory.sol"; +import "../../contracts/feeDistributor/DeoracleizedFeeDistributor.sol"; +import "../../contracts/structs/P2pStructs.sol"; + +contract Deoracleized is Test { + bytes pubKey; + bytes signature; + bytes32 depositDataRoot; + + bytes[] pubKeys; + bytes[] signatures; + bytes32[] depositDataRoots; + + FeeDistributorFactory constant factory = + FeeDistributorFactory(0xecA6e48C44C7c0cAf4651E5c5089e564031E8b90); + P2pOrgUnlimitedEthDepositor constant p2pEthDepositor = + P2pOrgUnlimitedEthDepositor(0x23BE839a14cEc3D6D716D904f09368Bbf9c750eb); + DeoracleizedFeeDistributor deoracleizedFeeDistributorTemplate; + DeoracleizedFeeDistributor deoracleizedFeeDistributorInstance; + + address constant p2pDeployerAddress = + 0x588ede4403DF0082C5ab245b35F0f79EB2d8033a; + address constant extraSecureP2pAddress = + 0xb0d0f9e74e15345D9E618C6f4Ca1C9Cb061C613A; + address constant clientDepositorAddress = + 0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8; + address payable constant clientWcAddress = + payable(0xB3E84B6C6409826DC45432B655D8C9489A14A0D7); + address payable constant serviceAddress = + payable(0x6Bb8b45a1C6eA816B70d76f83f7dC4f0f87365Ff); + + uint96 constant defaultClientBasisPoints = 9000; + uint256 constant clientDepositedEth = 320000 ether; + + address private operatorAddress; + uint256 private operatorPrivateKey; + + bytes32 constant withdrawalCredentials_01 = + 0x010000000000000000000000B3E84B6C6409826DC45432B655D8C9489A14A0D7; + bytes32 depositId_32_01; + + FeeRecipient clientFeeRecipientDefault = + FeeRecipient({ + recipient: clientWcAddress, + basisPoints: defaultClientBasisPoints + }); + FeeRecipient referrerFeeRecipientDefault = + FeeRecipient({recipient: payable(address(0)), basisPoints: 0}); + + function setUp() public { + vm.createSelectFork("mainnet", 20819000); + + (operatorAddress, operatorPrivateKey) = makeAddrAndKey("operator"); + + vm.startPrank(p2pDeployerAddress); + deoracleizedFeeDistributorTemplate = new DeoracleizedFeeDistributor( + address(factory), + serviceAddress + ); + vm.stopPrank(); + + checkOwnership(); + setOperator(); + setOwner(); + depositId_32_01 = p2pEthDepositor.getDepositId( + withdrawalCredentials_01, + 32 ether, + address(deoracleizedFeeDistributorTemplate), + clientFeeRecipientDefault, + referrerFeeRecipientDefault + ); + + pubKey = bytes( + hex"87f08e27a19e0d15764838e3af5c33645545610f268c2dadba3c2c789e2579a5d5300a3d72c6fb5fce4e9aa1c2f32d40" + ); + signature = bytes( + hex"816597afd6c13068692512ed57e7c6facde10be01b247c58d67f15e3716ec7eb9856d28e25e1375ab526b098fdd3094405435a9bf7bf95369697365536cb904f0ae4f8da07f830ae1892182e318588ce8dd6220be2145f6c29d28e0d57040d42" + ); + depositDataRoot = bytes32( + hex"34b7017543befa837eb0af8a32b2c6e543b1d869ff526680c9d59291b742d5b7" + ); + for (uint256 i = 0; i < VALIDATORS_MAX_AMOUNT; i++) { + pubKeys.push(pubKey); + signatures.push(signature); + depositDataRoots.push(depositDataRoot); + } + } + + function test_Main_Use_Case_Deoracleized() public { + console.log("test_Main_Use_Case_Deoracleized started"); + + addEthToDeoracleizedFeeDistributor(); + makeBeaconDepositForDeoracleizedFeeDistributor(); + withdrawDeoracleizedFeeDistributor(); + + console.log("test_Main_Use_Case_Deoracleized finished"); + } + + function withdrawDeoracleizedFeeDistributor() private { + console.log("withdrawDeoracleizedFeeDistributor"); + + uint256 clientAmount = 9 ether; + uint256 serviceAmount = 0.6 ether; + uint256 referrerAmount = 0.4 ether; + + uint256 elRewards = clientAmount + serviceAmount + referrerAmount; + + vm.deal(address(deoracleizedFeeDistributorInstance), elRewards); + + uint256 serviceBalanceBefore = serviceAddress.balance; + uint256 clientBalanceBefore = clientWcAddress.balance; + + vm.expectRevert( + abi.encodeWithSelector( + Access__CallerNeitherOperatorNorOwner.selector, + address(this), + operatorAddress, + extraSecureP2pAddress + ) + ); + deoracleizedFeeDistributorInstance.withdraw( + clientAmount, + serviceAmount, + referrerAmount + ); + + vm.startPrank(operatorAddress); + deoracleizedFeeDistributorInstance.withdraw( + clientAmount, + serviceAmount, + referrerAmount + ); + vm.stopPrank(); + + uint256 serviceBalanceAfter = serviceAddress.balance; + uint256 clientBalanceAfter = clientWcAddress.balance; + + assertEq(serviceBalanceAfter - serviceBalanceBefore, serviceAmount); + assertEq(clientBalanceAfter - clientBalanceBefore, clientAmount); + } + + function makeBeaconDepositForDeoracleizedFeeDistributor() private { + console.log("makeBeaconDepositForDeoracleizedFeeDistributor"); + + vm.startPrank(operatorAddress); + + uint256 balanceBefore = p2pEthDepositor.totalBalance(); + + assertEq( + p2pEthDepositor.depositAmount(depositId_32_01), + clientDepositedEth + ); + + p2pEthDepositor.makeBeaconDeposit( + withdrawalCredentials_01, + MIN_ACTIVATION_BALANCE, + address(deoracleizedFeeDistributorInstance), + pubKeys, + signatures, + depositDataRoots + ); + + uint256 balanceAfter = balanceBefore - + MIN_ACTIVATION_BALANCE * + VALIDATORS_MAX_AMOUNT; + assertEq(p2pEthDepositor.totalBalance(), balanceAfter); + assertEq( + p2pEthDepositor.depositAmount(depositId_32_01), + clientDepositedEth - MIN_ACTIVATION_BALANCE * VALIDATORS_MAX_AMOUNT + ); + + vm.stopPrank(); + } + + function checkOwnership() private { + console.log("checkOwnership"); + + assertEq(factory.owner(), p2pDeployerAddress); + assertEq( + deoracleizedFeeDistributorTemplate.owner(), + p2pDeployerAddress + ); + } + + function setOperator() private { + console.log("setOperator"); + + vm.startPrank(p2pDeployerAddress); + + assertTrue(factory.operator() != operatorAddress); + factory.changeOperator(operatorAddress); + assertEq(factory.operator(), operatorAddress); + + vm.stopPrank(); + } + + function setOwner() private { + console.log("setOwner"); + + vm.startPrank(p2pDeployerAddress); + assertTrue(factory.owner() != extraSecureP2pAddress); + factory.transferOwnership(extraSecureP2pAddress); + assertTrue(factory.owner() != extraSecureP2pAddress); + vm.startPrank(extraSecureP2pAddress); + factory.acceptOwnership(); + assertEq(factory.owner(), extraSecureP2pAddress); + assertEq( + deoracleizedFeeDistributorTemplate.owner(), + extraSecureP2pAddress + ); + vm.stopPrank(); + } + + function addEthToDeoracleizedFeeDistributor() private { + console.log("addEthToDeoracleizedFeeDistributor"); + + vm.startPrank(clientDepositorAddress); + + assertTrue(address(deoracleizedFeeDistributorInstance) == address(0)); + + uint256 totalBalanceBefore = p2pEthDepositor.totalBalance(); + + (, address feeDistributorInstance) = p2pEthDepositor.addEth{ + value: clientDepositedEth + }( + withdrawalCredentials_01, + MIN_ACTIVATION_BALANCE, + address(deoracleizedFeeDistributorTemplate), + clientFeeRecipientDefault, + referrerFeeRecipientDefault, + "" + ); + + deoracleizedFeeDistributorInstance = DeoracleizedFeeDistributor( + payable(feeDistributorInstance) + ); + + uint256 totalBalanceAfter = p2pEthDepositor.totalBalance(); + + assertTrue(address(deoracleizedFeeDistributorInstance) != address(0)); + assertEq(totalBalanceAfter - totalBalanceBefore, clientDepositedEth); + + vm.stopPrank(); + + vm.expectRevert("ERC1167: create2 failed"); + deployDeoracleizedFeeDistributorCreationWithoutDepositor(); + } + + function deployDeoracleizedFeeDistributorCreationWithoutDepositor() + private + returns (address newFeeDistributorAddress) + { + vm.startPrank(operatorAddress); + + newFeeDistributorAddress = factory.createFeeDistributor( + address(deoracleizedFeeDistributorTemplate), + clientFeeRecipientDefault, + referrerFeeRecipientDefault + ); + + vm.stopPrank(); + } +} From b39d6765da6ab7c3d38c249c898e0a39d625140b Mon Sep 17 00:00:00 2001 From: Alexander Biryukov Date: Wed, 25 Sep 2024 15:02:33 +0400 Subject: [PATCH 3/5] test_Main_Use_Case_Deoracleized --- .../feeDistributor/DeoracleizedFeeDistributor.sol | 2 +- test/foundry/Deoracleized.t.sol | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol index c9d4585..63999bc 100644 --- a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol +++ b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol @@ -46,7 +46,7 @@ contract DeoracleizedFeeDistributor is BaseFeeDistributor { revert FeeDistributor__AmountsExceedBalance(); } - if (_clientAmount == _serviceAmount == _referrerAmount == 0) { + if (_clientAmount + _serviceAmount + _referrerAmount == 0) { revert FeeDistributor__AmountsAreZero(); } diff --git a/test/foundry/Deoracleized.t.sol b/test/foundry/Deoracleized.t.sol index 6ca61aa..500bdd3 100644 --- a/test/foundry/Deoracleized.t.sol +++ b/test/foundry/Deoracleized.t.sol @@ -24,7 +24,9 @@ contract Deoracleized is Test { FeeDistributorFactory constant factory = FeeDistributorFactory(0xecA6e48C44C7c0cAf4651E5c5089e564031E8b90); P2pOrgUnlimitedEthDepositor constant p2pEthDepositor = - P2pOrgUnlimitedEthDepositor(0x23BE839a14cEc3D6D716D904f09368Bbf9c750eb); + P2pOrgUnlimitedEthDepositor( + payable(0x23BE839a14cEc3D6D716D904f09368Bbf9c750eb) + ); DeoracleizedFeeDistributor deoracleizedFeeDistributorTemplate; DeoracleizedFeeDistributor deoracleizedFeeDistributorInstance; @@ -122,7 +124,7 @@ contract Deoracleized is Test { vm.expectRevert( abi.encodeWithSelector( - Access__CallerNeitherOperatorNorOwner.selector, + Access__AddressNeitherOperatorNorOwner.selector, address(this), operatorAddress, extraSecureP2pAddress @@ -135,11 +137,18 @@ contract Deoracleized is Test { ); vm.startPrank(operatorAddress); + vm.expectRevert(FeeDistributor__ReferrerNotSet.selector); deoracleizedFeeDistributorInstance.withdraw( clientAmount, serviceAmount, referrerAmount ); + + deoracleizedFeeDistributorInstance.withdraw( + clientAmount, + serviceAmount, + 0 + ); vm.stopPrank(); uint256 serviceBalanceAfter = serviceAddress.balance; From 9aabcd80485d07fbaa474aafc32a8ed8214aaae1 Mon Sep 17 00:00:00 2001 From: Alexander Biryukov Date: Wed, 25 Sep 2024 15:32:47 +0400 Subject: [PATCH 4/5] add struct Withdrawal --- .../DeoracleizedFeeDistributor.sol | 32 +++++++++---------- contracts/structs/P2pStructs.sol | 10 ++++++ test/foundry/Deoracleized.t.sol | 30 ++++++++++------- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol index 63999bc..c24e5ab 100644 --- a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol +++ b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol @@ -23,11 +23,7 @@ contract DeoracleizedFeeDistributor is BaseFeeDistributor { ) BaseFeeDistributor(_factory, _service) {} /// @notice Withdraw - function withdraw( - uint256 _clientAmount, - uint256 _serviceAmount, - uint256 _referrerAmount - ) external nonReentrant { + function withdraw(Withdrawal calldata _withdrawal) external nonReentrant { i_factory.checkOperatorOrOwner(msg.sender); if (s_clientConfig.recipient == address(0)) { @@ -39,45 +35,49 @@ contract DeoracleizedFeeDistributor is BaseFeeDistributor { revert FeeDistributor__NothingToWithdraw(); } + uint256 clientAmount = uint256(_withdrawal.clientAmount); + uint256 serviceAmount = uint256(_withdrawal.serviceAmount); + uint256 referrerAmount = uint256(_withdrawal.referrerAmount); + if ( - _clientAmount + _serviceAmount + _referrerAmount > + clientAmount + serviceAmount + referrerAmount > address(this).balance ) { revert FeeDistributor__AmountsExceedBalance(); } - if (_clientAmount + _serviceAmount + _referrerAmount == 0) { + if (clientAmount + serviceAmount + referrerAmount == 0) { revert FeeDistributor__AmountsAreZero(); } - if (_referrerAmount > 0) { + if (referrerAmount > 0) { if (s_referrerConfig.recipient != address(0)) { // if there is a referrer // Send ETH to referrer. Ignore the possible yet unlikely revert in the receive function. P2pAddressLib._sendValue( s_referrerConfig.recipient, - _referrerAmount + referrerAmount ); } else { revert FeeDistributor__ReferrerNotSet(); } } - if (_serviceAmount > 0) { + if (serviceAmount > 0) { // Send ETH to service. Ignore the possible yet unlikely revert in the receive function. - P2pAddressLib._sendValue(i_service, _serviceAmount); + P2pAddressLib._sendValue(i_service, serviceAmount); } - if (_clientAmount > 0) { + if (clientAmount > 0) { // Send ETH to client. Ignore the possible yet unlikely revert in the receive function. - P2pAddressLib._sendValue(s_clientConfig.recipient, _clientAmount); + P2pAddressLib._sendValue(s_clientConfig.recipient, clientAmount); } emit FeeDistributor__Withdrawn( - _serviceAmount, - _clientAmount, - _referrerAmount + serviceAmount, + clientAmount, + referrerAmount ); } } diff --git a/contracts/structs/P2pStructs.sol b/contracts/structs/P2pStructs.sol index 2219275..d96c2f1 100644 --- a/contracts/structs/P2pStructs.sol +++ b/contracts/structs/P2pStructs.sol @@ -50,3 +50,13 @@ struct ClientDeposit { ClientDepositStatus status; uint96 ethAmountPerValidatorInWei; } + +/// @dev 256 bit struct +/// @member clientAmount ETH in wei to be sent to the client +/// @member serviceAmount ETH in wei to be sent to the service +/// @member referrerAmount ETH in wei to be sent to the referrer +struct Withdrawal { + uint80 clientAmount; + uint80 serviceAmount; + uint80 referrerAmount; +} diff --git a/test/foundry/Deoracleized.t.sol b/test/foundry/Deoracleized.t.sol index 500bdd3..03b0a3b 100644 --- a/test/foundry/Deoracleized.t.sol +++ b/test/foundry/Deoracleized.t.sol @@ -111,9 +111,9 @@ contract Deoracleized is Test { function withdrawDeoracleizedFeeDistributor() private { console.log("withdrawDeoracleizedFeeDistributor"); - uint256 clientAmount = 9 ether; - uint256 serviceAmount = 0.6 ether; - uint256 referrerAmount = 0.4 ether; + uint80 clientAmount = 9 ether; + uint80 serviceAmount = 0.6 ether; + uint80 referrerAmount = 0.4 ether; uint256 elRewards = clientAmount + serviceAmount + referrerAmount; @@ -131,23 +131,29 @@ contract Deoracleized is Test { ) ); deoracleizedFeeDistributorInstance.withdraw( - clientAmount, - serviceAmount, - referrerAmount + Withdrawal({ + clientAmount: clientAmount, + serviceAmount: serviceAmount, + referrerAmount: referrerAmount + }) ); vm.startPrank(operatorAddress); vm.expectRevert(FeeDistributor__ReferrerNotSet.selector); deoracleizedFeeDistributorInstance.withdraw( - clientAmount, - serviceAmount, - referrerAmount + Withdrawal({ + clientAmount: clientAmount, + serviceAmount: serviceAmount, + referrerAmount: referrerAmount + }) ); deoracleizedFeeDistributorInstance.withdraw( - clientAmount, - serviceAmount, - 0 + Withdrawal({ + clientAmount: clientAmount, + serviceAmount: serviceAmount, + referrerAmount: 0 + }) ); vm.stopPrank(); From 88bc52c199074f884e12ed847b18d41413d48ac3 Mon Sep 17 00:00:00 2001 From: Alexander Biryukov Date: Wed, 25 Sep 2024 16:26:01 +0400 Subject: [PATCH 5/5] deploy --- .../DeoracleizedFeeDistributor.sol | 5 +++ package.json | 4 +-- script/Deploy.s.sol | 34 ++++++------------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol index c24e5ab..b6045de 100644 --- a/contracts/feeDistributor/DeoracleizedFeeDistributor.sol +++ b/contracts/feeDistributor/DeoracleizedFeeDistributor.sol @@ -80,4 +80,9 @@ contract DeoracleizedFeeDistributor is BaseFeeDistributor { referrerAmount ); } + + /// @inheritdoc Erc4337Account + function withdrawSelector() public pure override returns (bytes4) { + return DeoracleizedFeeDistributor.withdraw.selector; + } } diff --git a/package.json b/package.json index 8c0f87a..507be9d 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,10 @@ "sendErc4337UserOperation": "yarn hardhat run --network holesky scripts/sendErc4337UserOperation.ts", "sendErc4337UserOperationForOracleFeeDistributor": "yarn hardhat run --network holesky scripts/sendErc4337UserOperationForOracleFeeDistributor.ts", "generateMockMerkleTree": "yarn hardhat run --network holesky scripts/generateMockMerkleTree.ts", - "deployWithForge": "forge script script/Deploy.s.sol:Deploy --rpc-url $GOERLI_RPC_URL --private-key $PRIVATE_KEY --broadcast --chain holesky --json --verify --etherscan-api-key $ETHERSCAN_API_KEY -vvvv" + "deployWithForge": "forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --chain holesky --json --verify --etherscan-api-key $ETHERSCAN_API_KEY -vvvvv" }, "dependencies": { "@google-cloud/bigquery": "6.2.0" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} +} \ No newline at end of file diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 6caf16d..8ca98cb 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -4,36 +4,24 @@ pragma solidity 0.8.24; import {Script} from "forge-std/Script.sol"; -import "../contracts/oracle/Oracle.sol"; -import "../contracts/feeDistributorFactory/FeeDistributorFactory.sol"; -import "../contracts/feeDistributor/OracleFeeDistributor.sol"; -import "../contracts/p2pEth2Depositor/P2pOrgUnlimitedEthDepositor.sol"; +import "../contracts/feeDistributor/DeoracleizedFeeDistributor.sol"; contract Deploy is Script { - uint96 constant defaultClientBasisPoints = 9000; - address payable constant serviceAddress = payable(0x6Bb8b45a1C6eA816B70d76f83f7dC4f0f87365Ff); - address constant oracleAddress = 0x4E67DfF29304075A383D877F0BA760b94FE38803; + address payable constant serviceAddress = + payable(0x6Bb8b45a1C6eA816B70d76f83f7dC4f0f87365Ff); + address constant factoryAddress = + 0x5cdF046Bd49629E5130a4A82400733523Ba5820C; - function run() external returns ( - FeeDistributorFactory, - OracleFeeDistributor, - P2pOrgUnlimitedEthDepositor - ) { + function run() external returns (DeoracleizedFeeDistributor) { uint256 deployerKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerKey); - - Oracle oracle = new Oracle(); - FeeDistributorFactory factory = new FeeDistributorFactory(defaultClientBasisPoints); - OracleFeeDistributor oracleFeeDistributorTemplate = new OracleFeeDistributor(address(oracle), address(factory), serviceAddress); - P2pOrgUnlimitedEthDepositor p2pEthDepositor = new P2pOrgUnlimitedEthDepositor(address(factory)); - factory.setP2pEth2Depositor(address(p2pEthDepositor)); + DeoracleizedFeeDistributor deoracleizedFeeDistributorTemplate = new DeoracleizedFeeDistributor( + factoryAddress, + serviceAddress + ); vm.stopBroadcast(); - return ( - factory, - oracleFeeDistributorTemplate, - p2pEthDepositor - ); + return (deoracleizedFeeDistributorTemplate); } }