From 87fae75b16ceb4bdcf6cf1d04aba13cb83049722 Mon Sep 17 00:00:00 2001 From: Lukasz Golebiewski Date: Mon, 26 Jul 2021 13:34:00 +0200 Subject: [PATCH] [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) + } + } + }