From 962199e7731ffc8285e05a1362d499e4348e10d9 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 15 Nov 2023 17:14:03 +0100 Subject: [PATCH 01/14] first iteration --- bootloader/bootloader.yul | 308 +++++++++++++++++++++++++++----------- 1 file changed, 221 insertions(+), 87 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index eba58c9..cbbe15d 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -23,16 +23,74 @@ object "Bootloader" { /// @dev This method ensures that the prices provided by the operator /// are not absurdly high - function validateOperatorProvidedPrices(l1GasPrice, fairL2GasPrice) { + function validateOperatorProvidedPrices(l1GasPrice, fairL2GasPrice, pubdataPrice) { if gt(l1GasPrice, MAX_ALLOWED_L1_GAS_PRICE()) { assertionError("L1 gas price too high") } + // The limit is the same for pubdata price and L1 gas price + if gt(pubdataPrice, MAX_ALLOWED_L1_GAS_PRICE()) { + assertionError("Pubdata price too high") + } + if gt(fairL2GasPrice, MAX_ALLOWED_FAIR_L2_GAS_PRICE()) { assertionError("L2 fair gas price too high") } } + /// todo + function getFeeParams( + l1GasPrice, + pubdataPrice, + fairL2GasPrice, + // we allow operator to provide it because of validium support + // maxPubdataPerBatch, + // // we allow operator to provide it because of potentially better future values + // maxComputeGasPerBatch + ) { + // todo: think about the L1Messenger + // if lt(maxPubdataPerBatch, GUARANTEED_PUBDATA_PER_BATCH()) { + // assertionError("maxPubdataPerBatch too low") + // } + + // todo: enforce limit for maxComputeGasPerBatch if we decide that this field is at all needed. + // MAybe we say that `fairL2GasPrice` MUST take into account the proof verification cost + + let batchOverheadETH := getBatchOverheadEth(l1GasPrice, fairL2GasPrice) + // let pubdataPriceETH := safeAdd( + // pubdataPrice, + // // This is equivalent to safeDiv as maxPubdataPerBatch > 0 + // // Due to floor division in theory the operator might get underpaid, but + // // it is negligible + // div(batchOverheadETH, maxPubdataPerBatch), + // "ll" + // ) + + // includes the overhead cost + // let baseComputeCost := safeAdd( + // fairL2GasPrice, div(batchOverheadETH, maxComputeGasPerBatch) + // ) + let baseFee := max( + fairL2GasPrice, + ceilDiv(pubdataPrice, MAX_L2_GAS_PER_PUBDATA()) + ) + + let gasPricePerPubdata := ceilDiv(pubdataPrice, baseFee) + + // Per byte + let memoryOverheadGas := ceilDiv(batchOverheadETH, safeMul(BOOTLOADER_MEMORY_FOR_TXS(), baseFee, "opp")) + + // Per tx slot + let txSlotOverheadGas := ceilDiv(batchOverheadETH, safeMul(MAX_TRANSACTIONS_IN_BATCH(), baseFee, "opl")) + + // Save it all into memory + // todo: maybe use local vars + mstore(0, baseFee) + mstore(32, gasPricePerPubdata) + mstore(64, memoryOverheadGas) + mstore(96, txSlotOverheadGas) + } + /// @dev Returns the baseFee for this batch based on the /// L1 gas price and the fair L2 gas price. function getBaseFee(l1GasPrice, fairL2GasPrice) -> baseFee, gasPricePerPubdata { @@ -56,6 +114,12 @@ object "Bootloader" { ret := {{GUARANTEED_PUBDATA_BYTES}} } + /// todo + function GUARANTEED_PUBDATA_PER_BATCH() -> ret { + // todo: rename var + ret := {{MAX_PUBDATA_PER_BATCH}} + } + /// @dev The maximal gasPerPubdata, which allows users to still be /// able to send `GUARANTEED_PUBDATA_PER_TX` onchain. function MAX_L2_GAS_PER_PUBDATA() -> ret { @@ -125,6 +189,41 @@ object "Bootloader" { ret := 32 } + /// todo + function CACHED_FEE_PARAMS_SLOTS() -> ret { + ret := 2 + } + + /// todo + function CACHED_FEE_PARAMS_BEGIN_SLOT() -> ret { + ret := add(SCRATCH_SPACE_BEGIN_SLOT(), SCRATCH_SPACE_SLOTS()) + } + + /// todo + function CACHED_FEE_PARAMS_BEGIN_BYTE() -> ret { + ret := mul(CACHED_FEE_PARAMS_BEGIN_SLOT(), 32) + } + + /// todo + function SET_TX_SLOT_OVERHEAD(value) -> ret { + mstore(CACHED_FEE_PARAMS_BEGIN_BYTE(), value) + } + + /// todo + function GET_TX_SLOT_OVERHEAD() -> ret { + ret := mload(CACHED_FEE_PARAMS_BEGIN_BYTE()) + } + + /// todo + function SET_MEMORY_OVERHEAD(value) -> ret { + mstore(add(CACHED_FEE_PARAMS_BEGIN_BYTE(), 32), value) + } + + /// todo + function GET_MEMORY_OVERHEAD() -> ret { + ret := mload(add(CACHED_FEE_PARAMS_BEGIN_BYTE(), 32)) + } + /// @dev Slots reserved for saving the paymaster context /// @dev The paymasters are allowed to consume at most /// 32 slots (1024 bytes) for their context. @@ -140,7 +239,7 @@ object "Bootloader" { /// @dev Slot from which the paymaster context starts function PAYMASTER_CONTEXT_BEGIN_SLOT() -> ret { - ret := add(SCRATCH_SPACE_BEGIN_SLOT(), SCRATCH_SPACE_SLOTS()) + ret := add(CACHED_FEE_PARAMS_BEGIN_SLOT(), CACHED_FEE_PARAMS_SLOTS()) } /// @dev The byte from which the paymaster context starts @@ -885,6 +984,70 @@ object "Bootloader" { returndatacopy(PAYMASTER_CONTEXT_BEGIN_BYTE(), returnedContextOffset, add(32, returnedContextLen)) } + /// todo + function PRIORITY_TX_LEGACY_MAX_GAS_LIMIT() -> ret { + ret := 72000000 + } + + /// todo + function getGasLimitForL1Tx( + innerTxDataOffset, + transactionIndex, + gasPerPubdata, + ) -> gasLimitForTx, reservedGas { + // todo think about Validiums for L1->L2 transactions + + // The L1 gas price that was used on L1 in order to calculate the ovehead + // on L1. + // Note, that for the pre-upgrade transactions, this field was never set. + // In this case, we use a different formula + let l1GasPrice := getReserved2(innerTxDataOffset) + + switch l1GasPrice + case 0 { + // It is assumed that l1GasPrice is 0 for legacy L1->L2 transactions. + // We could in theory duplicate the previous logic for such transactions, but + // in order to avoid maintaining big edge cases, the following approach is chosen: + // - We assume that their overhead is 0 and we let `PRIORITY_TX_LEGACY_MAX_GAS_LIMIT` + // gasLimit into the transaction. The rest are to be refunded via reservedGas. + // This situation is strictly not worse for the users (i.e. they anyway receive as much gas as possible). + // + // Even if these transactions do try to make our batch close, they will still pay at least for the execution + // itself, so it should be fine. + + gasLimitForTx := min(getGasLimit(innerTxDataOffset), PRIORITY_TX_LEGACY_MAX_GAS_LIMIT()) + reservedGas := safeSub(getGasLimit(innerTxDataOffset), gasLimitForTx, "l2g") + } + default { + let pubdataPriceETH := safeMul(gasPerPubdata, getMaxFeePerGas(innerTxDataOffset), "l1g") + + getFeeParams( + l1GasPrice, + pubdataPriceETH, + getMaxFeePerGas(innerTxDataOffset) + ) + + let baseFee := mload(0) + let calculatedGasPricePerPubdata := mload(32) + let memorySlotOverhead := mload(64) + let setTxSlotOverhead := mload(96) + + if iszero(eq(calculatedGasPricePerPubdata, gasPerPubdata)) { + assertionError("L1 tx gas per pubdata mismatch") + } + + gasLimitForTx, reservedGas := getGasLimitForTx( + innerTxDataOffset, + transactionIndex, + gasPerPubdata, + setTxSlotOverhead, + memorySlotOverhead, + L1_TX_INTRINSIC_L2_GAS(), + L1_TX_INTRINSIC_PUBDATA() + ) + } + } + /// @dev The function responsible for processing L1->L2 transactions. /// @param txDataOffset The offset to the transaction's information /// @param resultPtr The pointer at which the result of the execution of this transaction @@ -907,12 +1070,10 @@ object "Bootloader" { // Skipping the first formal 0x20 byte let innerTxDataOffset := add(txDataOffset, 32) - let gasLimitForTx, reservedGas := getGasLimitForTx( + let gasLimitForTx, reservedGas := getGasLimitForL1Tx( innerTxDataOffset, transactionIndex, - gasPerPubdata, - L1_TX_INTRINSIC_L2_GAS(), - L1_TX_INTRINSIC_PUBDATA() + gasPerPubdata, ) let gasUsedOnPreparation := 0 @@ -1103,8 +1264,18 @@ object "Bootloader" { // Firsly, we publish all the bytecodes needed. This is needed to be done separately, since // bytecodes usually form the bulk of the L2 gas prices. - - let gasLimitForTx, reservedGas := getGasLimitForTx(innerTxDataOffset, transactionIndex, gasPerPubdata, L2_TX_INTRINSIC_GAS(), L2_TX_INTRINSIC_PUBDATA()) + + /// todo: maybe supply gas price to be used for overhead. + /// it is not needed right now per se, but more robust long term + let gasLimitForTx, reservedGas := getGasLimitForTx( + innerTxDataOffset, + transactionIndex, + gasPerPubdata, + GET_TX_SLOT_OVERHEAD(), + GET_MEMORY_OVERHEAD(), + L2_TX_INTRINSIC_GAS(), + L2_TX_INTRINSIC_PUBDATA() + ) let gasPrice := getGasPrice(getMaxFeePerGas(innerTxDataOffset), getMaxPriorityFeePerGas(innerTxDataOffset)) @@ -1160,8 +1331,10 @@ object "Bootloader" { innerTxDataOffset, transactionIndex, gasPerPubdata, + txSlotOverheadGas, + txLengthEncodingOverheadGas, intrinsicGas, - intrinsicPubdata + intrinsicPubdata, ) -> gasLimitForTx, reservedGas { let totalGasLimit := getGasLimit(innerTxDataOffset) @@ -1185,8 +1358,9 @@ object "Bootloader" { let operatorOverheadForTransaction := getVerifiedOperatorOverheadForTx( transactionIndex, totalGasLimit, - gasPerPubdata, - txEncodingLen + txEncodingLen, + txSlotOverheadGas, + txLengthEncodingOverheadGas ) gasLimitForTx := safeSub(totalGasLimit, operatorOverheadForTransaction, "qr") @@ -1633,9 +1807,11 @@ object "Bootloader" { function getVerifiedOperatorOverheadForTx( transactionIndex, txTotalGasLimit, - gasPerPubdataByte, - txEncodeLen + txEncodeLen, + txSlotOverheadGas, + txLengthEncodingOverheadGas ) -> ret { + // TODO: maybe remove/amend as we use too few params now let operatorOverheadForTransaction := getOperatorOverheadForTx(transactionIndex) if gt(operatorOverheadForTransaction, txTotalGasLimit) { assertionError("Overhead higher than gasLimit") @@ -1643,9 +1819,9 @@ object "Bootloader" { let txGasLimit := min(safeSub(txTotalGasLimit, operatorOverheadForTransaction, "www"), MAX_GAS_PER_TRANSACTION()) let requiredOverhead := getTransactionUpfrontOverhead( - txGasLimit, - gasPerPubdataByte, - txEncodeLen + txEncodeLen, + txSlotOverheadGas, + txLengthEncodingOverheadGas ) debugLog("txTotalGasLimit", txTotalGasLimit) @@ -1856,21 +2032,13 @@ object "Bootloader" { } /// Returns the batch overhead to be paid, assuming a certain value of gasPerPubdata - function getBatchOverheadGas(gasPerPubdata) -> ret { + function getBatchOverheadEth(l1GasPrice, fairL2GasPrice) -> ret { let computationOverhead := BATCH_OVERHEAD_L2_GAS() let l1GasOverhead := BATCH_OVERHEAD_L1_GAS() - let l1GasPerPubdata := L1_GAS_PER_PUBDATA_BYTE() - - // Since the user specifies the amount of gas he is willing to pay for a *byte of pubdata*, - // we need to convert the number of L1 gas needed to process the batch into the equivalent number of - // pubdata to pay for. - // The difference between ceil and floor division here is negligible, - // so we prefer doing the cheaper operation for the end user - let pubdataEquivalentForL1Gas := safeDiv(l1GasOverhead, l1GasPerPubdata, "dd") - + ret := safeAdd( - computationOverhead, - safeMul(gasPerPubdata, pubdataEquivalentForL1Gas, "aa"), + safeMul(computationOverhead, fairL2GasPrice, "aak"), + safeMul(l1GasOverhead, l1GasPrice, "aa"), "ab" ) } @@ -1880,57 +2048,21 @@ object "Bootloader" { /// limited resource per batch: a single-instance circuit, etc. /// The transaction needs to be able to pay the same % of the costs for publishing & proving the batch /// as the % of the batch's limited resources that it can consume. - /// @param txGasLimit The gasLimit for the transaction (note, that this limit should not include the overhead). - /// @param gasPerPubdataByte The price for pubdata byte in gas. /// @param txEncodeLen The length of the ABI-encoding of the transaction - /// @dev The % following 3 resources is taken into account when calculating the % of the batch's overhead to pay. - /// 1. The % of the maximal gas per transaction. It is assumed that `MAX_GAS_PER_TRANSACTION` gas is enough to consume all - /// the single-instance circuits. Meaning that the transaction should pay at least txGasLimit/MAX_GAS_PER_TRANSACTION part - /// of the overhead. - /// 2. Overhead for taking up the bootloader memory. The bootloader memory has a cap on its length, mainly enforced to keep the RAM requirements + /// @dev The % following 2 resources is taken into account when calculating the % of the batch's overhead to pay. + /// 1. Overhead for taking up the bootloader memory. The bootloader memory has a cap on its length, mainly enforced to keep the RAM requirements /// for the node smaller. That is, the user needs to pay a share proportional to the length of the ABI encoding of the transaction. - /// 3. Overhead for taking up a slot for the transaction. Since each batch has the limited number of transactions in it, the user must pay + /// 2. Overhead for taking up a slot for the transaction. Since each batch has the limited number of transactions in it, the user must pay /// at least 1/MAX_TRANSACTIONS_IN_BATCH part of the overhead. function getTransactionUpfrontOverhead( - txGasLimit, - gasPerPubdataByte, - txEncodeLen + txEncodeLen, + txSlotOverheadGas, + txLengthEncodingOverheadGas ) -> ret { - ret := 0 - let totalBatchOverhead := getBatchOverheadGas(gasPerPubdataByte) - debugLog("totalBatchOverhead", totalBatchOverhead) - - let overheadForCircuits := ceilDiv( - safeMul(totalBatchOverhead, txGasLimit, "ac"), - MAX_GAS_PER_TRANSACTION() - ) - ret := max(ret, overheadForCircuits) - debugLog("overheadForCircuits", overheadForCircuits) - - - let overheadForLength := ceilDiv( - safeMul(txEncodeLen, totalBatchOverhead, "ad"), - BOOTLOADER_MEMORY_FOR_TXS() - ) - ret := max(ret, overheadForLength) - debugLog("overheadForLength", overheadForLength) - - - let overheadForSlot := ceilDiv( - totalBatchOverhead, - MAX_TRANSACTIONS_IN_BATCH() + ret := max( + mul(txEncodeLen, txLengthEncodingOverheadGas), + txSlotOverheadGas ) - ret := max(ret, overheadForSlot) - debugLog("overheadForSlot", overheadForSlot) - - // In the proved batch we ensure that the gasPerPubdataByte is not zero - // to avoid the potential edge case of division by zero. In Yul, division by - // zero does not panic, but returns zero. - - if and(iszero(gasPerPubdataByte), FORBID_ZERO_GAS_PER_PUBDATA()) { - assertionError("zero gasPerPubdataByte") - } - } /// @dev A method where all panics in the nearCalls get to. @@ -3721,21 +3853,27 @@ object "Bootloader" { /// the operator still provides it to make sure that its data is in sync. let EXPECTED_BASE_FEE := mload(192) - validateOperatorProvidedPrices(L1_GAS_PRICE, FAIR_L2_GAS_PRICE) + /// TODO + let PUBDATA_PRICE := mload(224) - let baseFee := 0 + validateOperatorProvidedPrices(L1_GAS_PRICE, FAIR_L2_GAS_PRICE, PUBDATA_PRICE) - + getFeeParams( + L1_GAS_PRICE, + PUBDATA_PRICE, + FAIR_L2_GAS_PRICE + ) - // This implementation of the bootloader relies on the correct version of the SystemContext - // and it can not be upgraded via a standard upgrade transaction, but needs to ensure - // correctness itself before any transaction is executed. - upgradeSystemContextIfNeeded() + let baseFee := mload(0) + let GAS_PRICE_PER_PUBDATA := mload(32) + SET_MEMORY_OVERHEAD(mload(64)) + SET_TX_SLOT_OVERHEAD(mload(96)) + + // Only for the proved batch we enforce that the baseFee proposed // by the operator is equal to the expected one. For the playground batch, we allow - // the operator to provide any baseFee the operator wants. - baseFee, GAS_PRICE_PER_PUBDATA := getBaseFee(L1_GAS_PRICE, FAIR_L2_GAS_PRICE) + // the operator to provide any baseFee the operator wants.s if iszero(eq(baseFee, EXPECTED_BASE_FEE)) { debugLog("baseFee", baseFee) debugLog("EXPECTED_BASE_FEE", EXPECTED_BASE_FEE) @@ -3748,12 +3886,8 @@ object "Bootloader" { - baseFee, GAS_PRICE_PER_PUBDATA := getBaseFee(L1_GAS_PRICE, FAIR_L2_GAS_PRICE) - - let SHOULD_SET_NEW_BATCH := mload(224) + let SHOULD_SET_NEW_BATCH := mload(288) - upgradeSystemContextIfNeeded() - switch SHOULD_SET_NEW_BATCH case 0 { unsafeOverrideBatch(NEW_BATCH_TIMESTAMP, NEW_BATCH_NUMBER, EXPECTED_BASE_FEE) From 7e17e3df6bc6da27e8ed913b1b3aac91a0de25c5 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Thu, 16 Nov 2023 13:39:32 +0100 Subject: [PATCH 02/14] simplify overhead calculation --- bootloader/bootloader.yul | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index cbbe15d..7d74a7c 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -56,7 +56,7 @@ object "Bootloader" { // todo: enforce limit for maxComputeGasPerBatch if we decide that this field is at all needed. // MAybe we say that `fairL2GasPrice` MUST take into account the proof verification cost - let batchOverheadETH := getBatchOverheadEth(l1GasPrice, fairL2GasPrice) + let batchOverheadETH := getBatchOverheadEth(l1GasPrice) // let pubdataPriceETH := safeAdd( // pubdataPrice, // // This is equivalent to safeDiv as maxPubdataPerBatch > 0 @@ -126,14 +126,6 @@ object "Bootloader" { ret := div(MAX_GAS_PER_TRANSACTION(), GUARANTEED_PUBDATA_PER_TX()) } - /// @dev The computational overhead for a batch. - /// It includes the combined price for 1 instance of all the circuits - /// (since they might be partially filled), the price for running - /// the common parts of the bootloader as well as general maintainance of the system. - function BATCH_OVERHEAD_L2_GAS() -> ret { - ret := {{BATCH_OVERHEAD_L2_GAS}} - } - /// @dev The overhead for the interaction with L1. /// It should cover proof verification as well as other minor /// overheads for committing/executing a transaction in a batch. @@ -2032,15 +2024,9 @@ object "Bootloader" { } /// Returns the batch overhead to be paid, assuming a certain value of gasPerPubdata - function getBatchOverheadEth(l1GasPrice, fairL2GasPrice) -> ret { - let computationOverhead := BATCH_OVERHEAD_L2_GAS() + function getBatchOverheadEth(l1GasPrice) -> ret { let l1GasOverhead := BATCH_OVERHEAD_L1_GAS() - - ret := safeAdd( - safeMul(computationOverhead, fairL2GasPrice, "aak"), - safeMul(l1GasOverhead, l1GasPrice, "aa"), - "ab" - ) + ret := safeMul(l1GasOverhead, l1GasPrice, "aa") } /// @dev This method returns the overhead that should be paid upfront by a transaction. @@ -3846,6 +3832,7 @@ object "Bootloader" { /// @notice The minimal gas price that the operator agrees upon. /// In the future, it will have an EIP1559-like lower bound. + /// todo: rename to something like "operator minimal gas price" let FAIR_L2_GAS_PRICE := mload(160) /// @notice The expected base fee by the operator. From f7a04c82a644c99dac68ab7cdeb03b1940e90017 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 17 Nov 2023 10:37:50 +0100 Subject: [PATCH 03/14] use short term fee model --- bootloader/bootloader.yul | 67 +++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 7d74a7c..314202a 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -38,7 +38,17 @@ object "Bootloader" { } } - /// todo + // todo: explain how this constant was created + function TX_SLOT_OVERHEAD_GAS() -> ret { + ret := 150000 + } + + // todo: explain how this constant was created + function MEMORY_OVERHEAD_GAS() -> ret { + ret := 35 + } + + /// todo remove l1GasPrice function getFeeParams( l1GasPrice, pubdataPrice, @@ -56,7 +66,7 @@ object "Bootloader" { // todo: enforce limit for maxComputeGasPerBatch if we decide that this field is at all needed. // MAybe we say that `fairL2GasPrice` MUST take into account the proof verification cost - let batchOverheadETH := getBatchOverheadEth(l1GasPrice) + // let batchOverheadETH := getBatchOverheadEth(l1GasPrice) // let pubdataPriceETH := safeAdd( // pubdataPrice, // // This is equivalent to safeDiv as maxPubdataPerBatch > 0 @@ -77,18 +87,18 @@ object "Bootloader" { let gasPricePerPubdata := ceilDiv(pubdataPrice, baseFee) - // Per byte - let memoryOverheadGas := ceilDiv(batchOverheadETH, safeMul(BOOTLOADER_MEMORY_FOR_TXS(), baseFee, "opp")) + // // Per byte + // let memoryOverheadGas := ceilDiv(batchOverheadETH, safeMul(BOOTLOADER_MEMORY_FOR_TXS(), baseFee, "opp")) - // Per tx slot - let txSlotOverheadGas := ceilDiv(batchOverheadETH, safeMul(MAX_TRANSACTIONS_IN_BATCH(), baseFee, "opl")) + // // Per tx slot + // let txSlotOverheadGas := ceilDiv(batchOverheadETH, safeMul(MAX_TRANSACTIONS_IN_BATCH(), baseFee, "opl")) // Save it all into memory // todo: maybe use local vars mstore(0, baseFee) mstore(32, gasPricePerPubdata) - mstore(64, memoryOverheadGas) - mstore(96, txSlotOverheadGas) + mstore(64, MEMORY_OVERHEAD_GAS()) + mstore(96, TX_SLOT_OVERHEAD_GAS()) } /// @dev Returns the baseFee for this batch based on the @@ -993,28 +1003,29 @@ object "Bootloader" { // on L1. // Note, that for the pre-upgrade transactions, this field was never set. // In this case, we use a different formula - let l1GasPrice := getReserved2(innerTxDataOffset) - - switch l1GasPrice - case 0 { - // It is assumed that l1GasPrice is 0 for legacy L1->L2 transactions. - // We could in theory duplicate the previous logic for such transactions, but - // in order to avoid maintaining big edge cases, the following approach is chosen: - // - We assume that their overhead is 0 and we let `PRIORITY_TX_LEGACY_MAX_GAS_LIMIT` - // gasLimit into the transaction. The rest are to be refunded via reservedGas. - // This situation is strictly not worse for the users (i.e. they anyway receive as much gas as possible). - // - // Even if these transactions do try to make our batch close, they will still pay at least for the execution - // itself, so it should be fine. - - gasLimitForTx := min(getGasLimit(innerTxDataOffset), PRIORITY_TX_LEGACY_MAX_GAS_LIMIT()) - reservedGas := safeSub(getGasLimit(innerTxDataOffset), gasLimitForTx, "l2g") - } - default { + // let l1GasPrice := getReserved2(innerTxDataOffset) + + // switch l1GasPrice + // case 0 { + // // It is assumed that l1GasPrice is 0 for legacy L1->L2 transactions. + // // We could in theory duplicate the previous logic for such transactions, but + // // in order to avoid maintaining big edge cases, the following approach is chosen: + // // - We assume that their overhead is 0 and we let `PRIORITY_TX_LEGACY_MAX_GAS_LIMIT` + // // gasLimit into the transaction. The rest are to be refunded via reservedGas. + // // This situation is strictly not worse for the users (i.e. they anyway receive as much gas as possible). + // // + // // Even if these transactions do try to make our batch close, they will still pay at least for the execution + // // itself, so it should be fine. + + // gasLimitForTx := min(getGasLimit(innerTxDataOffset), PRIORITY_TX_LEGACY_MAX_GAS_LIMIT()) + // reservedGas := safeSub(getGasLimit(innerTxDataOffset), gasLimitForTx, "l2g") + // } + // default { let pubdataPriceETH := safeMul(gasPerPubdata, getMaxFeePerGas(innerTxDataOffset), "l1g") getFeeParams( - l1GasPrice, + // todo: remove as l1 gas price is no longer used there + 0, pubdataPriceETH, getMaxFeePerGas(innerTxDataOffset) ) @@ -1037,7 +1048,7 @@ object "Bootloader" { L1_TX_INTRINSIC_L2_GAS(), L1_TX_INTRINSIC_PUBDATA() ) - } + // } } /// @dev The function responsible for processing L1->L2 transactions. From 68ec6901da03323a69f6174a6abfde565e5ec70d Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 17 Nov 2023 13:29:48 +0100 Subject: [PATCH 04/14] trust l1 transaction --- bootloader/bootloader.yul | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 314202a..260c6f5 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -1021,30 +1021,30 @@ object "Bootloader" { // reservedGas := safeSub(getGasLimit(innerTxDataOffset), gasLimitForTx, "l2g") // } // default { - let pubdataPriceETH := safeMul(gasPerPubdata, getMaxFeePerGas(innerTxDataOffset), "l1g") + // let pubdataPriceETH := safeMul(gasPerPubdata, getMaxFeePerGas(innerTxDataOffset), "l1g") - getFeeParams( - // todo: remove as l1 gas price is no longer used there - 0, - pubdataPriceETH, - getMaxFeePerGas(innerTxDataOffset) - ) - - let baseFee := mload(0) - let calculatedGasPricePerPubdata := mload(32) - let memorySlotOverhead := mload(64) - let setTxSlotOverhead := mload(96) + // getFeeParams( + // // todo: remove as l1 gas price is no longer used there + // 0, + // pubdataPriceETH, + // getMaxFeePerGas(innerTxDataOffset) + // ) - if iszero(eq(calculatedGasPricePerPubdata, gasPerPubdata)) { - assertionError("L1 tx gas per pubdata mismatch") - } + // let baseFee := mload(0) + // let calculatedGasPricePerPubdata := mload(32) + // let memorySlotOverhead := mload(64) + // let setTxSlotOverhead := mload(96) + + // if iszero(eq(calculatedGasPricePerPubdata, gasPerPubdata)) { + // assertionError("L1 tx gas per pubdata mismatch") + // } gasLimitForTx, reservedGas := getGasLimitForTx( innerTxDataOffset, transactionIndex, gasPerPubdata, - setTxSlotOverhead, - memorySlotOverhead, + TX_SLOT_OVERHEAD_GAS(), + MEMORY_OVERHEAD_GAS(), L1_TX_INTRINSIC_L2_GAS(), L1_TX_INTRINSIC_PUBDATA() ) From c73137c06899a7b182735468be92f31942f59f30 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Fri, 17 Nov 2023 14:27:38 +0100 Subject: [PATCH 05/14] minor fmt --- bootloader/bootloader.yul | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 260c6f5..e9d6307 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -1039,15 +1039,15 @@ object "Bootloader" { // assertionError("L1 tx gas per pubdata mismatch") // } - gasLimitForTx, reservedGas := getGasLimitForTx( - innerTxDataOffset, - transactionIndex, - gasPerPubdata, - TX_SLOT_OVERHEAD_GAS(), - MEMORY_OVERHEAD_GAS(), - L1_TX_INTRINSIC_L2_GAS(), - L1_TX_INTRINSIC_PUBDATA() - ) + gasLimitForTx, reservedGas := getGasLimitForTx( + innerTxDataOffset, + transactionIndex, + gasPerPubdata, + TX_SLOT_OVERHEAD_GAS(), + MEMORY_OVERHEAD_GAS(), + L1_TX_INTRINSIC_L2_GAS(), + L1_TX_INTRINSIC_PUBDATA() + ) // } } From 45328afa2b4a0d507f4450b5fd9c967aa33791b3 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Sat, 18 Nov 2023 20:03:56 +0100 Subject: [PATCH 06/14] better playground batch --- bootloader/bootloader.yul | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index e9d6307..717be7a 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -85,7 +85,7 @@ object "Bootloader" { ceilDiv(pubdataPrice, MAX_L2_GAS_PER_PUBDATA()) ) - let gasPricePerPubdata := ceilDiv(pubdataPrice, baseFee) + let gasPricePerPubdata := gasPerPubdataFromBaseFee(baseFee, pubdataPrice) // // Per byte // let memoryOverheadGas := ceilDiv(batchOverheadETH, safeMul(BOOTLOADER_MEMORY_FOR_TXS(), baseFee, "opp")) @@ -101,6 +101,10 @@ object "Bootloader" { mstore(96, TX_SLOT_OVERHEAD_GAS()) } + function gasPerPubdataFromBaseFee(baseFee, pubdataPrice) -> ret { + ret := ceilDiv(pubdataPrice, baseFee) + } + /// @dev Returns the baseFee for this batch based on the /// L1 gas price and the fair L2 gas price. function getBaseFee(l1GasPrice, fairL2GasPrice) -> baseFee, gasPricePerPubdata { @@ -3894,6 +3898,8 @@ object "Bootloader" { setNewBatch(PREV_BATCH_HASH, NEW_BATCH_TIMESTAMP, NEW_BATCH_NUMBER, EXPECTED_BASE_FEE) } + GAS_PRICE_PER_PUBDATA := gasPerPubdataFromBaseFee(EXPECTED_BASE_FEE, PUBDATA_PRICE) + } From 60638921251bb99b19320d0121264e8690fb8184 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Sun, 19 Nov 2023 12:32:32 +0100 Subject: [PATCH 07/14] use 80k for tx slot overhead --- bootloader/bootloader.yul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 717be7a..42a6bef 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -40,7 +40,7 @@ object "Bootloader" { // todo: explain how this constant was created function TX_SLOT_OVERHEAD_GAS() -> ret { - ret := 150000 + ret := 80000 } // todo: explain how this constant was created From bbc15793ba43b07f7ba858ab08cd569acf39b1a1 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Sun, 19 Nov 2023 14:02:36 +0100 Subject: [PATCH 08/14] some more refactor --- bootloader/bootloader.yul | 202 +++++--------------------------------- 1 file changed, 24 insertions(+), 178 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 42a6bef..51a3e7d 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -23,11 +23,7 @@ object "Bootloader" { /// @dev This method ensures that the prices provided by the operator /// are not absurdly high - function validateOperatorProvidedPrices(l1GasPrice, fairL2GasPrice, pubdataPrice) { - if gt(l1GasPrice, MAX_ALLOWED_L1_GAS_PRICE()) { - assertionError("L1 gas price too high") - } - + function validateOperatorProvidedPrices(fairL2GasPrice, pubdataPrice) { // The limit is the same for pubdata price and L1 gas price if gt(pubdataPrice, MAX_ALLOWED_L1_GAS_PRICE()) { assertionError("Pubdata price too high") @@ -48,92 +44,29 @@ object "Bootloader" { ret := 35 } - /// todo remove l1GasPrice + // todo: comment function getFeeParams( - l1GasPrice, pubdataPrice, fairL2GasPrice, - // we allow operator to provide it because of validium support - // maxPubdataPerBatch, - // // we allow operator to provide it because of potentially better future values - // maxComputeGasPerBatch - ) { - // todo: think about the L1Messenger - // if lt(maxPubdataPerBatch, GUARANTEED_PUBDATA_PER_BATCH()) { - // assertionError("maxPubdataPerBatch too low") - // } - - // todo: enforce limit for maxComputeGasPerBatch if we decide that this field is at all needed. - // MAybe we say that `fairL2GasPrice` MUST take into account the proof verification cost - - // let batchOverheadETH := getBatchOverheadEth(l1GasPrice) - // let pubdataPriceETH := safeAdd( - // pubdataPrice, - // // This is equivalent to safeDiv as maxPubdataPerBatch > 0 - // // Due to floor division in theory the operator might get underpaid, but - // // it is negligible - // div(batchOverheadETH, maxPubdataPerBatch), - // "ll" - // ) - - // includes the overhead cost - // let baseComputeCost := safeAdd( - // fairL2GasPrice, div(batchOverheadETH, maxComputeGasPerBatch) - // ) - let baseFee := max( + ) -> baseFee, gasPerPubdata { + baseFee := max( fairL2GasPrice, ceilDiv(pubdataPrice, MAX_L2_GAS_PER_PUBDATA()) ) - let gasPricePerPubdata := gasPerPubdataFromBaseFee(baseFee, pubdataPrice) - - // // Per byte - // let memoryOverheadGas := ceilDiv(batchOverheadETH, safeMul(BOOTLOADER_MEMORY_FOR_TXS(), baseFee, "opp")) - - // // Per tx slot - // let txSlotOverheadGas := ceilDiv(batchOverheadETH, safeMul(MAX_TRANSACTIONS_IN_BATCH(), baseFee, "opl")) - - // Save it all into memory - // todo: maybe use local vars - mstore(0, baseFee) - mstore(32, gasPricePerPubdata) - mstore(64, MEMORY_OVERHEAD_GAS()) - mstore(96, TX_SLOT_OVERHEAD_GAS()) + gasPerPubdata := gasPerPubdataFromBaseFee(baseFee, pubdataPrice) } function gasPerPubdataFromBaseFee(baseFee, pubdataPrice) -> ret { ret := ceilDiv(pubdataPrice, baseFee) } - /// @dev Returns the baseFee for this batch based on the - /// L1 gas price and the fair L2 gas price. - function getBaseFee(l1GasPrice, fairL2GasPrice) -> baseFee, gasPricePerPubdata { - // By default, we want to provide the fair L2 gas price. - // That it means that the operator controls - // what the value of the baseFee will be. In the future, - // a better system, aided by EIP1559 should be added. - - let pubdataBytePriceETH := safeMul(l1GasPrice, L1_GAS_PER_PUBDATA_BYTE(), "aoa") - - baseFee := max( - fairL2GasPrice, - ceilDiv(pubdataBytePriceETH, MAX_L2_GAS_PER_PUBDATA()) - ) - gasPricePerPubdata := ceilDiv(pubdataBytePriceETH, baseFee) - } - /// @dev It should be always possible to submit a transaction /// that consumes such amount of public data. function GUARANTEED_PUBDATA_PER_TX() -> ret { ret := {{GUARANTEED_PUBDATA_BYTES}} } - /// todo - function GUARANTEED_PUBDATA_PER_BATCH() -> ret { - // todo: rename var - ret := {{MAX_PUBDATA_PER_BATCH}} - } - /// @dev The maximal gasPerPubdata, which allows users to still be /// able to send `GUARANTEED_PUBDATA_PER_TX` onchain. function MAX_L2_GAS_PER_PUBDATA() -> ret { @@ -990,71 +923,6 @@ object "Bootloader" { returndatacopy(PAYMASTER_CONTEXT_BEGIN_BYTE(), returnedContextOffset, add(32, returnedContextLen)) } - /// todo - function PRIORITY_TX_LEGACY_MAX_GAS_LIMIT() -> ret { - ret := 72000000 - } - - /// todo - function getGasLimitForL1Tx( - innerTxDataOffset, - transactionIndex, - gasPerPubdata, - ) -> gasLimitForTx, reservedGas { - // todo think about Validiums for L1->L2 transactions - - // The L1 gas price that was used on L1 in order to calculate the ovehead - // on L1. - // Note, that for the pre-upgrade transactions, this field was never set. - // In this case, we use a different formula - // let l1GasPrice := getReserved2(innerTxDataOffset) - - // switch l1GasPrice - // case 0 { - // // It is assumed that l1GasPrice is 0 for legacy L1->L2 transactions. - // // We could in theory duplicate the previous logic for such transactions, but - // // in order to avoid maintaining big edge cases, the following approach is chosen: - // // - We assume that their overhead is 0 and we let `PRIORITY_TX_LEGACY_MAX_GAS_LIMIT` - // // gasLimit into the transaction. The rest are to be refunded via reservedGas. - // // This situation is strictly not worse for the users (i.e. they anyway receive as much gas as possible). - // // - // // Even if these transactions do try to make our batch close, they will still pay at least for the execution - // // itself, so it should be fine. - - // gasLimitForTx := min(getGasLimit(innerTxDataOffset), PRIORITY_TX_LEGACY_MAX_GAS_LIMIT()) - // reservedGas := safeSub(getGasLimit(innerTxDataOffset), gasLimitForTx, "l2g") - // } - // default { - // let pubdataPriceETH := safeMul(gasPerPubdata, getMaxFeePerGas(innerTxDataOffset), "l1g") - - // getFeeParams( - // // todo: remove as l1 gas price is no longer used there - // 0, - // pubdataPriceETH, - // getMaxFeePerGas(innerTxDataOffset) - // ) - - // let baseFee := mload(0) - // let calculatedGasPricePerPubdata := mload(32) - // let memorySlotOverhead := mload(64) - // let setTxSlotOverhead := mload(96) - - // if iszero(eq(calculatedGasPricePerPubdata, gasPerPubdata)) { - // assertionError("L1 tx gas per pubdata mismatch") - // } - - gasLimitForTx, reservedGas := getGasLimitForTx( - innerTxDataOffset, - transactionIndex, - gasPerPubdata, - TX_SLOT_OVERHEAD_GAS(), - MEMORY_OVERHEAD_GAS(), - L1_TX_INTRINSIC_L2_GAS(), - L1_TX_INTRINSIC_PUBDATA() - ) - // } - } - /// @dev The function responsible for processing L1->L2 transactions. /// @param txDataOffset The offset to the transaction's information /// @param resultPtr The pointer at which the result of the execution of this transaction @@ -1077,10 +945,12 @@ object "Bootloader" { // Skipping the first formal 0x20 byte let innerTxDataOffset := add(txDataOffset, 32) - let gasLimitForTx, reservedGas := getGasLimitForL1Tx( - innerTxDataOffset, + let gasLimitForTx, reservedGas := getGasLimitForTx( + innerTxDataOffset, transactionIndex, gasPerPubdata, + L1_TX_INTRINSIC_L2_GAS(), + L1_TX_INTRINSIC_PUBDATA() ) let gasUsedOnPreparation := 0 @@ -1272,14 +1142,10 @@ object "Bootloader" { // Firsly, we publish all the bytecodes needed. This is needed to be done separately, since // bytecodes usually form the bulk of the L2 gas prices. - /// todo: maybe supply gas price to be used for overhead. - /// it is not needed right now per se, but more robust long term let gasLimitForTx, reservedGas := getGasLimitForTx( innerTxDataOffset, transactionIndex, gasPerPubdata, - GET_TX_SLOT_OVERHEAD(), - GET_MEMORY_OVERHEAD(), L2_TX_INTRINSIC_GAS(), L2_TX_INTRINSIC_PUBDATA() ) @@ -1338,8 +1204,6 @@ object "Bootloader" { innerTxDataOffset, transactionIndex, gasPerPubdata, - txSlotOverheadGas, - txLengthEncodingOverheadGas, intrinsicGas, intrinsicPubdata, ) -> gasLimitForTx, reservedGas { @@ -1365,9 +1229,7 @@ object "Bootloader" { let operatorOverheadForTransaction := getVerifiedOperatorOverheadForTx( transactionIndex, totalGasLimit, - txEncodingLen, - txSlotOverheadGas, - txLengthEncodingOverheadGas + txEncodingLen ) gasLimitForTx := safeSub(totalGasLimit, operatorOverheadForTransaction, "qr") @@ -1814,22 +1676,15 @@ object "Bootloader" { function getVerifiedOperatorOverheadForTx( transactionIndex, txTotalGasLimit, - txEncodeLen, - txSlotOverheadGas, - txLengthEncodingOverheadGas + txEncodeLen ) -> ret { - // TODO: maybe remove/amend as we use too few params now let operatorOverheadForTransaction := getOperatorOverheadForTx(transactionIndex) if gt(operatorOverheadForTransaction, txTotalGasLimit) { assertionError("Overhead higher than gasLimit") } let txGasLimit := min(safeSub(txTotalGasLimit, operatorOverheadForTransaction, "www"), MAX_GAS_PER_TRANSACTION()) - let requiredOverhead := getTransactionUpfrontOverhead( - txEncodeLen, - txSlotOverheadGas, - txLengthEncodingOverheadGas - ) + let requiredOverhead := getTransactionUpfrontOverhead(txEncodeLen) debugLog("txTotalGasLimit", txTotalGasLimit) debugLog("requiredOverhead", requiredOverhead) @@ -2056,13 +1911,11 @@ object "Bootloader" { /// 2. Overhead for taking up a slot for the transaction. Since each batch has the limited number of transactions in it, the user must pay /// at least 1/MAX_TRANSACTIONS_IN_BATCH part of the overhead. function getTransactionUpfrontOverhead( - txEncodeLen, - txSlotOverheadGas, - txLengthEncodingOverheadGas + txEncodeLen ) -> ret { ret := max( - mul(txEncodeLen, txLengthEncodingOverheadGas), - txSlotOverheadGas + safeMul(txEncodeLen, MEMORY_OVERHEAD_GAS(), "iot"), + TX_SLOT_OVERHEAD_GAS() ) } @@ -3841,13 +3694,13 @@ object "Bootloader" { /// of the VM and the state of the operator. let NEW_BATCH_NUMBER := mload(96) - /// @notice The gas price on L1 for ETH. In the future, a trustless value will be enforced. + /// @notice The minimal price per pubdata byte in ETH that the operator agrees on. + /// In the future, a trustless value will be enforced. /// For now, this value is trusted to be fairly provided by the operator. - let L1_GAS_PRICE := mload(128) + let FAIR_PUBDATA_PRICE := mload(128) /// @notice The minimal gas price that the operator agrees upon. /// In the future, it will have an EIP1559-like lower bound. - /// todo: rename to something like "operator minimal gas price" let FAIR_L2_GAS_PRICE := mload(160) /// @notice The expected base fee by the operator. @@ -3855,22 +3708,15 @@ object "Bootloader" { /// the operator still provides it to make sure that its data is in sync. let EXPECTED_BASE_FEE := mload(192) - /// TODO - let PUBDATA_PRICE := mload(224) + validateOperatorProvidedPrices(FAIR_L2_GAS_PRICE, FAIR_PUBDATA_PRICE) - validateOperatorProvidedPrices(L1_GAS_PRICE, FAIR_L2_GAS_PRICE, PUBDATA_PRICE) + let baseFee := 0 - getFeeParams( - L1_GAS_PRICE, - PUBDATA_PRICE, + baseFee, GAS_PRICE_PER_PUBDATA := getFeeParams( + FAIR_PUBDATA_PRICE, FAIR_L2_GAS_PRICE ) - let baseFee := mload(0) - let GAS_PRICE_PER_PUBDATA := mload(32) - SET_MEMORY_OVERHEAD(mload(64)) - SET_TX_SLOT_OVERHEAD(mload(96)) - // Only for the proved batch we enforce that the baseFee proposed @@ -3888,7 +3734,7 @@ object "Bootloader" { - let SHOULD_SET_NEW_BATCH := mload(288) + let SHOULD_SET_NEW_BATCH := mload(224) switch SHOULD_SET_NEW_BATCH case 0 { @@ -3898,7 +3744,7 @@ object "Bootloader" { setNewBatch(PREV_BATCH_HASH, NEW_BATCH_TIMESTAMP, NEW_BATCH_NUMBER, EXPECTED_BASE_FEE) } - GAS_PRICE_PER_PUBDATA := gasPerPubdataFromBaseFee(EXPECTED_BASE_FEE, PUBDATA_PRICE) + GAS_PRICE_PER_PUBDATA := gasPerPubdataFromBaseFee(EXPECTED_BASE_FEE, FAIR_PUBDATA_PRICE) } From b93d4ab8e95191e9323539ebcb60d2b6c6a7c81d Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Sun, 19 Nov 2023 21:43:57 +0100 Subject: [PATCH 09/14] add some new comments --- bootloader/bootloader.yul | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 51a3e7d..c90afc8 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -11,7 +11,7 @@ object "Bootloader" { // While we definitely cannot control the gas price on L1, // we need to check the operator does not provide any absurd numbers there - function MAX_ALLOWED_L1_GAS_PRICE() -> ret { + function MAX_ALLOWED_FAIR_PUBDATA_PRICE() -> ret { // 100k gwei ret := 100000000000000 } @@ -25,8 +25,8 @@ object "Bootloader" { /// are not absurdly high function validateOperatorProvidedPrices(fairL2GasPrice, pubdataPrice) { // The limit is the same for pubdata price and L1 gas price - if gt(pubdataPrice, MAX_ALLOWED_L1_GAS_PRICE()) { - assertionError("Pubdata price too high") + if gt(pubdataPrice, MAX_ALLOWED_FAIR_PUBDATA_PRICE()) { + assertionError("Fair pubdata price too high") } if gt(fairL2GasPrice, MAX_ALLOWED_FAIR_L2_GAS_PRICE()) { @@ -34,29 +34,42 @@ object "Bootloader" { } } - // todo: explain how this constant was created + /// @dev The overhead for a transaction slot in L2 gas. + /// It is roughly equal to 80kk/MAX_TRANSACTIONS_PER_BATCH, i.e. how many gas would an L1->L2 transaction + /// need to pay to compensate for the batch being closed. + /// @dev It is expected of the operator to set the "fair L2 gas price" appropriately to ensure that it is + /// compensated enough in case the batch might be prematurely sealed because of the memory being filled up. function TX_SLOT_OVERHEAD_GAS() -> ret { ret := 80000 } - // todo: explain how this constant was created + /// @dev The overhead for each byte of the bootloader memory that the encoding of the transaction. + /// It is roughly equal to 80kk/BOOTLOADER_MEMORY_FOR_TXS, i.e. how many gas would an L1->L2 transaction + /// need to pay to compensate for the batch being closed. + /// @dev It is expected of the operator to set the "fair L2 gas price" appropriately to ensure that it is + /// compensated enough in case the batch might be prematurely sealed because of the memory being filled up. function MEMORY_OVERHEAD_GAS() -> ret { - ret := 35 + ret := 10 } - // todo: comment + /// @dev Returns the base fee and gas per pubdata based on the fair pubdata price and L2 gas price provided by the operator + /// @param pubdataPrice The price of a single byte of pubdata in Wei + /// @param fairL2GasPrice The price of an L2 gas in Wei + /// @return baseFee and gasPerPubdata The base fee and the gas per pubdata to be used by L2 transactions in this batch. function getFeeParams( - pubdataPrice, + fairPubdataPrice, fairL2GasPrice, ) -> baseFee, gasPerPubdata { baseFee := max( fairL2GasPrice, - ceilDiv(pubdataPrice, MAX_L2_GAS_PER_PUBDATA()) + ceilDiv(fairPubdataPrice, MAX_L2_GAS_PER_PUBDATA()) ) - gasPerPubdata := gasPerPubdataFromBaseFee(baseFee, pubdataPrice) + gasPerPubdata := gasPerPubdataFromBaseFee(baseFee, fairPubdataPrice) } + /// @dev Calculates the gas per pubdata based on the pubdata price provided by the operator + /// as well the the fixed baseFee. function gasPerPubdataFromBaseFee(baseFee, pubdataPrice) -> ret { ret := ceilDiv(pubdataPrice, baseFee) } @@ -3697,10 +3710,12 @@ object "Bootloader" { /// @notice The minimal price per pubdata byte in ETH that the operator agrees on. /// In the future, a trustless value will be enforced. /// For now, this value is trusted to be fairly provided by the operator. + /// It is expected of the operator to already include the L1 batch overhead costs into the value. let FAIR_PUBDATA_PRICE := mload(128) /// @notice The minimal gas price that the operator agrees upon. /// In the future, it will have an EIP1559-like lower bound. + /// It is expected of the operator to already include the L1 batch overhead costs into the value. let FAIR_L2_GAS_PRICE := mload(160) /// @notice The expected base fee by the operator. From b7f46756e3f924d63c2ccb36dbc52e70013a297a Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Mon, 20 Nov 2023 00:06:03 +0100 Subject: [PATCH 10/14] remove some unused vars --- bootloader/bootloader.yul | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index c90afc8..e1e2053 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -141,41 +141,6 @@ object "Bootloader" { ret := 32 } - /// todo - function CACHED_FEE_PARAMS_SLOTS() -> ret { - ret := 2 - } - - /// todo - function CACHED_FEE_PARAMS_BEGIN_SLOT() -> ret { - ret := add(SCRATCH_SPACE_BEGIN_SLOT(), SCRATCH_SPACE_SLOTS()) - } - - /// todo - function CACHED_FEE_PARAMS_BEGIN_BYTE() -> ret { - ret := mul(CACHED_FEE_PARAMS_BEGIN_SLOT(), 32) - } - - /// todo - function SET_TX_SLOT_OVERHEAD(value) -> ret { - mstore(CACHED_FEE_PARAMS_BEGIN_BYTE(), value) - } - - /// todo - function GET_TX_SLOT_OVERHEAD() -> ret { - ret := mload(CACHED_FEE_PARAMS_BEGIN_BYTE()) - } - - /// todo - function SET_MEMORY_OVERHEAD(value) -> ret { - mstore(add(CACHED_FEE_PARAMS_BEGIN_BYTE(), 32), value) - } - - /// todo - function GET_MEMORY_OVERHEAD() -> ret { - ret := mload(add(CACHED_FEE_PARAMS_BEGIN_BYTE(), 32)) - } - /// @dev Slots reserved for saving the paymaster context /// @dev The paymasters are allowed to consume at most /// 32 slots (1024 bytes) for their context. @@ -191,7 +156,7 @@ object "Bootloader" { /// @dev Slot from which the paymaster context starts function PAYMASTER_CONTEXT_BEGIN_SLOT() -> ret { - ret := add(CACHED_FEE_PARAMS_BEGIN_SLOT(), CACHED_FEE_PARAMS_SLOTS()) + ret := add(SCRATCH_SPACE_BEGIN_SLOT(), SCRATCH_SPACE_SLOTS()) } /// @dev The byte from which the paymaster context starts From e2d6109ac9155b10f52a4080dfed534a8709586b Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Mon, 20 Nov 2023 14:57:26 +0100 Subject: [PATCH 11/14] remove bad comma --- bootloader/bootloader.yul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index e1e2053..7f0aa27 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -1183,7 +1183,7 @@ object "Bootloader" { transactionIndex, gasPerPubdata, intrinsicGas, - intrinsicPubdata, + intrinsicPubdata ) -> gasLimitForTx, reservedGas { let totalGasLimit := getGasLimit(innerTxDataOffset) From aabef8180f15732765e171addbcfada788b6d9ba Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 21 Nov 2023 12:50:18 +0100 Subject: [PATCH 12/14] remove unused method --- bootloader/bootloader.yul | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index 7f0aa27..fb58490 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -650,36 +650,6 @@ object "Bootloader" { } } - /// @dev Checks whether the code hash of the system context contract is correct and updates it if needed. - /// @dev The L1 contracts expect all the system logs to be present in the first boojum upgrade batch already. - /// However, the old system context did not send the same system logs. Usually we upgrade system context - /// via an upgrade transaction, but in this case the transaction won't be even processed, because of failure to create an L2 block. - function upgradeSystemContextIfNeeded() { - let expectedCodeHash := {{SYSTEM_CONTEXT_EXPECTED_CODE_HASH}} - - let actualCodeHash := extcodehash(SYSTEM_CONTEXT_ADDR()) - if iszero(eq(expectedCodeHash, actualCodeHash)) { - // Preparing the calldata to upgrade the SystemContext contract - {{UPGRADE_SYSTEM_CONTEXT_CALLDATA}} - - // We'll use a mimicCall to simulate the correct sender. - let success := mimicCallOnlyResult( - CONTRACT_DEPLOYER_ADDR(), - FORCE_DEPLOYER(), - 0, - 0, - 0, - 0, - 0, - 0 - ) - - if iszero(success) { - assertionError("system context upgrade fail") - } - } - } - /// @dev Calculates the canonical hash of the L1->L2 transaction that will be /// sent to L1 as a message to the L1 contract that a certain operation has been processed. function getCanonicalL1TxHash(txDataOffset) -> ret { From 92b50a490f2d9d753af95a7858adddc590e697a5 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 21 Nov 2023 12:53:22 +0100 Subject: [PATCH 13/14] update hashes --- SystemContractsHashes.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/SystemContractsHashes.json b/SystemContractsHashes.json index 78e00ae..c4cb468 100644 --- a/SystemContractsHashes.json +++ b/SystemContractsHashes.json @@ -143,35 +143,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x0100038548508a2a29b0c6e8a86fc0ec5c512baf563155e8171afd1a060c81fa", - "sourceCodeHash": "0x8a2f1171cb02b1500e75e607a7a16ea8782b54800fb3396d0aea241117539265" + "bytecodeHash": "0x0100038565d307509535f5857460173abfee8953c6797da1ebe641c3c8c77919", + "sourceCodeHash": "0xbf0a19c4484b310dc93a7e302d609ddbe0ea97a05bcb13fb51b1fd472a2231e4" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x01000989a967ab5b446c084adf05e13399a24e5cf37e9cf7db05a5dd6d7c5e0b", - "sourceCodeHash": "0xf8d6ef018c46d562d4473628c4b13af322320a4c24015c884083bf5654ce7408" + "bytecodeHash": "0x010009093c1b6ae32a78c2c9621866c92a78e7cce4fbe9ff476da7bd40cf253d", + "sourceCodeHash": "0xe08a1d1863a5a0bb210519a30cfaad07224b617c6cc393814d968e408d8a2fb8" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x0100096912f983649836f812c6db81c814cc0a5ff24b80ecffbf79ca01e7946c", - "sourceCodeHash": "0xc130da5db5af59763a518394dddf96358664eef53b11260d4c4f86ae967c454d" + "bytecodeHash": "0x010008ebd1415a5ee442d6fe93840fdcb546281548ae66911208396eac61e635", + "sourceCodeHash": "0xe8adf65d3d38e2f4c556cdd03d9c982a2f59d39cae702eb745098927a30eb92c" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100099308cc5367de190e240aa355df7d0cfacb6a752726bad8f3100044629f", - "sourceCodeHash": "0x7fd1d118ecb97b79a2bb9d66e132638344d6ad333486f8ee520455679ae5aaaa" + "bytecodeHash": "0x01000913389644733f0a1aecfdd625a622bec1623bf5d4d3367e0b6bc2f3a0ce", + "sourceCodeHash": "0x5656d2f077d8cece040ff70c06b987633c375d397f3ecc07e948cad6a755ed38" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x01000983d4ac4f797cf5c077e022f72284969b13248c2a8e9846f574bdeb5b88", - "sourceCodeHash": "0x444b9dad3a29511c5a80d6f50e1ccf4500031452d2ef3edb4f63593b7070a24d" + "bytecodeHash": "0x010008fb85e4ba8937946e844cddf049b41042c1721aa4c8aade0ebb4e6047f7", + "sourceCodeHash": "0xe474593d1225b9143c33b8ca7fe15329910b2429020982968af0d2ccec9ff4dd" } ] From b49cfc6173087a85a67ead2628450f1274bf2565 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Tue, 9 Jan 2024 21:50:25 +0100 Subject: [PATCH 14/14] Port 1 4 1 fixes (#105) * Fix typographical errors (#91) fix typos * Fix misleading comment (#92) * fix misleading comment * fix lint --- bootloader/bootloader.yul | 10 +++++----- contracts/L1Messenger.sol | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bootloader/bootloader.yul b/bootloader/bootloader.yul index e0a0ae8..8356a23 100644 --- a/bootloader/bootloader.yul +++ b/bootloader/bootloader.yul @@ -124,7 +124,7 @@ object "Bootloader" { } /// @dev The slot from which the scratch space starts. - /// Scatch space is used for various temporary values + /// Scratch space is used for various temporary values function SCRATCH_SPACE_BEGIN_SLOT() -> ret { ret := 8 } @@ -359,7 +359,7 @@ object "Bootloader" { /// @dev Slots needed to store L1 Messenger pubdata. /// @dev Note that are many more these than the maximal pubdata in batch, since - /// it needs to also accomodate uncompressed state diffs that are required for the state diff + /// it needs to also accommodate uncompressed state diffs that are required for the state diff /// compression verification. function OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS() -> ret { ret := {{OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS}} @@ -2728,7 +2728,7 @@ object "Bootloader" { ) } default { - // For L2 transactions, we use near call panic, it will triger the validation + // For L2 transactions, we use near call panic, it will trigger the validation // step of the transaction to fail, returning a consistent error message. nearCallPanic() } @@ -3852,7 +3852,7 @@ object "Bootloader" { setTxOrigin(0) setGasPrice(0) - // Transfering all the ETH received in the block to the operator + // Transferring all the ETH received in the block to the operator directETHTransfer( selfbalance(), OPERATOR_ADDRESS @@ -3865,7 +3865,7 @@ object "Bootloader" { // So we need to have this method to reflect it in the system contracts too. // // The reason is that as of now our node requires that each storage write (event, etc) belongs to a particular - // L2 block. In case a batch is sealed by timeout (i.e. the resources of the batch have not been exhaused, but we need + // L2 block. In case a batch is sealed by timeout (i.e. the resources of the batch have not been exhausted, but we need // to seal it to assure timely finality), we need to process sending funds to the operator *after* the last // non-empty L2 block has been already sealed. We can not override old L2 blocks, so we need to create a new empty "fictive" block for it. // diff --git a/contracts/L1Messenger.sol b/contracts/L1Messenger.sol index 47ee326..3016016 100644 --- a/contracts/L1Messenger.sol +++ b/contracts/L1Messenger.sol @@ -272,8 +272,8 @@ contract L1Messenger is IL1Messenger, ISystemContract { /// Check State Diffs /// encoding is as follows: - /// header (1 byte version, 3 bytes total len of compressed, 1 byte enumeration index size, 2 bytes number of initial writes) - /// body (N bytes of initial writes [32 byte derived key || compressed value], M bytes repeated writes [enumeration index || compressed value]) + /// header (1 byte version, 3 bytes total len of compressed, 1 byte enumeration index size) + /// body (`compressedStateDiffSize` bytes, 4 bytes number of state diffs, `numberOfStateDiffs` * `STATE_DIFF_ENTRY_SIZE` bytes for the uncompressed state diffs) /// encoded state diffs: [20bytes address][32bytes key][32bytes derived key][8bytes enum index][32bytes initial value][32bytes final value] require( uint256(uint8(bytes1(_totalL2ToL1PubdataAndStateDiffs[calldataPtr]))) ==