Skip to content

Commit

Permalink
[ETCM-921] Implement EXTCODECOPY
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasz-golebiewski committed Jul 26, 2021
1 parent 26f2d8f commit 87fae75
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 6 deletions.
27 changes: 21 additions & 6 deletions src/main/scala/io/iohk/ethereum/vm/OpCode.scala
Expand Up @@ -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 =>
Expand Down Expand Up @@ -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
}
}

Expand Down
62 changes: 62 additions & 0 deletions src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}

}

0 comments on commit 87fae75

Please sign in to comment.