From 7874da4ddae9f78c8de8ec5c16cead2db28c5904 Mon Sep 17 00:00:00 2001 From: Vladislav Volosnikov Date: Mon, 30 Dec 2024 16:33:58 +0100 Subject: [PATCH] Change memory expansion checks --- system-contracts/contracts/EvmEmulator.yul | 268 ++++++++---------- .../EvmEmulatorFunctions.template.yul | 100 ++++--- .../evm-emulator/EvmEmulatorLoop.template.yul | 33 +-- 3 files changed, 175 insertions(+), 226 deletions(-) diff --git a/system-contracts/contracts/EvmEmulator.yul b/system-contracts/contracts/EvmEmulator.yul index 5b41e6643..ba2ad472f 100644 --- a/system-contracts/contracts/EvmEmulator.yul +++ b/system-contracts/contracts/EvmEmulator.yul @@ -239,56 +239,67 @@ 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 { // memory expansion costs 0 if size is 0 if size { - let oldSizeInWords := mload(MEM_LEN_OFFSET()) + checkOverflow(offset, size) + gasCost := _expandMemoryInternal(add(offset, size)) + } + } - // 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 new size of memory IN BYTES. + function _expandMemoryInternal(newMemsize) -> gasCost { + if gt(newMemsize, MAX_POSSIBLE_MEM_LEN()) { + panic() + } + + 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) -> memorySize { + if size { + checkOverflow(offset, size) + memorySize := add(offset, size) } } - function checkMemIsAccessible(relativeOffset, size) { - if size { - checkOverflow(relativeOffset, size) + function expandMemory2(retOffset, retSize, argsOffset, argsSize) -> maxExpand { + let maxNewMemsize := _memsizeRequired(retOffset, retSize) + let argsMemsize := _memsizeRequired(argsOffset, argsSize) - 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 + maxExpand := _expandMemoryInternal(maxNewMemsize) } } @@ -831,16 +842,15 @@ object "EvmEmulator" { function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) + // memory_expansion_cost + gasUsed := expandMemory2(retOffset, retSize, argsOffset, argsSize) - gasUsed := 100 // warm address access cost + let addressAccessCost := 100 // warm address access cost if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost + addressAccessCost := 2600 // cold address access cost } - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + gasUsed := add(gasUsed, addressAccessCost) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { @@ -1086,8 +1096,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() @@ -1296,8 +1304,6 @@ 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)) dynamicGas := add(dynamicGas, mul(375, topicCount)) @@ -1615,8 +1621,6 @@ 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 @@ -1699,8 +1703,6 @@ 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)) @@ -1744,8 +1746,6 @@ 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)) @@ -1820,9 +1820,7 @@ object "EvmEmulator" { len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) 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( @@ -1869,8 +1867,6 @@ 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)) @@ -2017,10 +2013,7 @@ object "EvmEmulator" { evmGasLeft := chargeGas(evmGasLeft, 3) let offset := accessStackHead(sp, stackHead) - - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32)) stackHead := mload(add(MEM_OFFSET(), offset)) @@ -2035,9 +2028,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)) mstore(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -2051,9 +2042,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)) mstore8(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -2234,9 +2223,6 @@ 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 wordsCopied := div(add(size, 31), 32) // div rounding up @@ -2750,8 +2736,6 @@ object "EvmEmulator" { size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) if size { - checkMemIsAccessible(offset, size) - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) returnLen := size @@ -2789,10 +2773,9 @@ object "EvmEmulator" { switch iszero(size) case 0 { - checkMemIsAccessible(offset, size) evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) - // Don't check overflow here since previous checks are enough to ensure this is safe + // Don't check overflow here since check in expandMemory is enough to ensure this is safe offset := add(offset, MEM_OFFSET()) } default { @@ -3385,56 +3368,67 @@ 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 { // 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) + gasCost := _expandMemoryInternal(add(offset, size)) } } - 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) + // This function can overflow, it is the job of the caller to ensure that it does not. + // The argument to this function is the new size of memory IN BYTES. + function _expandMemoryInternal(newMemsize) -> gasCost { + if gt(newMemsize, MAX_POSSIBLE_MEM_LEN()) { + panic() + } + + 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) -> memorySize { if size { - checkOverflow(relativeOffset, size) + checkOverflow(offset, size) + memorySize := add(offset, size) + } + } - if gt(add(relativeOffset, size), MAX_POSSIBLE_MEM_LEN()) { - panic() - } + function expandMemory2(retOffset, retSize, argsOffset, argsSize) -> maxExpand { + let maxNewMemsize := _memsizeRequired(retOffset, retSize) + let argsMemsize := _memsizeRequired(argsOffset, argsSize) + + if lt(maxNewMemsize, argsMemsize) { + maxNewMemsize := argsMemsize + } + + if maxNewMemsize { // Memory expansion costs 0 if size is 0 + maxExpand := _expandMemoryInternal(maxNewMemsize) } } @@ -3977,16 +3971,15 @@ object "EvmEmulator" { function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) + // memory_expansion_cost + gasUsed := expandMemory2(retOffset, retSize, argsOffset, argsSize) - gasUsed := 100 // warm address access cost + let addressAccessCost := 100 // warm address access cost if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost + addressAccessCost := 2600 // cold address access cost } - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + gasUsed := add(gasUsed, addressAccessCost) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { @@ -4232,8 +4225,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() @@ -4442,8 +4433,6 @@ 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)) dynamicGas := add(dynamicGas, mul(375, topicCount)) @@ -4749,8 +4738,6 @@ 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 @@ -4833,8 +4820,6 @@ 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)) @@ -4878,8 +4863,6 @@ 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)) @@ -4954,9 +4937,7 @@ object "EvmEmulator" { len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) 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( @@ -5003,8 +4984,6 @@ 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)) @@ -5151,10 +5130,7 @@ object "EvmEmulator" { evmGasLeft := chargeGas(evmGasLeft, 3) let offset := accessStackHead(sp, stackHead) - - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32)) stackHead := mload(add(MEM_OFFSET(), offset)) @@ -5169,9 +5145,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)) mstore(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -5185,9 +5159,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)) mstore8(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -5368,9 +5340,6 @@ 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 wordsCopied := div(add(size, 31), 32) // div rounding up @@ -5884,8 +5853,6 @@ object "EvmEmulator" { size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) if size { - checkMemIsAccessible(offset, size) - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) returnLen := size @@ -5923,10 +5890,9 @@ object "EvmEmulator" { switch iszero(size) case 0 { - checkMemIsAccessible(offset, size) evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) - // Don't check overflow here since previous checks are enough to ensure this is safe + // Don't check overflow here since check in expandMemory is enough to ensure this is safe offset := add(offset, MEM_OFFSET()) } default { diff --git a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul index 61feb9235..a9253a8cd 100644 --- a/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul @@ -179,56 +179,67 @@ 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 { // memory expansion costs 0 if size is 0 if size { - let oldSizeInWords := mload(MEM_LEN_OFFSET()) + checkOverflow(offset, size) + gasCost := _expandMemoryInternal(add(offset, size)) + } +} - // 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 new size of memory IN BYTES. +function _expandMemoryInternal(newMemsize) -> gasCost { + if gt(newMemsize, MAX_POSSIBLE_MEM_LEN()) { + panic() + } + + 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) -> memorySize { + if size { + checkOverflow(offset, size) + memorySize := add(offset, size) } } -function checkMemIsAccessible(relativeOffset, size) { - if size { - checkOverflow(relativeOffset, size) +function expandMemory2(retOffset, retSize, argsOffset, argsSize) -> maxExpand { + let maxNewMemsize := _memsizeRequired(retOffset, retSize) + let argsMemsize := _memsizeRequired(argsOffset, argsSize) - 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 + maxExpand := _expandMemoryInternal(maxNewMemsize) } } @@ -771,16 +782,15 @@ function performDelegateCall(oldSp, evmGasLeft, isStatic, oldStackHead) -> newGa function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize) -> addr, gasUsed { addr := and(rawAddr, 0xffffffffffffffffffffffffffffffffffffffff) - checkMemIsAccessible(argsOffset, argsSize) - checkMemIsAccessible(retOffset, retSize) + // memory_expansion_cost + gasUsed := expandMemory2(retOffset, retSize, argsOffset, argsSize) - gasUsed := 100 // warm address access cost + let addressAccessCost := 100 // warm address access cost if iszero($llvm_AlwaysInline_llvm$_warmAddress(addr)) { - gasUsed := 2600 // cold address access cost + addressAccessCost := 2600 // cold address access cost } - // memory_expansion_cost - gasUsed := add(gasUsed, expandMemory2(retOffset, retSize, argsOffset, argsSize)) + gasUsed := add(gasUsed, addressAccessCost) } function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft { @@ -1026,8 +1036,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() @@ -1236,8 +1244,6 @@ 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)) dynamicGas := add(dynamicGas, mul(375, topicCount)) diff --git a/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul b/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul index d5056dd19..58e959514 100644 --- a/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul +++ b/system-contracts/evm-emulator/EvmEmulatorLoop.template.yul @@ -283,8 +283,6 @@ 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 @@ -367,8 +365,6 @@ 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)) @@ -412,8 +408,6 @@ 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)) @@ -488,9 +482,7 @@ for { } true { } { len, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) 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( @@ -537,8 +529,6 @@ 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)) @@ -685,10 +675,7 @@ for { } true { } { evmGasLeft := chargeGas(evmGasLeft, 3) let offset := accessStackHead(sp, stackHead) - - checkMemIsAccessible(offset, 32) - let expansionGas := expandMemory(offset, 32) - evmGasLeft := chargeGas(evmGasLeft, expansionGas) + evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, 32)) stackHead := mload(add(MEM_OFFSET(), offset)) @@ -703,9 +690,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)) mstore(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -719,9 +704,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)) mstore8(add(MEM_OFFSET(), offset), value) ip := add(ip, 1) @@ -902,9 +885,6 @@ 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 wordsCopied := div(add(size, 31), 32) // div rounding up @@ -1418,8 +1398,6 @@ for { } true { } { size, sp, stackHead := popStackItemWithoutCheck(sp, stackHead) if size { - checkMemIsAccessible(offset, size) - evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) returnLen := size @@ -1457,10 +1435,9 @@ for { } true { } { switch iszero(size) case 0 { - checkMemIsAccessible(offset, size) evmGasLeft := chargeGas(evmGasLeft, expandMemory(offset, size)) - // Don't check overflow here since previous checks are enough to ensure this is safe + // Don't check overflow here since check in expandMemory is enough to ensure this is safe offset := add(offset, MEM_OFFSET()) } default {