From 6fc83b59130feeb06900081f58c6495b9d740b38 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 19 Jul 2021 11:00:21 +0200 Subject: [PATCH 01/17] [ETCM-912] Add Magneto block height to EVM config --- src/main/scala/io/iohk/ethereum/extvm/VMServer.scala | 1 + .../scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala | 6 +++++- src/test/scala/io/iohk/ethereum/extvm/VMClientSpec.scala | 1 + src/test/scala/io/iohk/ethereum/vm/Fixtures.scala | 2 ++ src/test/scala/io/iohk/ethereum/vm/VMSpec.scala | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/scala/io/iohk/ethereum/extvm/VMServer.scala b/src/main/scala/io/iohk/ethereum/extvm/VMServer.scala index 38290fa3e8..e4bb0e1a60 100644 --- a/src/main/scala/io/iohk/ethereum/extvm/VMServer.scala +++ b/src/main/scala/io/iohk/ethereum/extvm/VMServer.scala @@ -207,6 +207,7 @@ class VMServer(messageHandler: MessageHandler) extends Logger { aghartaBlockNumber = BigInt(9573000), //TODO include agharta block number in protobuf petersburgBlockNumber = BigInt(10000000), //TODO include petersburg block number in protobuf phoenixBlockNumber = BigInt(10500839), //TODO include phoenix block number in protobuf + magnetoBlockNumber = BigInt(13189133), //TODO include magneto block number in protobuf chainId = 0x3d.toByte //TODO include chainId in protobuf ) } diff --git a/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala b/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala index 49368c1167..3e4dbee76e 100644 --- a/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala +++ b/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala @@ -6,6 +6,7 @@ import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.Agharta import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.Atlantis import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.BeforeAtlantis import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.EtcFork +import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.Magneto import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.Phoenix import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.BeforeByzantium import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.Byzantium @@ -35,13 +36,15 @@ case class BlockchainConfigForEvm( aghartaBlockNumber: BigInt, petersburgBlockNumber: BigInt, phoenixBlockNumber: BigInt, + magnetoBlockNumber: BigInt, chainId: Byte ) { def etcForkForBlockNumber(blockNumber: BigInt): EtcFork = blockNumber match { case _ if blockNumber < atlantisBlockNumber => BeforeAtlantis case _ if blockNumber < aghartaBlockNumber => Atlantis case _ if blockNumber < phoenixBlockNumber => Agharta - case _ if blockNumber >= phoenixBlockNumber => Phoenix + case _ if blockNumber < magnetoBlockNumber => Phoenix + case _ if blockNumber >= magnetoBlockNumber => Magneto } def ethForkForBlockNumber(blockNumber: BigInt): BlockchainConfigForEvm.EthForks.Value = blockNumber match { @@ -82,6 +85,7 @@ object BlockchainConfigForEvm { aghartaBlockNumber = forkBlockNumbers.aghartaBlockNumber, petersburgBlockNumber = forkBlockNumbers.petersburgBlockNumber, phoenixBlockNumber = forkBlockNumbers.phoenixBlockNumber, + magnetoBlockNumber = forkBlockNumbers.magnetoBlockNumber, chainId = chainId ) } diff --git a/src/test/scala/io/iohk/ethereum/extvm/VMClientSpec.scala b/src/test/scala/io/iohk/ethereum/extvm/VMClientSpec.scala index 428c719582..06defaf3ef 100644 --- a/src/test/scala/io/iohk/ethereum/extvm/VMClientSpec.scala +++ b/src/test/scala/io/iohk/ethereum/extvm/VMClientSpec.scala @@ -195,6 +195,7 @@ class VMClientSpec extends AnyFlatSpec with Matchers with MockFactory { aghartaBlockNumber = 0, petersburgBlockNumber = 0, phoenixBlockNumber = 0, + magnetoBlockNumber = 0, chainId = 0x3d.toByte ) val evmConfig: EvmConfig = EvmConfig.FrontierConfigBuilder(blockchainConfigForEvm) diff --git a/src/test/scala/io/iohk/ethereum/vm/Fixtures.scala b/src/test/scala/io/iohk/ethereum/vm/Fixtures.scala index 72a090564f..cc6f3691f8 100644 --- a/src/test/scala/io/iohk/ethereum/vm/Fixtures.scala +++ b/src/test/scala/io/iohk/ethereum/vm/Fixtures.scala @@ -6,6 +6,7 @@ object Fixtures { val PetersburgBlockNumber = 400 val PhoenixBlockNumber = 600 val IstanbulBlockNumber = 600 + val MagnetoBlockNumber = 700 val blockchainConfig: BlockchainConfigForEvm = BlockchainConfigForEvm( // block numbers are irrelevant @@ -23,6 +24,7 @@ object Fixtures { aghartaBlockNumber = 0, petersburgBlockNumber = PetersburgBlockNumber, phoenixBlockNumber = PhoenixBlockNumber, + magnetoBlockNumber = MagnetoBlockNumber, chainId = 0x3d.toByte ) diff --git a/src/test/scala/io/iohk/ethereum/vm/VMSpec.scala b/src/test/scala/io/iohk/ethereum/vm/VMSpec.scala index 41e5e0c112..a7b7e67d86 100644 --- a/src/test/scala/io/iohk/ethereum/vm/VMSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/VMSpec.scala @@ -162,6 +162,7 @@ class VMSpec extends AnyWordSpec with ScalaCheckPropertyChecks with Matchers { aghartaBlockNumber = Long.MaxValue, petersburgBlockNumber = Long.MaxValue, phoenixBlockNumber = Long.MaxValue, + magnetoBlockNumber = Long.MaxValue, chainId = 0x3d.toByte ) From 4385f87f13f636fe2ecd40298f6af3cb216118a9 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 19 Jul 2021 15:15:06 +0200 Subject: [PATCH 02/17] [ETCM-912] Add accessedAddresses and accessedStorageKeys to ProgramState --- .../ethereum/vm/PrecompiledContracts.scala | 24 +++++++++++-------- .../io/iohk/ethereum/vm/ProgramState.scala | 17 +++++++++++-- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/PrecompiledContracts.scala b/src/main/scala/io/iohk/ethereum/vm/PrecompiledContracts.scala index 1aa8e5757d..31f5dbcd22 100644 --- a/src/main/scala/io/iohk/ethereum/vm/PrecompiledContracts.scala +++ b/src/main/scala/io/iohk/ethereum/vm/PrecompiledContracts.scala @@ -63,18 +63,22 @@ object PrecompiledContracts { private def getContract(context: ProgramContext[_, _]): Option[PrecompiledContract] = context.recipientAddr.flatMap { addr => - val ethFork = context.evmConfig.blockchainConfig.ethForkForBlockNumber(context.blockHeader.number) - val etcFork = context.evmConfig.blockchainConfig.etcForkForBlockNumber(context.blockHeader.number) - - if (ethFork >= EthForks.Istanbul || etcFork >= EtcForks.Phoenix) { - istanbulPhoenixContracts.get(addr) - } else if (ethFork >= EthForks.Byzantium || etcFork >= EtcForks.Atlantis) { - // byzantium and atlantis hard fork introduce the same set of precompiled contracts - byzantiumAtlantisContracts.get(addr) - } else - contracts.get(addr) + getContracts(context).get(addr) } + def getContracts(context: ProgramContext[_, _]): Map[Address, PrecompiledContract] = { + val ethFork = context.evmConfig.blockchainConfig.ethForkForBlockNumber(context.blockHeader.number) + val etcFork = context.evmConfig.blockchainConfig.etcForkForBlockNumber(context.blockHeader.number) + + if (ethFork >= EthForks.Istanbul || etcFork >= EtcForks.Phoenix) { + istanbulPhoenixContracts + } else if (ethFork >= EthForks.Byzantium || etcFork >= EtcForks.Atlantis) { + // byzantium and atlantis hard fork introduce the same set of precompiled contracts + byzantiumAtlantisContracts + } else + contracts + } + sealed trait PrecompiledContract { protected def exec(inputData: ByteString): Option[ByteString] protected def gas(inputData: ByteString, etcFork: EtcFork, ethFork: EthFork): BigInt diff --git a/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala b/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala index 65c260d488..b9abcfb17f 100644 --- a/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala +++ b/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala @@ -19,7 +19,12 @@ object ProgramState { world = context.world, staticCtx = context.staticCtx, addressesToDelete = context.initialAddressesToDelete, - originalWorld = context.originalWorld + originalWorld = context.originalWorld, + accessedAddresses = PrecompiledContracts.getContracts(context).keySet ++ Set( + context.originAddr, + context.recipientAddr.getOrElse(context.callerAddr) + ), + accessedStorageKeys = Set.empty ) } @@ -58,7 +63,9 @@ case class ProgramState[W <: WorldStateProxy[W, S], S <: Storage[S]]( halted: Boolean = false, staticCtx: Boolean = false, error: Option[ProgramError] = None, - originalWorld: W + originalWorld: W, + accessedAddresses: Set[Address], + accessedStorageKeys: Set[(Address, BigInt)] ) { def config: EvmConfig = env.evmConfig @@ -126,6 +133,12 @@ case class ProgramState[W <: WorldStateProxy[W, S], S <: Storage[S]]( def revert(data: ByteString): ProgramState[W, S] = copy(error = Some(RevertOccurs), returnData = data, halted = true) + def addAccessedAddress(addr: Address): ProgramState[W, S] = + copy(accessedAddresses = accessedAddresses.+(addr)) + + def addAccessedStorageKey(addr: Address, storageKey: BigInt): ProgramState[W, S] = + copy(accessedStorageKeys = accessedStorageKeys.+((addr, storageKey))) + def toResult: ProgramResult[W, S] = ProgramResult[W, S]( returnData, From 97137837f38725ee5a69f242ab387156d3ef6e4b Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 19 Jul 2021 15:15:55 +0200 Subject: [PATCH 03/17] [ETCM-912] Add newly created address to accessedaddresses --- src/main/scala/io/iohk/ethereum/vm/OpCode.scala | 2 ++ src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala index 6bbb42ad65..62be29c176 100644 --- a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala +++ b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala @@ -885,6 +885,7 @@ abstract class CreateOp(code: Int, delta: Int) extends OpCode(code, delta, 1, _. .withWorld(world2) .withStack(resultStack) .withReturnData(returnData) + .addAccessedAddress(newAddress) .step() case None => @@ -902,6 +903,7 @@ abstract class CreateOp(code: Int, delta: Int) extends OpCode(code, delta, 1, _. .withMemory(memory1) .withInternalTxs(internalTx +: result.internalTxs) .withReturnData(ByteString.empty) + .addAccessedAddress(newAddress) .step() } } diff --git a/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala b/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala index 9d7f7a5f52..e711f0dc18 100644 --- a/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala @@ -248,6 +248,13 @@ class CreateOpcodeSpec extends AnyWordSpec with Matchers with ScalaCheckProperty "leave return buffer empty" in { result.stateOut.returnData shouldEqual ByteString.empty } + + "add the new contract to accessed_addresses" in { + result.world.getGuaranteedAccount(newAccountAddress()) + val addr = newAccountAddress() + + result.stateOut.accessedAddresses should contain (addr) + } } "initialization code fails" should { From c62cf5f86e256b6d98e64604334f1b842e1ee29a Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 19 Jul 2021 16:40:51 +0200 Subject: [PATCH 04/17] [ETCM-912] Add Magneto fee schedule --- .../scala/io/iohk/ethereum/vm/EvmConfig.scala | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala b/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala index d5e2c89b71..2c06c9a364 100644 --- a/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala +++ b/src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala @@ -42,7 +42,8 @@ object EvmConfig { (blockchainConfig.aghartaBlockNumber, 7, AghartaConfigBuilder), (blockchainConfig.petersburgBlockNumber, 8, PetersburgConfigBuilder), (blockchainConfig.istanbulBlockNumber, 9, IstanbulConfigBuilder), - (blockchainConfig.phoenixBlockNumber, 9, PhoenixConfigBuilder) + (blockchainConfig.phoenixBlockNumber, 9, PhoenixConfigBuilder), + (blockchainConfig.magnetoBlockNumber, 10, MagnetoConfigBuilder) ) // highest transition block that is less/equal to `blockNumber` @@ -61,6 +62,7 @@ object EvmConfig { val ConstantinopleOpCodes: OpCodeList = OpCodeList(OpCodes.ConstantinopleOpCodes) val AghartaOpCodes = ConstantinopleOpCodes val PhoenixOpCodes: OpCodeList = OpCodeList(OpCodes.PhoenixOpCodes) + val MagnetoOpCodes: OpCodeList = PhoenixOpCodes val FrontierConfigBuilder: EvmConfigBuilder = config => EvmConfig( @@ -132,6 +134,12 @@ object EvmConfig { opCodeList = PhoenixOpCodes ) + val MagnetoConfigBuilder: EvmConfigBuilder = config => + PhoenixConfigBuilder(config).copy( + feeSchedule = new ethereum.vm.FeeSchedule.MagnetoFeeSchedule, + opCodeList = MagnetoOpCodes + ) + case class OpCodeList(opCodes: List[OpCode]) { val byteToOpCode: Map[Byte, OpCode] = opCodes.map(op => op.code -> op).toMap @@ -251,6 +259,9 @@ object FeeSchedule { override val G_copy = 3 override val G_blockhash = 20 override val G_extcode = 20 + override val G_cold_sload = 2100 + override val G_cold_account_access = 2600 + override val G_warm_storage_read = 100 } class HomesteadFeeSchedule extends FrontierFeeSchedule { @@ -283,6 +294,10 @@ object FeeSchedule { override val G_txdatanonzero = 16 } + class MagnetoFeeSchedule extends PhoenixFeeSchedule { + override val G_sload: BigInt = G_warm_storage_read + override val G_sreset: BigInt = 5000 - G_cold_sload + } } trait FeeSchedule { @@ -321,4 +336,7 @@ trait FeeSchedule { val G_copy: BigInt val G_blockhash: BigInt val G_extcode: BigInt + val G_cold_sload: BigInt + val G_cold_account_access: BigInt + val G_warm_storage_read: BigInt } From 88377bffe230ba4f163c6ebcf38a71b10739f9c6 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Thu, 22 Jul 2021 16:26:19 +0200 Subject: [PATCH 05/17] [ETCM-912] Implement EXTCODESIZE changes --- .../scala/io/iohk/ethereum/vm/OpCode.scala | 29 +++++++++-- .../iohk/ethereum/vm/CreateOpcodeSpec.scala | 2 +- .../io/iohk/ethereum/vm/OpCodeGasSpec.scala | 11 +++- .../vm/OpCodeGasSpecPostEip2929.scala | 50 +++++++++++++++++++ 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala diff --git a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala index 62be29c176..e81fe0842f 100644 --- a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala +++ b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala @@ -452,13 +452,34 @@ case object CODECOPY extends OpCode(0x39, 3, 0, _.G_verylow) { case object GASPRICE extends ConstOp(0x3a)(_.env.gasPrice) -case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_extcode) with ConstGas { +case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_zero) { protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = { - val (addr, stack1) = state.stack.pop - val codeSize = state.world.getCode(Address(addr)).size + val (addrUint, stack1) = state.stack.pop + val addr = Address(addrUint) + val codeSize = state.world.getCode(addr).size val stack2 = stack1.push(UInt256(codeSize)) - state.withStack(stack2).step() + state.withStack(stack2).addAccessedAddress(addr).step() + } + + protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { + + val currentBlockNumber = state.env.blockHeader.number + val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber) + + val eip2929Enabled = isEip2929Enabled(etcFork) + + if (eip2929Enabled) { + val (addr, _) = state.stack.pop + if (state.accessedAddresses.contains(Address(addr))) + state.config.feeSchedule.G_warm_storage_read + else + state.config.feeSchedule.G_cold_account_access + } else + state.config.feeSchedule.G_extcode } + + private def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto + } case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_extcode) { diff --git a/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala b/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala index e711f0dc18..3a844dd755 100644 --- a/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala @@ -253,7 +253,7 @@ class CreateOpcodeSpec extends AnyWordSpec with Matchers with ScalaCheckProperty result.world.getGuaranteedAccount(newAccountAddress()) val addr = newAccountAddress() - result.stateOut.accessedAddresses should contain (addr) + result.stateOut.accessedAddresses should contain(addr) } } diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala index 9246f53602..da61b4e9d7 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala @@ -51,7 +51,6 @@ class OpCodeGasSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc CALLVALUE -> G_base, CALLDATALOAD -> G_verylow, CALLDATASIZE -> G_base, - EXTCODESIZE -> G_extcode, BLOCKHASH -> G_blockhash, COINBASE -> G_base, TIMESTAMP -> G_base, @@ -582,5 +581,15 @@ class OpCodeGasSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc } + test(EXTCODESIZE) { op => + val stateGen = getProgramStateGen( + stackGen = getStackGen(elems = 1) + ) + forAll(stateGen) { stateIn => + val stateOut = op.execute(stateIn) + verifyGas(G_extcode, stateIn, stateOut) + } + } + verifyAllOpCodesRegistered(except = CREATE, CREATE2, CALL, CALLCODE, DELEGATECALL, STATICCALL, INVALID) } diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala new file mode 100644 index 0000000000..db398bc429 --- /dev/null +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala @@ -0,0 +1,50 @@ +package io.iohk.ethereum.vm + +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +import io.iohk.ethereum.domain.Address +import io.iohk.ethereum.vm.Generators._ + +import Fixtures.blockchainConfig + +class OpCodeGasSpecPostEip2929 extends AnyFunSuite with OpCodeTesting with Matchers with ScalaCheckPropertyChecks { + + override val config: EvmConfig = EvmConfig.MagnetoConfigBuilder(blockchainConfig) + + import config.feeSchedule._ + + test(EXTCODESIZE) { op => + val stateGen = getProgramStateGen( + evmConfig = config, + stackGen = getStackGen(elems = 1), + blockNumberGen = getUInt256Gen(Fixtures.MagnetoBlockNumber) + ) + val codeGen = getByteStringGen(0, 512) + + forAll(stateGen) { stateIn => + val (addrUint, _) = stateIn.stack.pop + val addr = Address(addrUint) + stateIn.accessedAddresses shouldNot contain(addr) + + val stateOut = op.execute(stateIn) + + verifyGas(G_cold_account_access, stateIn, stateOut) + stateOut.accessedAddresses should contain(addr) + } + + forAll(stateGen, codeGen) { (stateIn, extCode) => + val (addrUint, _) = stateIn.stack.pop + val addr = Address(addrUint) + val program = Program(extCode) + val world1 = stateIn.world.saveCode(addr, program.code) + val stateInWithExtCode = stateIn.withWorld(world1).addAccessedAddress(addr) + + val stateOut = op.execute(stateInWithExtCode) + + verifyGas(G_warm_storage_read, stateIn, stateOut) + stateOut.accessedAddresses should contain(addr) + } + } +} From 9c39a5f9096654e0eff71d5b2050c2fd70fb0716 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 26 Jul 2021 13:34:00 +0200 Subject: [PATCH 06/17] [ETCM-921] Implement EXTCODECOPY --- .../scala/io/iohk/ethereum/vm/OpCode.scala | 27 ++++++-- .../vm/OpCodeGasSpecPostEip2929.scala | 62 +++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala index e81fe0842f..b83587c9e9 100644 --- a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala +++ b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala @@ -199,6 +199,8 @@ abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constG protected def availableInContext[W <: WorldStateProxy[W, S], S <: Storage[S]]: ProgramState[W, S] => Boolean = _ => true + + protected def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto } sealed trait ConstGas { self: OpCode => @@ -478,23 +480,36 @@ case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_zero) { state.config.feeSchedule.G_extcode } - private def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto - } -case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_extcode) { +case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_zero) { protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = { val (Seq(address, memOffset, codeOffset, size), stack1) = state.stack.pop(4) - val codeCopy = OpCode.sliceBytes(state.world.getCode(Address(address)), codeOffset, size) + val addr = Address(address) + val codeCopy = OpCode.sliceBytes(state.world.getCode(addr), codeOffset, size) val mem1 = state.memory.store(memOffset, codeCopy) - state.withStack(stack1).withMemory(mem1).step() + state.withStack(stack1).withMemory(mem1).addAccessedAddress(addr).step() } protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { val (Seq(_, memOffset, _, size), _) = state.stack.pop(4) val memCost = state.config.calcMemCost(state.memory.size, memOffset, size) val copyCost = state.config.feeSchedule.G_copy * wordsForBytes(size) - memCost + copyCost + + val currentBlockNumber = state.env.blockHeader.number + val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber) + val eip2929Enabled = isEip2929Enabled(etcFork) + + val accessCost = if (eip2929Enabled) { + val (addr, _) = state.stack.pop + if (state.accessedAddresses.contains(Address(addr))) { + state.config.feeSchedule.G_warm_storage_read + } else { + state.config.feeSchedule.G_cold_account_access + } + } else + state.config.feeSchedule.G_extcode + memCost + copyCost + accessCost } } diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala index db398bc429..07496ae040 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala @@ -5,6 +5,8 @@ import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import io.iohk.ethereum.domain.Address +import io.iohk.ethereum.domain.UInt256 +import io.iohk.ethereum.domain.UInt256._ import io.iohk.ethereum.vm.Generators._ import Fixtures.blockchainConfig @@ -47,4 +49,64 @@ class OpCodeGasSpecPostEip2929 extends AnyFunSuite with OpCodeTesting with Match stateOut.accessedAddresses should contain(addr) } } + + test(EXTCODECOPY) { op => + val table = Table[UInt256, Boolean, BigInt]( + ("size", "accessed", "expectedGas"), + (0, false, G_cold_account_access), + (0, true, G_warm_storage_read), + (1, false, G_cold_account_access + G_copy * 1), + (1, true, G_warm_storage_read + G_copy * 1), + (32, false, G_cold_account_access + G_copy * 1), + (32, true, G_warm_storage_read + G_copy * 1), + (33, false, G_cold_account_access + G_copy * 2), + (33, true, G_warm_storage_read + G_copy * 2), + (Two ** 16, false, G_cold_account_access + G_copy * 2048), + (Two ** 16, true, G_warm_storage_read + G_copy * 2048), + (Two ** 16 + 1, false, G_cold_account_access + G_copy * 2049), + (Two ** 16 + 1, true, G_warm_storage_read + G_copy * 2049) + ) + + forAll(table) { (size, accessed, expectedGas) => + val initState = getProgramStateGen( + evmConfig = config, + blockNumberGen = getUInt256Gen(Fixtures.MagnetoBlockNumber) + ).sample.get + // Pick an address (small, so it fits into memory) that is not on the precompiles list + val addr = getUInt256Gen(max = 1000).map(Address(_)).retryUntil(!initState.accessedAddresses.contains(_)).sample.get + val stackIn = Stack.empty().push(Seq(size, Zero, Zero, addr.toUInt256)) + val memIn = Memory.empty.store(addr.toUInt256, Array.fill[Byte](size.toInt)(-1)) + val stateIn = initState.withStack(stackIn).withMemory(memIn).copy(gas = expectedGas) + + val stateOut = if (accessed) op.execute(stateIn.addAccessedAddress(addr)) else op.execute(stateIn) + + verifyGas(expectedGas, stateIn, stateOut, allowOOG = false) + stateOut.accessedAddresses should contain(addr) + } + + val maxGas = 2 * (G_cold_account_access + G_copy * 8) + val stateGen = getProgramStateGen( + evmConfig = config, + blockNumberGen = getUInt256Gen(Fixtures.MagnetoBlockNumber), + stackGen = getStackGen(elems = 4, maxUInt = UInt256(256)), + gasGen = getBigIntGen(max = maxGas), + memGen = getMemoryGen(256) + ) + + forAll(stateGen) { stateIn => + val stateOut = op.execute(stateIn) + + val (Seq(address, offset, _, size), _) = stateIn.stack.pop(4) + val addr = Address(address) + val memCost = config.calcMemCost(stateIn.memory.size, offset, size) + val copyCost = G_copy * wordsForBytes(size) + val expectedGas = + if (stateIn.accessedAddresses.contains(addr)) + G_warm_storage_read + memCost + copyCost + else G_cold_account_access + memCost + copyCost + + verifyGas(expectedGas, stateIn, stateOut) + } + } + } From dc6055ec3c31957d8e1a341df432c144ae7eabcf Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 26 Jul 2021 13:42:02 +0200 Subject: [PATCH 07/17] fixup! [ETCM-912] Add newly created address to accessedaddresses --- src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala b/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala index 3a844dd755..5cdb7c286e 100644 --- a/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/CreateOpcodeSpec.scala @@ -250,8 +250,8 @@ class CreateOpcodeSpec extends AnyWordSpec with Matchers with ScalaCheckProperty } "add the new contract to accessed_addresses" in { - result.world.getGuaranteedAccount(newAccountAddress()) val addr = newAccountAddress() + result.world.getGuaranteedAccount(addr) result.stateOut.accessedAddresses should contain(addr) } From 62fd75faffedfc9919fcc6fbab4b1b990536a6da Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 26 Jul 2021 14:46:17 +0200 Subject: [PATCH 08/17] fixup! [ETCM-912] Implement EXTCODESIZE changes --- src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala index be07c77968..1dabb30cb1 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala @@ -275,13 +275,14 @@ class OpCodeFunSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc val codeGen = getByteStringGen(0, 512) forAll(stateGen, codeGen) { (stateIn, extCode) => + val (addr,_) = stateIn.stack.pop val stateOut = executeOp(op, stateIn) withStackVerification(op, stateIn, stateOut) { val (_, stack1) = stateIn.stack.pop - stateOut shouldEqual stateIn.withStack(stack1.push(UInt256.Zero)).step() + stateOut shouldEqual stateIn.addAccessedAddress(Address(addr)).withStack(stack1.push(UInt256.Zero)).step() } - val (addr, stack1) = stateIn.stack.pop + val (_, stack1) = stateIn.stack.pop val program = Program(extCode) val world1 = stateIn.world.saveCode(Address(addr), program.code) @@ -290,7 +291,7 @@ class OpCodeFunSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc withStackVerification(op, stateInWithExtCode, stateOutWithExtCode) { val stack2 = stack1.push(UInt256(extCode.size)) - stateOutWithExtCode shouldEqual stateInWithExtCode.withStack(stack2).step() + stateOutWithExtCode shouldEqual stateInWithExtCode.addAccessedAddress(Address(addr)).withStack(stack2).step() } } } From a8e7b22a86cf8b25cb56acdcee61ca69edf1e0f0 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 26 Jul 2021 14:46:31 +0200 Subject: [PATCH 09/17] fixup! [ETCM-921] Implement EXTCODECOPY --- src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala index 1dabb30cb1..a45adf2013 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala @@ -322,7 +322,7 @@ class OpCodeFunSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc val (storedInMem, _) = stateOut.memory.load(memOffset, size) code shouldEqual storedInMem - val expectedState = stateIn.withStack(stateOut.stack).withMemory(stateOut.memory).step() + val expectedState = stateIn.addAccessedAddress(Address(addr)).withStack(stateOut.stack).withMemory(stateOut.memory).step() stateOut shouldEqual expectedState } } From 4b85d7cecc46392f8b2ca33dd86018dbcd7d8637 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 26 Jul 2021 14:48:42 +0200 Subject: [PATCH 10/17] [ETCM-921] Format --- src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala | 5 +++-- .../scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala index a45adf2013..5541590332 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala @@ -275,7 +275,7 @@ class OpCodeFunSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc val codeGen = getByteStringGen(0, 512) forAll(stateGen, codeGen) { (stateIn, extCode) => - val (addr,_) = stateIn.stack.pop + val (addr, _) = stateIn.stack.pop val stateOut = executeOp(op, stateIn) withStackVerification(op, stateIn, stateOut) { val (_, stack1) = stateIn.stack.pop @@ -322,7 +322,8 @@ class OpCodeFunSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc val (storedInMem, _) = stateOut.memory.load(memOffset, size) code shouldEqual storedInMem - val expectedState = stateIn.addAccessedAddress(Address(addr)).withStack(stateOut.stack).withMemory(stateOut.memory).step() + val expectedState = + stateIn.addAccessedAddress(Address(addr)).withStack(stateOut.stack).withMemory(stateOut.memory).step() stateOut shouldEqual expectedState } } diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala index 07496ae040..cbecfc7e1b 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala @@ -73,7 +73,8 @@ class OpCodeGasSpecPostEip2929 extends AnyFunSuite with OpCodeTesting with Match blockNumberGen = getUInt256Gen(Fixtures.MagnetoBlockNumber) ).sample.get // Pick an address (small, so it fits into memory) that is not on the precompiles list - val addr = getUInt256Gen(max = 1000).map(Address(_)).retryUntil(!initState.accessedAddresses.contains(_)).sample.get + val addr = + getUInt256Gen(max = 1000).map(Address(_)).retryUntil(!initState.accessedAddresses.contains(_)).sample.get val stackIn = Stack.empty().push(Seq(size, Zero, Zero, addr.toUInt256)) val memIn = Memory.empty.store(addr.toUInt256, Array.fill[Byte](size.toInt)(-1)) val stateIn = initState.withStack(stackIn).withMemory(memIn).copy(gas = expectedGas) From 0074de677c75b6424630981d8fc0c66d00e42dd7 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 26 Jul 2021 17:19:33 +0200 Subject: [PATCH 11/17] [ETCM-912] Fix style --- src/main/scala/io/iohk/ethereum/vm/ProgramState.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala b/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala index b9abcfb17f..919c82544f 100644 --- a/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala +++ b/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala @@ -134,10 +134,10 @@ case class ProgramState[W <: WorldStateProxy[W, S], S <: Storage[S]]( copy(error = Some(RevertOccurs), returnData = data, halted = true) def addAccessedAddress(addr: Address): ProgramState[W, S] = - copy(accessedAddresses = accessedAddresses.+(addr)) + copy(accessedAddresses = accessedAddresses. +(addr)) def addAccessedStorageKey(addr: Address, storageKey: BigInt): ProgramState[W, S] = - copy(accessedStorageKeys = accessedStorageKeys.+((addr, storageKey))) + copy(accessedStorageKeys = accessedStorageKeys. +((addr, storageKey))) def toResult: ProgramResult[W, S] = ProgramResult[W, S]( From 478fa0a52b2730778f6eaf1ca61505d06d9b4514 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 26 Jul 2021 21:10:23 +0200 Subject: [PATCH 12/17] fixup! [ETCM-912] Fix style --- src/main/scala/io/iohk/ethereum/vm/ProgramState.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala b/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala index 919c82544f..04da3f668d 100644 --- a/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala +++ b/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala @@ -134,10 +134,10 @@ case class ProgramState[W <: WorldStateProxy[W, S], S <: Storage[S]]( copy(error = Some(RevertOccurs), returnData = data, halted = true) def addAccessedAddress(addr: Address): ProgramState[W, S] = - copy(accessedAddresses = accessedAddresses. +(addr)) + copy(accessedAddresses = accessedAddresses + addr) def addAccessedStorageKey(addr: Address, storageKey: BigInt): ProgramState[W, S] = - copy(accessedStorageKeys = accessedStorageKeys. +((addr, storageKey))) + copy(accessedStorageKeys = accessedStorageKeys + ((addr, storageKey))) def toResult: ProgramResult[W, S] = ProgramResult[W, S]( From 73d9966af8812ca479b4811436a9222d12257b3c Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Tue, 27 Jul 2021 09:10:13 +0200 Subject: [PATCH 13/17] [ETCM-912] Implement gas calculation changes in EXTCODEHASH --- .../scala/io/iohk/ethereum/vm/OpCode.scala | 110 ++++++++++++------ .../io/iohk/ethereum/vm/OpCodeFunSpec.scala | 7 +- .../io/iohk/ethereum/vm/OpCodeGasSpec.scala | 11 +- .../vm/OpCodeGasSpecPostEip2929.scala | 2 +- 4 files changed, 78 insertions(+), 52 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala index b83587c9e9..62dbebd2a5 100644 --- a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala +++ b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala @@ -184,15 +184,20 @@ abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constG else if (state.stack.size - delta + alpha > state.stack.maxSize) state.withError(StackOverflow) else { - val constGas: BigInt = constGasFn(state.config.feeSchedule) - - val gas: BigInt = constGas + varGas(state) + val gas: BigInt = calcGas(state) if (gas > state.gas) state.copy(gas = 0).withError(OutOfGas) else exec(state).spendGas(gas) } + protected def calcGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = + constGas(state) + varGas(state) + + protected def constGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = constGasFn( + state.config.feeSchedule + ) + protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] @@ -203,10 +208,54 @@ abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constG protected def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto } +trait AddrAccessGas { self: OpCode => + + private def coldGasFn: FeeSchedule => BigInt = _.G_cold_account_access + private def warmGasFn: FeeSchedule => BigInt = _.G_warm_storage_read + + override protected def constGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { + val currentBlockNumber = state.env.blockHeader.number + val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber) + val eip2929Enabled = isEip2929Enabled(etcFork) + if (eip2929Enabled) { + val addr = address(state) + if (state.accessedAddresses.contains(addr)) + warmGasFn(state.config.feeSchedule) + else + coldGasFn(state.config.feeSchedule) + } else + constGasFn(state.config.feeSchedule) + } + + protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address + +} + sealed trait ConstGas { self: OpCode => protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = 0 } +sealed trait AddressAccessOpGas extends ConstGas { self: OpCode => + override protected def calcGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { + val currentBlockNumber = state.env.blockHeader.number + val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber) + + val eip2929Enabled = isEip2929Enabled(etcFork) + + if (eip2929Enabled) { + val addr = address(state) + if (state.accessedAddresses.contains(addr)) + state.config.feeSchedule.G_warm_storage_read + else + state.config.feeSchedule.G_cold_account_access + } else + constGasFn(state.config.feeSchedule) + } + + protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address + +} + case object STOP extends OpCode(0x00, 0, 0, _.G_zero) with ConstGas { protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = state.withReturnData(ByteString.empty).halt @@ -367,7 +416,8 @@ case object BALANCE extends OpCode(0x31, 1, 1, _.G_balance) with ConstGas { } } -case object EXTCODEHASH extends OpCode(0x3f, 1, 1, _.G_balance) with ConstGas { +case object EXTCODEHASH extends OpCode(0x3f, 1, 1, _.G_balance) with AddrAccessGas with ConstGas { + protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = { val (accountAddress, stack1) = state.stack.pop val address = Address(accountAddress) @@ -397,7 +447,12 @@ case object EXTCODEHASH extends OpCode(0x3f, 1, 1, _.G_balance) with ConstGas { } val stack2 = stack1.push(codeHash) - state.withStack(stack2).step() + state.withStack(stack2).addAccessedAddress(address).step() + } + + protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address = { + val (accountAddress, _) = state.stack.pop + Address(accountAddress) } } @@ -454,7 +509,7 @@ case object CODECOPY extends OpCode(0x39, 3, 0, _.G_verylow) { case object GASPRICE extends ConstOp(0x3a)(_.env.gasPrice) -case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_zero) { +case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_extcode) with AddrAccessGas with ConstGas { protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = { val (addrUint, stack1) = state.stack.pop val addr = Address(addrUint) @@ -463,26 +518,14 @@ case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_zero) { state.withStack(stack2).addAccessedAddress(addr).step() } - protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { - - val currentBlockNumber = state.env.blockHeader.number - val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber) - - val eip2929Enabled = isEip2929Enabled(etcFork) - - if (eip2929Enabled) { - val (addr, _) = state.stack.pop - if (state.accessedAddresses.contains(Address(addr))) - state.config.feeSchedule.G_warm_storage_read - else - state.config.feeSchedule.G_cold_account_access - } else - state.config.feeSchedule.G_extcode + protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address = { + val (accountAddress, _) = state.stack.pop + Address(accountAddress) } - } -case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_zero) { +case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_extcode) with AddrAccessGas { + protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = { val (Seq(address, memOffset, codeOffset, size), stack1) = state.stack.pop(4) val addr = Address(address) @@ -491,25 +534,16 @@ case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_zero) { state.withStack(stack1).withMemory(mem1).addAccessedAddress(addr).step() } - protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { + override protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { val (Seq(_, memOffset, _, size), _) = state.stack.pop(4) val memCost = state.config.calcMemCost(state.memory.size, memOffset, size) val copyCost = state.config.feeSchedule.G_copy * wordsForBytes(size) + memCost + copyCost + } - val currentBlockNumber = state.env.blockHeader.number - val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber) - val eip2929Enabled = isEip2929Enabled(etcFork) - - val accessCost = if (eip2929Enabled) { - val (addr, _) = state.stack.pop - if (state.accessedAddresses.contains(Address(addr))) { - state.config.feeSchedule.G_warm_storage_read - } else { - state.config.feeSchedule.G_cold_account_access - } - } else - state.config.feeSchedule.G_extcode - memCost + copyCost + accessCost + protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address = { + val (Seq(accountAddress, _, _, _), _) = state.stack.pop(4) + Address(accountAddress) } } diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala index 5541590332..1f3e3c92d1 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeFunSpec.scala @@ -179,13 +179,14 @@ class OpCodeFunSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc test(EXTCODEHASH) { op => forAll(getProgramStateGen(), getByteStringGen(0, 256)) { (stateIn, extCode) => + val (addr, _) = stateIn.stack.pop val stateOut = executeOp(op, stateIn) withStackVerification(op, stateIn, stateOut) { val (_, stack1) = stateIn.stack.pop - stateOut shouldEqual stateIn.withStack(stack1.push(UInt256.Zero)).step() + stateOut shouldEqual stateIn.addAccessedAddress(Address(addr)).withStack(stack1.push(UInt256.Zero)).step() } - val (addr, stack1) = stateIn.stack.pop + val (_, stack1) = stateIn.stack.pop val codeHash = kec256(extCode) val account = Account(codeHash = codeHash) @@ -199,7 +200,7 @@ class OpCodeFunSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc // if account is empty we should push 0 onto stack val toPushOnStack = if (codeHash == Account.EmptyCodeHash) UInt256.Zero else UInt256(codeHash) val stack2 = stack1.push(toPushOnStack) - stateOutWithAccount shouldEqual stateInWithAccount.withStack(stack2).step() + stateOutWithAccount shouldEqual stateInWithAccount.addAccessedAddress(Address(addr)).withStack(stack2).step() } } } diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala index da61b4e9d7..199caa757e 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala @@ -48,6 +48,7 @@ class OpCodeGasSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc ADDRESS -> G_base, BALANCE -> G_balance, EXTCODEHASH -> G_balance, + EXTCODESIZE -> G_extcode, CALLVALUE -> G_base, CALLDATALOAD -> G_verylow, CALLDATASIZE -> G_base, @@ -581,15 +582,5 @@ class OpCodeGasSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc } - test(EXTCODESIZE) { op => - val stateGen = getProgramStateGen( - stackGen = getStackGen(elems = 1) - ) - forAll(stateGen) { stateIn => - val stateOut = op.execute(stateIn) - verifyGas(G_extcode, stateIn, stateOut) - } - } - verifyAllOpCodesRegistered(except = CREATE, CREATE2, CALL, CALLCODE, DELEGATECALL, STATICCALL, INVALID) } diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala index cbecfc7e1b..23b424d958 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala @@ -17,7 +17,7 @@ class OpCodeGasSpecPostEip2929 extends AnyFunSuite with OpCodeTesting with Match import config.feeSchedule._ - test(EXTCODESIZE) { op => + test(EXTCODESIZE, EXTCODEHASH) { op => val stateGen = getProgramStateGen( evmConfig = config, stackGen = getStackGen(elems = 1), From bbc5f246076d48a0cd9dc3452d8b88cfcab8d305 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Tue, 27 Jul 2021 11:38:51 +0200 Subject: [PATCH 14/17] [ETCM-912] Remove leftovers --- .../scala/io/iohk/ethereum/vm/OpCode.scala | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala index 62dbebd2a5..f0edcfa405 100644 --- a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala +++ b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala @@ -235,27 +235,6 @@ sealed trait ConstGas { self: OpCode => protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = 0 } -sealed trait AddressAccessOpGas extends ConstGas { self: OpCode => - override protected def calcGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { - val currentBlockNumber = state.env.blockHeader.number - val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber) - - val eip2929Enabled = isEip2929Enabled(etcFork) - - if (eip2929Enabled) { - val addr = address(state) - if (state.accessedAddresses.contains(addr)) - state.config.feeSchedule.G_warm_storage_read - else - state.config.feeSchedule.G_cold_account_access - } else - constGasFn(state.config.feeSchedule) - } - - protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address - -} - case object STOP extends OpCode(0x00, 0, 0, _.G_zero) with ConstGas { protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = state.withReturnData(ByteString.empty).halt From 7b5785f9c496d3d0f03db5054897d6f239ee6250 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Tue, 27 Jul 2021 11:44:42 +0200 Subject: [PATCH 15/17] [ETCM-912] Document ProgramState --- src/main/scala/io/iohk/ethereum/vm/ProgramState.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala b/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala index 04da3f668d..27dc19e0b4 100644 --- a/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala +++ b/src/main/scala/io/iohk/ethereum/vm/ProgramState.scala @@ -45,6 +45,8 @@ object ProgramState { * @param staticCtx a flag to indicate static context (EIP-214) * @param error indicates whether the program terminated abnormally * @param originalWorld state of the world at the beginning og the current transaction, read-only, + * @param accessedAddresses set of addresses which have already been accessed in this transaction (EIP-2929) + * @param accessedStorageKeys set of storage slots which have already been accessed in this transaction (EIP-2929) * needed for https://eips.ethereum.org/EIPS/eip-1283 */ case class ProgramState[W <: WorldStateProxy[W, S], S <: Storage[S]]( From d28d6ded1955b76be236085ffd81641f0fbbd27d Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Tue, 27 Jul 2021 11:48:32 +0200 Subject: [PATCH 16/17] [ETCM-912] Move isEip2929Enabled flag to BlockchainConfigForEvm --- src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala | 2 ++ src/main/scala/io/iohk/ethereum/vm/OpCode.scala | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala b/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala index 3e4dbee76e..e65ff20c1a 100644 --- a/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala +++ b/src/main/scala/io/iohk/ethereum/vm/BlockchainConfigForEvm.scala @@ -68,6 +68,8 @@ object BlockchainConfigForEvm { val BeforeByzantium, Byzantium, Constantinople, Petersburg, Istanbul, Berlin = Value } + def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto + def apply(blockchainConfig: BlockchainConfig): BlockchainConfigForEvm = { import blockchainConfig._ BlockchainConfigForEvm( diff --git a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala index f0edcfa405..02ea2b8e6f 100644 --- a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala +++ b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala @@ -13,6 +13,7 @@ import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.EtcFork import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.EthFork +import io.iohk.ethereum.vm.BlockchainConfigForEvm._ // scalastyle:off magic.number // scalastyle:off number.of.types @@ -205,7 +206,6 @@ abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constG protected def availableInContext[W <: WorldStateProxy[W, S], S <: Storage[S]]: ProgramState[W, S] => Boolean = _ => true - protected def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto } trait AddrAccessGas { self: OpCode => From 98c406c3e190c6b6e5cde80f46e3bfb37ce22221 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Tue, 27 Jul 2021 14:02:31 +0200 Subject: [PATCH 17/17] [ETCM-912] Rename constGas -> baseGas --- .../scala/io/iohk/ethereum/vm/OpCode.scala | 24 +++++++++---------- .../scala/io/iohk/ethereum/vm/Assembly.scala | 2 +- .../io/iohk/ethereum/vm/OpCodeGasSpec.scala | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala index 02ea2b8e6f..0644c55127 100644 --- a/src/main/scala/io/iohk/ethereum/vm/OpCode.scala +++ b/src/main/scala/io/iohk/ethereum/vm/OpCode.scala @@ -172,7 +172,7 @@ object OpCode { * @param delta number of words to be popped from stack * @param alpha number of words to be pushed to stack */ -abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constGasFn: FeeSchedule => BigInt) +abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val baseGasFn: FeeSchedule => BigInt) extends Product with Serializable { def this(code: Int, pop: Int, push: Int, constGasFn: FeeSchedule => BigInt) = this(code.toByte, pop, push, constGasFn) @@ -193,9 +193,9 @@ abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constG } protected def calcGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = - constGas(state) + varGas(state) + baseGas(state) + varGas(state) - protected def constGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = constGasFn( + protected def baseGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = baseGasFn( state.config.feeSchedule ) @@ -213,7 +213,7 @@ trait AddrAccessGas { self: OpCode => private def coldGasFn: FeeSchedule => BigInt = _.G_cold_account_access private def warmGasFn: FeeSchedule => BigInt = _.G_warm_storage_read - override protected def constGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { + override protected def baseGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = { val currentBlockNumber = state.env.blockHeader.number val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber) val eip2929Enabled = isEip2929Enabled(etcFork) @@ -224,7 +224,7 @@ trait AddrAccessGas { self: OpCode => else coldGasFn(state.config.feeSchedule) } else - constGasFn(state.config.feeSchedule) + baseGasFn(state.config.feeSchedule) } protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address @@ -240,8 +240,8 @@ case object STOP extends OpCode(0x00, 0, 0, _.G_zero) with ConstGas { state.withReturnData(ByteString.empty).halt } -sealed abstract class UnaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(val f: UInt256 => UInt256) - extends OpCode(code, 1, 1, constGasFn) +sealed abstract class UnaryOp(code: Int, baseGasFn: FeeSchedule => BigInt)(val f: UInt256 => UInt256) + extends OpCode(code, 1, 1, baseGasFn) with ConstGas { protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = { @@ -252,8 +252,8 @@ sealed abstract class UnaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(val } } -sealed abstract class BinaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(val f: (UInt256, UInt256) => UInt256) - extends OpCode(code.toByte, 2, 1, constGasFn) { +sealed abstract class BinaryOp(code: Int, baseGasFn: FeeSchedule => BigInt)(val f: (UInt256, UInt256) => UInt256) + extends OpCode(code.toByte, 2, 1, baseGasFn) { protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = { val (Seq(a, b), stack1) = state.stack.pop(2) @@ -263,9 +263,9 @@ sealed abstract class BinaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(val } } -sealed abstract class TernaryOp(code: Int, constGasFn: FeeSchedule => BigInt)( +sealed abstract class TernaryOp(code: Int, baseGasFn: FeeSchedule => BigInt)( val f: (UInt256, UInt256, UInt256) => UInt256 -) extends OpCode(code.toByte, 3, 1, constGasFn) { +) extends OpCode(code.toByte, 3, 1, baseGasFn) { protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = { val (Seq(a, b, c), stack1) = state.stack.pop(3) @@ -894,7 +894,7 @@ abstract class CreateOp(code: Int, delta: Int) extends OpCode(code, delta, 1, _. //FIXME: to avoid calculating this twice, we could adjust state.gas prior to execution in OpCode#execute //not sure how this would affect other opcodes [EC-243] - val availableGas = state.gas - (constGasFn(state.config.feeSchedule) + varGas(state)) + val availableGas = state.gas - (baseGasFn(state.config.feeSchedule) + varGas(state)) val startGas = state.config.gasCap(availableGas) val (initCode, memory1) = state.memory.load(inOffset, inSize) val world1 = state.world.increaseNonce(state.ownAddress) diff --git a/src/test/scala/io/iohk/ethereum/vm/Assembly.scala b/src/test/scala/io/iohk/ethereum/vm/Assembly.scala index 34468466d6..923ef4a543 100644 --- a/src/test/scala/io/iohk/ethereum/vm/Assembly.scala +++ b/src/test/scala/io/iohk/ethereum/vm/Assembly.scala @@ -31,7 +31,7 @@ case class Assembly(byteCode: ByteCode*) { val program: Program = Program(code) def linearConstGas(config: EvmConfig): BigInt = byteCode.foldLeft(BigInt(0)) { - case (g, b: OpCodeAsByteCode) => g + b.op.constGasFn(config.feeSchedule) + case (g, b: OpCodeAsByteCode) => g + b.op.baseGasFn(config.feeSchedule) case (g, _) => g } } diff --git a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala index 199caa757e..a28b90f69c 100644 --- a/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala +++ b/src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala @@ -124,7 +124,7 @@ class OpCodeGasSpec extends AnyFunSuite with OpCodeTesting with Matchers with Sc test(constGasOps: _*) { op => val stateGen = getProgramStateGen( stackGen = getStackGen(elems = op.delta), - gasGen = getBigIntGen(max = op.constGasFn(config.feeSchedule) * 2) + gasGen = getBigIntGen(max = op.baseGasFn(config.feeSchedule) * 2) ) forAll(stateGen) { stateIn =>