From 240c19855a2d040aa5d2650581e8e859ed2cb9f9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 7 Nov 2024 15:03:04 +0100 Subject: [PATCH 1/3] EIP-7251: return fee from getter and add usage example The get operation is meant to be used by contracts to compute the exact amount of ether required to add a request. It does not return the fee directly though, but it returns the count of 'excess requests' instead. The caller has to compute the fee themselves by applying the fee formula. I think this is not great. The fee logic, while reasonably straightforward, is an implementation detail of the contract. Duplicating it into caller contracts could lead to a mismatch in the computed values, and it's not necessary. I propose we change the system contract to return the fee directly. This contract change has also been submitted in this PR: https://github.com/lightclient/sys-asm/pull/33 The Rationale section of the EIP also had some outdated text about returning fee overage to the caller. The contract does not return overage, so I am removing that section here, and adding recommendations & example code for calling the contract. --- EIPS/eip-7251.md | 98 ++++++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/EIPS/eip-7251.md b/EIPS/eip-7251.md index 3a995d59ce54d..e431e4e9ae26a 100644 --- a/EIPS/eip-7251.md +++ b/EIPS/eip-7251.md @@ -135,13 +135,9 @@ def fake_exponential(factor: int, numerator: int, denominator: int) -> int: return output // denominator ``` -##### Excess Consolidation Requests Getter +##### Fee Getter -```python -def get_excess_consolidation_requests(): - count = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT) - return count -``` +When the input to the contract is length zero, interpret this as a get request for the current fee, i.e. the contract returns the result of `get_fee()`. ##### System Call @@ -233,28 +229,6 @@ eq push1 0xcf jumpi -calldatasize -iszero -iszero -push1 0x28 -jumpi - -push0 -sload -push0 -mstore -push1 0x20 -push0 -return - -jumpdest -calldatasize -push1 0x60 -eq -iszero -push2 0x019a -jumpi - push1 0x11 push0 sload @@ -276,7 +250,7 @@ push0 dup3 gt iszero -push1 0x80 +push1 0x68 jumpi dup2 @@ -294,7 +268,7 @@ push1 0x01 add swap2 swap1 -push1 0x65 +push1 0x4d jump jumpdest @@ -302,6 +276,27 @@ swap1 swap4 swap1 div +calldatasize +push1 0x60 +eq +push1 0x84 +jumpi + +calldatasize +push2 0x019a +jumpi + +callvalue +push2 0x019a +jumpi + +push0 +mstore +push1 0x20 +push0 +return + +jumpdest callvalue lt push2 0x019a @@ -386,11 +381,8 @@ eq push2 0x0129 jumpi -dup1 -push1 0x74 -mul -dup4 dup3 +dup2 add push1 0x04 mul @@ -412,12 +404,15 @@ swap1 push1 0x01 add sload -swap3 +dup5 +push1 0x74 +mul +swap4 push1 0x60 shl dup5 mstore -swap1 +swap2 dup4 push1 0x14 add @@ -472,8 +467,8 @@ jumpi pop push0 - jumpdest + push1 0x01 sload push1 0x01 @@ -621,6 +616,35 @@ Sync committee selection is also already weighted by effective balance, so this This proposal maintains the activation and exit churn invariants limiting active weight instead of validator count. Balance top-ups are now handled explicitly, being subject to the same activation queue as full deposits. +### Fee Overpayment + +Calls to the system contract require a fee payment defined by the current contract state. Overpaid fees are not returned to the caller. It is not generally possible to compute the exact required fee amount ahead of time. When adding a withdrawal request from a contract, the contract can perform a read operation to check for the current fee and then pay exactly the required amount. Here is an example in Solidity: + +``` +function addConsolidation(bytes memory srcPubkey, bytes memory targetPubkey) private { + assert(srcPubkey.length == 48); + assert(targetPubkey.length == 48); + + // Read current fee from the contract. + (bool readOK, bytes memory feeData) = ConsolidationsContract.staticcall(''); + if (!readOK) { + revert('reading fee failed'); + } + uint256 fee = uint256(bytes32(feeData)); + + // Add the request. + bytes memory callData = bytes.concat(srcPubkey, targetPubkey); + (bool writeOK,) = ConsolidationsContract.call{value: fee}(callData); + if (!writeOK) { + revert('adding request failed'); + } +} +``` + +Note: the system contract uses the EVM `CALLER` operation (Solidity: `msg.sender`) to get the address used in the consolidation request, i.e. the address that calls the system contract must match the 0x01 withdrawal credential recorded in the beacon state. + +Using an EOA to request consolidations will always result in overpayment of fees. There is no way for an EOA to use a wrapper contract to request a consolidation. And even if a way existed, the gas cost of returning the overage would likely be higher than the overage itself. If requesting consolidations from an EOA to the system contract is desired, we recommend that users perform transaction simulations to estimate a reasonable fee amount to sent. + ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). From 8f540512c15d3b91c5f4fe8724d7a6ba1fdb1d72 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 7 Nov 2024 16:08:29 +0100 Subject: [PATCH 2/3] EIP-7241: fix typo --- EIPS/eip-7251.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7251.md b/EIPS/eip-7251.md index e431e4e9ae26a..56b7c742a5f36 100644 --- a/EIPS/eip-7251.md +++ b/EIPS/eip-7251.md @@ -643,7 +643,7 @@ function addConsolidation(bytes memory srcPubkey, bytes memory targetPubkey) pri Note: the system contract uses the EVM `CALLER` operation (Solidity: `msg.sender`) to get the address used in the consolidation request, i.e. the address that calls the system contract must match the 0x01 withdrawal credential recorded in the beacon state. -Using an EOA to request consolidations will always result in overpayment of fees. There is no way for an EOA to use a wrapper contract to request a consolidation. And even if a way existed, the gas cost of returning the overage would likely be higher than the overage itself. If requesting consolidations from an EOA to the system contract is desired, we recommend that users perform transaction simulations to estimate a reasonable fee amount to sent. +Using an EOA to request consolidations will always result in overpayment of fees. There is no way for an EOA to use a wrapper contract to request a consolidation. And even if a way existed, the gas cost of returning the overage would likely be higher than the overage itself. If requesting consolidations from an EOA to the system contract is desired, we recommend that users perform transaction simulations to estimate a reasonable fee amount to send. ## Copyright From 09bb16956251886649773e0abefd475e264f2ac7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 7 Nov 2024 16:09:53 +0100 Subject: [PATCH 3/3] EIP-7241: fix typo --- EIPS/eip-7251.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7251.md b/EIPS/eip-7251.md index 56b7c742a5f36..19115f75d38bc 100644 --- a/EIPS/eip-7251.md +++ b/EIPS/eip-7251.md @@ -618,7 +618,7 @@ This proposal maintains the activation and exit churn invariants limiting active ### Fee Overpayment -Calls to the system contract require a fee payment defined by the current contract state. Overpaid fees are not returned to the caller. It is not generally possible to compute the exact required fee amount ahead of time. When adding a withdrawal request from a contract, the contract can perform a read operation to check for the current fee and then pay exactly the required amount. Here is an example in Solidity: +Calls to the system contract require a fee payment defined by the current contract state. Overpaid fees are not returned to the caller. It is not generally possible to compute the exact required fee amount ahead of time. When adding a consolidation request from a contract, the contract can perform a read operation to check for the current fee and then pay exactly the required amount. Here is an example in Solidity: ``` function addConsolidation(bytes memory srcPubkey, bytes memory targetPubkey) private {