From 72521d899bfab2353bc2f82901778533e41d2f79 Mon Sep 17 00:00:00 2001 From: Vladislav Volosnikov Date: Fri, 20 Dec 2024 16:59:23 +0100 Subject: [PATCH] Fix gas accounting in emulator --- system-contracts/contracts/EvmEmulator.yul | 322 ++++++++---------- .../evm-emulator/EvmEmulator.template.yul | 2 +- .../EvmEmulatorFunctions.template.yul | 114 ++++--- .../evm-emulator/EvmEmulatorLoop.template.yul | 46 +-- 4 files changed, 215 insertions(+), 269 deletions(-) diff --git a/system-contracts/contracts/EvmEmulator.yul b/system-contracts/contracts/EvmEmulator.yul index 5b41e6643..dd89c7b92 100644 --- a/system-contracts/contracts/EvmEmulator.yul +++ b/system-contracts/contracts/EvmEmulator.yul @@ -225,7 +225,7 @@ object "EvmEmulator" { function chargeGas(prevGas, toCharge) -> gasRemaining { if lt(prevGas, toCharge) { - panic() + revertWithGas(prevGas) } gasRemaining := sub(prevGas, toCharge) @@ -239,62 +239,73 @@ object "EvmEmulator" { } } - // This function can overflow, it is the job of the caller to ensure that it does not. // The argument to this function is the offset into the memory region IN BYTES. - function expandMemory(offset, size) -> gasCost { + function expandMemory(offset, size, evmGasLeft) -> gasCost { // memory expansion costs 0 if size is 0 if size { - let oldSizeInWords := mload(MEM_LEN_OFFSET()) + checkOverflow(offset, size, evmGasLeft) + gasCost := _expandMemoryInternal(add(offset, size), evmGasLeft) + } + } - // div rounding up - let newSizeInWords := div(add(add(offset, size), 31), 32) - - // memory_size_word = (memory_byte_size + 31) / 32 - // memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) - // memory_expansion_cost = new_memory_cost - last_memory_cost - if gt(newSizeInWords, oldSizeInWords) { - let linearPart := mul(3, sub(newSizeInWords, oldSizeInWords)) - let quadraticPart := sub( - div( - mul(newSizeInWords, newSizeInWords), - 512 - ), - div( - mul(oldSizeInWords, oldSizeInWords), - 512 - ) + // This function can overflow, it is the job of the caller to ensure that it does not. + // The argument to this function is the offset into the memory region IN BYTES. + function _expandMemoryInternal(newMemsize, evmGasLeft) -> gasCost { + if gt(newMemsize, MAX_POSSIBLE_MEM_LEN()) { + revertWithGas(evmGasLeft) // Not possible to pay for this memsize + } + + let oldSizeInWords := mload(MEM_LEN_OFFSET()) + + // div rounding up + let newSizeInWords := div(add(newMemsize, 31), 32) + + // memory_size_word = (memory_byte_size + 31) / 32 + // memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) + // memory_expansion_cost = new_memory_cost - last_memory_cost + if gt(newSizeInWords, oldSizeInWords) { + let linearPart := mul(3, sub(newSizeInWords, oldSizeInWords)) + let quadraticPart := sub( + div( + mul(newSizeInWords, newSizeInWords), + 512 + ), + div( + mul(oldSizeInWords, oldSizeInWords), + 512 ) - - gasCost := add(linearPart, quadraticPart) - - mstore(MEM_LEN_OFFSET(), newSizeInWords) - } + ) + + gasCost := add(linearPart, quadraticPart) + + mstore(MEM_LEN_OFFSET(), newSizeInWords) } } - function expandMemory2(retOffset, retSize, argsOffset, argsSize) -> maxExpand { - switch lt(add(retOffset, retSize), add(argsOffset, argsSize)) - case 0 { - maxExpand := expandMemory(retOffset, retSize) - } - default { - maxExpand := expandMemory(argsOffset, argsSize) + // Returns 0 if size is 0 + function _memsizeRequired(offset, size, evmGasLeft) -> memorySize { + if size { + checkOverflow(offset, size, evmGasLeft) + memorySize := add(offset, size) } } - function checkMemIsAccessible(relativeOffset, size) { - if size { - checkOverflow(relativeOffset, size) + function expandMemory2(retOffset, retSize, argsOffset, argsSize, evmGasLeft) -> gasCost { + let maxNewMemsize := _memsizeRequired(retOffset, retSize, evmGasLeft) + let argsMemsize := _memsizeRequired(argsOffset, argsSize, evmGasLeft) - if gt(add(relativeOffset, size), MAX_POSSIBLE_MEM_LEN()) { - panic() - } + if lt(maxNewMemsize, argsMemsize) { + maxNewMemsize := argsMemsize + } + + if maxNewMemsize { // Memory expansion costs 0 if size is 0 + gasCost := _expandMemoryInternal(maxNewMemsize, evmGasLeft) } } - function checkOverflow(data1, data2) { + function checkOverflow(data1, data2, evmGasLeft) { if lt(add(data1, data2), data2) { - panic() + revertWithGas(evmGasLeft) } } @@ -693,7 +704,7 @@ object "EvmEmulator" { // If value is not 0, then positive_value_cost is 9000. In this case there is also a call stipend that is given to make sure that a basic fallback function can be called. // If value is not 0 and the address given points to an empty account, then value_to_empty_account_cost is 25000. An account is empty if its balance is 0, its nonce is 0 and it has no code. - let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) if gt(value, 0) { if isStatic { @@ -740,7 +751,7 @@ object "EvmEmulator" { argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) evmGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(evmGasLeft, gasToPass) @@ -772,7 +783,7 @@ object "EvmEmulator" { argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) rawRetOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - let addr, gasUsed := _genericPrecallLogic(rawAddr, rawArgsOffset, argsSize, rawRetOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, rawArgsOffset, argsSize, rawRetOffset, retSize, evmGasLeft) newGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(newGasLeft, gasToPass) @@ -828,19 +839,16 @@ object "EvmEmulator" { stackHead := success } - function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { + function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) -> addr, gasUsed { addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - gasUsed := 100 // warm address access cost if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { gasUsed := 2600 // cold address access cost } // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize, evmGasLeft)) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { @@ -1086,8 +1094,6 @@ object "EvmEmulator" { } function $llvm_NoInline_llvm$_genericCreate(offset, size, value, evmGasLeftOld, isCreate2, salt) -> evmGasLeft, addr { - checkMemIsAccessible(offset, size) - // EIP-3860 if gt(size, MAX_POSSIBLE_INIT_BYTECODE_LEN()) { panic() @@ -1101,7 +1107,7 @@ object "EvmEmulator" { let minimum_word_size := div(add(size, 31), 32) // rounding up let dynamicGas := add( mul(2, minimum_word_size), - expandMemory(offset, size) + expandMemory(offset, size, evmGasLeftOld) ) if isCreate2 { // hash_cost = 6 * minimum_word_size @@ -1296,10 +1302,8 @@ object "EvmEmulator" { rawOffset, newSp, newStackHead := popStackItemWithoutCheck(sp, stackHead) size, newSp, newStackHead := popStackItemWithoutCheck(newSp, newStackHead) - checkMemIsAccessible(rawOffset, size) - // dynamicGas = 375 * topic_count + 8 * size + memory_expansion_cost - let dynamicGas := add(shl(3, size), expandMemory(rawOffset, size)) + let dynamicGas := add(shl(3, size), expandMemory(rawOffset, size, newEvmGasLeft)) dynamicGas := add(dynamicGas, mul(375, topicCount)) newEvmGasLeft := chargeGas(newEvmGasLeft, dynamicGas) @@ -1615,13 +1619,11 @@ object "EvmEmulator" { popStackCheck(sp, 2) rawOffset, sp, size := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(rawOffset, size) - // When an offset is first accessed (either read or write), memory may trigger // an expansion, which costs gas. // dynamicGas = 6 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(6, shr(5, add(size, 31))), expandMemory(rawOffset, size)) + let dynamicGas := add(mul(6, shr(5, add(size, 31))), expandMemory(rawOffset, size, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) let offset @@ -1699,11 +1701,9 @@ object "EvmEmulator" { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) dstOffset := add(dstOffset, MEM_OFFSET()) @@ -1744,11 +1744,9 @@ object "EvmEmulator" { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) dstOffset := add(dstOffset, MEM_OFFSET()) @@ -1821,13 +1819,11 @@ object "EvmEmulator" { addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost + address_access_cost // minimum_word_size = (size + 31) / 32 let dynamicGas := add( mul(3, shr(5, add(len, 31))), - expandMemory(dstOffset, len) + expandMemory(dstOffset, len, evmGasLeft) ) if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { @@ -1869,14 +1865,12 @@ object "EvmEmulator" { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // minimum_word_size = (size + 31) / 32 // dynamicGas = 3 * minimum_word_size + memory_expansion_cost - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) - checkOverflow(sourceOffset, len) + checkOverflow(sourceOffset, len, evmGasLeft) // Check returndata out-of-bounds error if gt(add(sourceOffset, len), mload(LAST_RETURNDATA_SIZE_OFFSET())) { @@ -2018,9 +2012,7 @@ object "EvmEmulator" { let offset := accessStackHead(sp, stackHead) - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32, evmGasLeft)) stackHead := mload(add(MEM_OFFSET(), offset)) @@ -2035,9 +2027,7 @@ object "EvmEmulator" { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32, evmGasLeft)) mstore(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -2051,9 +2041,7 @@ object "EvmEmulator" { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, 1) - let expansionGas := expandMemory(offset, 1) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 1, evmGasLeft)) mstore8(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -2234,11 +2222,8 @@ object "EvmEmulator" { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, size) - checkMemIsAccessible(destOffset, size) - // dynamic_gas = 3 * words_copied + memory_expansion_cost - let dynamicGas := expandMemory2(offset, size, destOffset, size) + let dynamicGas := expandMemory2(offset, size, destOffset, size, evmGasLeft) let wordsCopied := div(add(size, 31), 32) // div rounding up dynamicGas := add(dynamicGas, mul(3, wordsCopied)) @@ -2750,9 +2735,7 @@ object "EvmEmulator" { size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) if size { - checkMemIsAccessible(offset, size) - - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size, evmGasLeft)) returnLen := size @@ -2789,8 +2772,7 @@ object "EvmEmulator" { switch iszero(size) case 0 { - checkMemIsAccessible(offset, size) - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size, evmGasLeft)) // Don't check overflow here since previous checks are enough to ensure this is safe offset := add(offset, MEM_OFFSET()) @@ -3371,7 +3353,7 @@ object "EvmEmulator" { function chargeGas(prevGas, toCharge) -> gasRemaining { if lt(prevGas, toCharge) { - panic() + revertWithGas(prevGas) } gasRemaining := sub(prevGas, toCharge) @@ -3385,62 +3367,73 @@ object "EvmEmulator" { } } - // This function can overflow, it is the job of the caller to ensure that it does not. // The argument to this function is the offset into the memory region IN BYTES. - function expandMemory(offset, size) -> gasCost { + function expandMemory(offset, size, evmGasLeft) -> gasCost { // memory expansion costs 0 if size is 0 if size { - let oldSizeInWords := mload(MEM_LEN_OFFSET()) - - // div rounding up - let newSizeInWords := div(add(add(offset, size), 31), 32) - - // memory_size_word = (memory_byte_size + 31) / 32 - // memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) - // memory_expansion_cost = new_memory_cost - last_memory_cost - if gt(newSizeInWords, oldSizeInWords) { - let linearPart := mul(3, sub(newSizeInWords, oldSizeInWords)) - let quadraticPart := sub( - div( - mul(newSizeInWords, newSizeInWords), - 512 - ), - div( - mul(oldSizeInWords, oldSizeInWords), - 512 - ) - ) - - gasCost := add(linearPart, quadraticPart) - - mstore(MEM_LEN_OFFSET(), newSizeInWords) - } + checkOverflow(offset, size, evmGasLeft) + gasCost := _expandMemoryInternal(add(offset, size), evmGasLeft) } } - function expandMemory2(retOffset, retSize, argsOffset, argsSize) -> maxExpand { - switch lt(add(retOffset, retSize), add(argsOffset, argsSize)) - case 0 { - maxExpand := expandMemory(retOffset, retSize) + // This function can overflow, it is the job of the caller to ensure that it does not. + // The argument to this function is the offset into the memory region IN BYTES. + function _expandMemoryInternal(newMemsize, evmGasLeft) -> gasCost { + if gt(newMemsize, MAX_POSSIBLE_MEM_LEN()) { + revertWithGas(evmGasLeft) // Not possible to pay for this memsize } - default { - maxExpand := expandMemory(argsOffset, argsSize) + + let oldSizeInWords := mload(MEM_LEN_OFFSET()) + + // div rounding up + let newSizeInWords := div(add(newMemsize, 31), 32) + + // memory_size_word = (memory_byte_size + 31) / 32 + // memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) + // memory_expansion_cost = new_memory_cost - last_memory_cost + if gt(newSizeInWords, oldSizeInWords) { + let linearPart := mul(3, sub(newSizeInWords, oldSizeInWords)) + let quadraticPart := sub( + div( + mul(newSizeInWords, newSizeInWords), + 512 + ), + div( + mul(oldSizeInWords, oldSizeInWords), + 512 + ) + ) + + gasCost := add(linearPart, quadraticPart) + + mstore(MEM_LEN_OFFSET(), newSizeInWords) } } - function checkMemIsAccessible(relativeOffset, size) { + // Returns 0 if size is 0 + function _memsizeRequired(offset, size, evmGasLeft) -> memorySize { if size { - checkOverflow(relativeOffset, size) + checkOverflow(offset, size, evmGasLeft) + memorySize := add(offset, size) + } + } - if gt(add(relativeOffset, size), MAX_POSSIBLE_MEM_LEN()) { - panic() - } + function expandMemory2(retOffset, retSize, argsOffset, argsSize, evmGasLeft) -> gasCost { + let maxNewMemsize := _memsizeRequired(retOffset, retSize, evmGasLeft) + let argsMemsize := _memsizeRequired(argsOffset, argsSize, evmGasLeft) + + if lt(maxNewMemsize, argsMemsize) { + maxNewMemsize := argsMemsize + } + + if maxNewMemsize { // Memory expansion costs 0 if size is 0 + gasCost := _expandMemoryInternal(maxNewMemsize, evmGasLeft) } } - function checkOverflow(data1, data2) { + function checkOverflow(data1, data2, evmGasLeft) { if lt(add(data1, data2), data2) { - panic() + revertWithGas(evmGasLeft) } } @@ -3839,7 +3832,7 @@ object "EvmEmulator" { // If value is not 0, then positive_value_cost is 9000. In this case there is also a call stipend that is given to make sure that a basic fallback function can be called. // If value is not 0 and the address given points to an empty account, then value_to_empty_account_cost is 25000. An account is empty if its balance is 0, its nonce is 0 and it has no code. - let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) if gt(value, 0) { if isStatic { @@ -3886,7 +3879,7 @@ object "EvmEmulator" { argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) evmGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(evmGasLeft, gasToPass) @@ -3918,7 +3911,7 @@ object "EvmEmulator" { argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) rawRetOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - let addr, gasUsed := _genericPrecallLogic(rawAddr, rawArgsOffset, argsSize, rawRetOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, rawArgsOffset, argsSize, rawRetOffset, retSize, evmGasLeft) newGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(newGasLeft, gasToPass) @@ -3974,19 +3967,16 @@ object "EvmEmulator" { stackHead := success } - function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { + function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) -> addr, gasUsed { addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - gasUsed := 100 // warm address access cost if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { gasUsed := 2600 // cold address access cost } // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize, evmGasLeft)) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { @@ -4232,8 +4222,6 @@ object "EvmEmulator" { } function $llvm_NoInline_llvm$_genericCreate(offset, size, value, evmGasLeftOld, isCreate2, salt) -> evmGasLeft, addr { - checkMemIsAccessible(offset, size) - // EIP-3860 if gt(size, MAX_POSSIBLE_INIT_BYTECODE_LEN()) { panic() @@ -4247,7 +4235,7 @@ object "EvmEmulator" { let minimum_word_size := div(add(size, 31), 32) // rounding up let dynamicGas := add( mul(2, minimum_word_size), - expandMemory(offset, size) + expandMemory(offset, size, evmGasLeftOld) ) if isCreate2 { // hash_cost = 6 * minimum_word_size @@ -4442,10 +4430,8 @@ object "EvmEmulator" { rawOffset, newSp, newStackHead := popStackItemWithoutCheck(sp, stackHead) size, newSp, newStackHead := popStackItemWithoutCheck(newSp, newStackHead) - checkMemIsAccessible(rawOffset, size) - // dynamicGas = 375 * topic_count + 8 * size + memory_expansion_cost - let dynamicGas := add(shl(3, size), expandMemory(rawOffset, size)) + let dynamicGas := add(shl(3, size), expandMemory(rawOffset, size, newEvmGasLeft)) dynamicGas := add(dynamicGas, mul(375, topicCount)) newEvmGasLeft := chargeGas(newEvmGasLeft, dynamicGas) @@ -4749,13 +4735,11 @@ object "EvmEmulator" { popStackCheck(sp, 2) rawOffset, sp, size := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(rawOffset, size) - // When an offset is first accessed (either read or write), memory may trigger // an expansion, which costs gas. // dynamicGas = 6 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(6, shr(5, add(size, 31))), expandMemory(rawOffset, size)) + let dynamicGas := add(mul(6, shr(5, add(size, 31))), expandMemory(rawOffset, size, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) let offset @@ -4833,11 +4817,9 @@ object "EvmEmulator" { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) dstOffset := add(dstOffset, MEM_OFFSET()) @@ -4878,11 +4860,9 @@ object "EvmEmulator" { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) dstOffset := add(dstOffset, MEM_OFFSET()) @@ -4955,13 +4935,11 @@ object "EvmEmulator" { addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost + address_access_cost // minimum_word_size = (size + 31) / 32 let dynamicGas := add( mul(3, shr(5, add(len, 31))), - expandMemory(dstOffset, len) + expandMemory(dstOffset, len, evmGasLeft) ) if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { @@ -5003,14 +4981,12 @@ object "EvmEmulator" { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // minimum_word_size = (size + 31) / 32 // dynamicGas = 3 * minimum_word_size + memory_expansion_cost - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) - checkOverflow(sourceOffset, len) + checkOverflow(sourceOffset, len, evmGasLeft) // Check returndata out-of-bounds error if gt(add(sourceOffset, len), mload(LAST_RETURNDATA_SIZE_OFFSET())) { @@ -5152,9 +5128,7 @@ object "EvmEmulator" { let offset := accessStackHead(sp, stackHead) - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32, evmGasLeft)) stackHead := mload(add(MEM_OFFSET(), offset)) @@ -5169,9 +5143,7 @@ object "EvmEmulator" { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32, evmGasLeft)) mstore(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -5185,9 +5157,7 @@ object "EvmEmulator" { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, 1) - let expansionGas := expandMemory(offset, 1) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 1, evmGasLeft)) mstore8(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -5368,11 +5338,8 @@ object "EvmEmulator" { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, size) - checkMemIsAccessible(destOffset, size) - // dynamic_gas = 3 * words_copied + memory_expansion_cost - let dynamicGas := expandMemory2(offset, size, destOffset, size) + let dynamicGas := expandMemory2(offset, size, destOffset, size, evmGasLeft) let wordsCopied := div(add(size, 31), 32) // div rounding up dynamicGas := add(dynamicGas, mul(3, wordsCopied)) @@ -5884,9 +5851,7 @@ object "EvmEmulator" { size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) if size { - checkMemIsAccessible(offset, size) - - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size, evmGasLeft)) returnLen := size @@ -5923,8 +5888,7 @@ object "EvmEmulator" { switch iszero(size) case 0 { - checkMemIsAccessible(offset, size) - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size, evmGasLeft)) // Don't check overflow here since previous checks are enough to ensure this is safe offset := add(offset, MEM_OFFSET()) @@ -6306,7 +6270,7 @@ object "EvmEmulator" { if eq(isCallerEVM, 1) { // Includes gas returnOffset := sub(returnOffset, 32) - checkOverflow(returnLen, 32) + checkOverflow(returnLen, 32, evmGasLeft) returnLen := add(returnLen, 32) mstore(returnOffset, evmGasLeft) diff --git a/system-contracts/evm-emulator/EvmEmulator.template.yul b/system-contracts/evm-emulator/EvmEmulator.template.yul index 22d188075..45c0cc9e1 100644 --- a/system-contracts/evm-emulator/EvmEmulator.template.yul +++ b/system-contracts/evm-emulator/EvmEmulator.template.yul @@ -141,7 +141,7 @@ object "EvmEmulator" { if eq(isCallerEVM, 1) { // Includes gas returnOffset := sub(returnOffset, 32) - checkOverflow(returnLen, 32) + checkOverflow(returnLen, 32, evmGasLeft) returnLen := add(returnLen, 32) mstore(returnOffset, evmGasLeft) diff --git a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul index 61feb9235..732c898c4 100644 --- a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul @@ -165,7 +165,7 @@ function cached(cacheIndex, value) -> _value { function chargeGas(prevGas, toCharge) -> gasRemaining { if lt(prevGas, toCharge) { - panic() + revertWithGas(prevGas) } gasRemaining := sub(prevGas, toCharge) @@ -179,62 +179,73 @@ function getEvmGasFromContext() -> evmGas { } } -// This function can overflow, it is the job of the caller to ensure that it does not. // The argument to this function is the offset into the memory region IN BYTES. -function expandMemory(offset, size) -> gasCost { +function expandMemory(offset, size, evmGasLeft) -> gasCost { // memory expansion costs 0 if size is 0 if size { - let oldSizeInWords := mload(MEM_LEN_OFFSET()) - - // div rounding up - let newSizeInWords := div(add(add(offset, size), 31), 32) - - // memory_size_word = (memory_byte_size + 31) / 32 - // memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) - // memory_expansion_cost = new_memory_cost - last_memory_cost - if gt(newSizeInWords, oldSizeInWords) { - let linearPart := mul(3, sub(newSizeInWords, oldSizeInWords)) - let quadraticPart := sub( - div( - mul(newSizeInWords, newSizeInWords), - 512 - ), - div( - mul(oldSizeInWords, oldSizeInWords), - 512 - ) - ) - - gasCost := add(linearPart, quadraticPart) - - mstore(MEM_LEN_OFFSET(), newSizeInWords) - } + checkOverflow(offset, size, evmGasLeft) + gasCost := _expandMemoryInternal(add(offset, size), evmGasLeft) } } -function expandMemory2(retOffset, retSize, argsOffset, argsSize) -> maxExpand { - switch lt(add(retOffset, retSize), add(argsOffset, argsSize)) - case 0 { - maxExpand := expandMemory(retOffset, retSize) +// This function can overflow, it is the job of the caller to ensure that it does not. +// The argument to this function is the offset into the memory region IN BYTES. +function _expandMemoryInternal(newMemsize, evmGasLeft) -> gasCost { + if gt(newMemsize, MAX_POSSIBLE_MEM_LEN()) { + revertWithGas(evmGasLeft) // Not possible to pay for this memsize } - default { - maxExpand := expandMemory(argsOffset, argsSize) + + let oldSizeInWords := mload(MEM_LEN_OFFSET()) + + // div rounding up + let newSizeInWords := div(add(newMemsize, 31), 32) + + // memory_size_word = (memory_byte_size + 31) / 32 + // memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) + // memory_expansion_cost = new_memory_cost - last_memory_cost + if gt(newSizeInWords, oldSizeInWords) { + let linearPart := mul(3, sub(newSizeInWords, oldSizeInWords)) + let quadraticPart := sub( + div( + mul(newSizeInWords, newSizeInWords), + 512 + ), + div( + mul(oldSizeInWords, oldSizeInWords), + 512 + ) + ) + + gasCost := add(linearPart, quadraticPart) + + mstore(MEM_LEN_OFFSET(), newSizeInWords) } } -function checkMemIsAccessible(relativeOffset, size) { +// Returns 0 if size is 0 +function _memsizeRequired(offset, size, evmGasLeft) -> memorySize { if size { - checkOverflow(relativeOffset, size) + checkOverflow(offset, size, evmGasLeft) + memorySize := add(offset, size) + } +} - if gt(add(relativeOffset, size), MAX_POSSIBLE_MEM_LEN()) { - panic() - } +function expandMemory2(retOffset, retSize, argsOffset, argsSize, evmGasLeft) -> gasCost { + let maxNewMemsize := _memsizeRequired(retOffset, retSize, evmGasLeft) + let argsMemsize := _memsizeRequired(argsOffset, argsSize, evmGasLeft) + + if lt(maxNewMemsize, argsMemsize) { + maxNewMemsize := argsMemsize + } + + if maxNewMemsize { // Memory expansion costs 0 if size is 0 + gasCost := _expandMemoryInternal(maxNewMemsize, evmGasLeft) } } -function checkOverflow(data1, data2) { +function checkOverflow(data1, data2, evmGasLeft) { if lt(add(data1, data2), data2) { - panic() + revertWithGas(evmGasLeft) } } @@ -633,7 +644,7 @@ function performCall(oldSp, evmGasLeft, oldStackHead, isStatic) -> newGasLeft, s // If value is not 0, then positive_value_cost is 9000. In this case there is also a call stipend that is given to make sure that a basic fallback function can be called. // If value is not 0 and the address given points to an empty account, then value_to_empty_account_cost is 25000. An account is empty if its balance is 0, its nonce is 0 and it has no code. - let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) if gt(value, 0) { if isStatic { @@ -680,7 +691,7 @@ function performStaticCall(oldSp, evmGasLeft, oldStackHead) -> newGasLeft, sp, s argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) retOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) evmGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(evmGasLeft, gasToPass) @@ -712,7 +723,7 @@ function performDelegateCall(oldSp, evmGasLeft, isStatic, oldStackHead) -> newGa argsSize, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) rawRetOffset, sp, retSize := popStackItemWithoutCheck(sp, stackHead) - let addr, gasUsed := _genericPrecallLogic(rawAddr, rawArgsOffset, argsSize, rawRetOffset, retSize) + let addr, gasUsed := _genericPrecallLogic(rawAddr, rawArgsOffset, argsSize, rawRetOffset, retSize, evmGasLeft) newGasLeft := chargeGas(evmGasLeft, gasUsed) gasToPass := capGasForCall(newGasLeft, gasToPass) @@ -768,19 +779,16 @@ function performDelegateCall(oldSp, evmGasLeft, isStatic, oldStackHead) -> newGa stackHead := success } -function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { +function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize, evmGasLeft) -> addr, gasUsed { addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) - gasUsed := 100 // warm address access cost if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { gasUsed := 2600 // cold address access cost } // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize, evmGasLeft)) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { @@ -1026,8 +1034,6 @@ function performCreate2(oldEvmGasLeft, oldSp, oldStackHead) -> evmGasLeft, sp, s } function $llvm_NoInline_llvm$_genericCreate(offset, size, value, evmGasLeftOld, isCreate2, salt) -> evmGasLeft, addr { - checkMemIsAccessible(offset, size) - // EIP-3860 if gt(size, MAX_POSSIBLE_INIT_BYTECODE_LEN()) { panic() @@ -1041,7 +1047,7 @@ function $llvm_NoInline_llvm$_genericCreate(offset, size, value, evmGasLeftOld, let minimum_word_size := div(add(size, 31), 32) // rounding up let dynamicGas := add( mul(2, minimum_word_size), - expandMemory(offset, size) + expandMemory(offset, size, evmGasLeftOld) ) if isCreate2 { // hash_cost = 6 * minimum_word_size @@ -1236,10 +1242,8 @@ function _genericLog(sp, stackHead, evmGasLeft, topicCount, isStatic) -> newEvmG rawOffset, newSp, newStackHead := popStackItemWithoutCheck(sp, stackHead) size, newSp, newStackHead := popStackItemWithoutCheck(newSp, newStackHead) - checkMemIsAccessible(rawOffset, size) - // dynamicGas = 375 * topic_count + 8 * size + memory_expansion_cost - let dynamicGas := add(shl(3, size), expandMemory(rawOffset, size)) + let dynamicGas := add(shl(3, size), expandMemory(rawOffset, size, newEvmGasLeft)) dynamicGas := add(dynamicGas, mul(375, topicCount)) newEvmGasLeft := chargeGas(newEvmGasLeft, dynamicGas) diff --git a/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul b/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul index d5056dd19..71c6b4f64 100644 --- a/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul @@ -283,13 +283,11 @@ for { } true { } { popStackCheck(sp, 2) rawOffset, sp, size := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(rawOffset, size) - // When an offset is first accessed (either read or write), memory may trigger // an expansion, which costs gas. // dynamicGas = 6 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(6, shr(5, add(size, 31))), expandMemory(rawOffset, size)) + let dynamicGas := add(mul(6, shr(5, add(size, 31))), expandMemory(rawOffset, size, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) let offset @@ -367,11 +365,9 @@ for { } true { } { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) dstOffset := add(dstOffset, MEM_OFFSET()) @@ -412,11 +408,9 @@ for { } true { } { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost // minimum_word_size = (size + 31) / 32 - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) dstOffset := add(dstOffset, MEM_OFFSET()) @@ -489,13 +483,11 @@ for { } true { } { addr := and(addr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(dstOffset, len) - // dynamicGas = 3 * minimum_word_size + memory_expansion_cost + address_access_cost // minimum_word_size = (size + 31) / 32 let dynamicGas := add( mul(3, shr(5, add(len, 31))), - expandMemory(dstOffset, len) + expandMemory(dstOffset, len, evmGasLeft) ) if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { @@ -537,14 +529,12 @@ for { } true { } { sourceOffset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(dstOffset, len) - // minimum_word_size = (size + 31) / 32 // dynamicGas = 3 * minimum_word_size + memory_expansion_cost - let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len)) + let dynamicGas := add(mul(3, shr(5, add(len, 31))), expandMemory(dstOffset, len, evmGasLeft)) evmGasLeft := chargeGas(evmGasLeft, dynamicGas) - checkOverflow(sourceOffset, len) + checkOverflow(sourceOffset, len, evmGasLeft) // Check returndata out-of-bounds error if gt(add(sourceOffset, len), mload(LAST_RETURNDATA_SIZE_OFFSET())) { @@ -686,9 +676,7 @@ for { } true { } { let offset := accessStackHead(sp, stackHead) - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32, evmGasLeft)) stackHead := mload(add(MEM_OFFSET(), offset)) @@ -703,9 +691,7 @@ for { } true { } { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32, evmGasLeft)) mstore(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -719,9 +705,7 @@ for { } true { } { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) value, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, 1) - let expansionGas := expandMemory(offset, 1) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 1, evmGasLeft)) mstore8(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -902,11 +886,8 @@ for { } true { } { offset, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) - checkMemIsAccessible(offset, size) - checkMemIsAccessible(destOffset, size) - // dynamic_gas = 3 * words_copied + memory_expansion_cost - let dynamicGas := expandMemory2(offset, size, destOffset, size) + let dynamicGas := expandMemory2(offset, size, destOffset, size, evmGasLeft) let wordsCopied := div(add(size, 31), 32) // div rounding up dynamicGas := add(dynamicGas, mul(3, wordsCopied)) @@ -1418,9 +1399,7 @@ for { } true { } { size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) if size { - checkMemIsAccessible(offset, size) - - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size, evmGasLeft)) returnLen := size @@ -1457,8 +1436,7 @@ for { } true { } { switch iszero(size) case 0 { - checkMemIsAccessible(offset, size) - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size, evmGasLeft)) // Don't check overflow here since previous checks are enough to ensure this is safe offset := add(offset, MEM_OFFSET())