Skip to content

Commit de9be69

Browse files
[ETCM-921] Implement EXTCODECOPY
1 parent 044f411 commit de9be69

File tree

2 files changed

+83
-6
lines changed

2 files changed

+83
-6
lines changed

src/main/scala/io/iohk/ethereum/vm/OpCode.scala

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constG
199199

200200
protected def availableInContext[W <: WorldStateProxy[W, S], S <: Storage[S]]: ProgramState[W, S] => Boolean = _ =>
201201
true
202+
203+
protected def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto
202204
}
203205

204206
sealed trait ConstGas { self: OpCode =>
@@ -478,23 +480,36 @@ case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_zero) {
478480
state.config.feeSchedule.G_extcode
479481
}
480482

481-
private def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto
482-
483483
}
484484

485-
case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_extcode) {
485+
case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_zero) {
486486
protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
487487
val (Seq(address, memOffset, codeOffset, size), stack1) = state.stack.pop(4)
488-
val codeCopy = OpCode.sliceBytes(state.world.getCode(Address(address)), codeOffset, size)
488+
val addr = Address(address)
489+
val codeCopy = OpCode.sliceBytes(state.world.getCode(addr), codeOffset, size)
489490
val mem1 = state.memory.store(memOffset, codeCopy)
490-
state.withStack(stack1).withMemory(mem1).step()
491+
state.withStack(stack1).withMemory(mem1).addAccessedAddress(addr).step()
491492
}
492493

493494
protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = {
494495
val (Seq(_, memOffset, _, size), _) = state.stack.pop(4)
495496
val memCost = state.config.calcMemCost(state.memory.size, memOffset, size)
496497
val copyCost = state.config.feeSchedule.G_copy * wordsForBytes(size)
497-
memCost + copyCost
498+
499+
val currentBlockNumber = state.env.blockHeader.number
500+
val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber)
501+
val eip2929Enabled = isEip2929Enabled(etcFork)
502+
503+
val accessCost = if (eip2929Enabled) {
504+
val (addr, _) = state.stack.pop
505+
if (state.accessedAddresses.contains(Address(addr))) {
506+
state.config.feeSchedule.G_warm_storage_read
507+
} else {
508+
state.config.feeSchedule.G_cold_account_access
509+
}
510+
} else
511+
state.config.feeSchedule.G_extcode
512+
memCost + copyCost + accessCost
498513
}
499514
}
500515

src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpecPostEip2929.scala

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import org.scalatest.matchers.should.Matchers
55
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
66

77
import io.iohk.ethereum.domain.Address
8+
import io.iohk.ethereum.domain.UInt256
9+
import io.iohk.ethereum.domain.UInt256._
810
import io.iohk.ethereum.vm.Generators._
911

1012
import Fixtures.blockchainConfig
@@ -47,4 +49,64 @@ class OpCodeGasSpecPostEip2929 extends AnyFunSuite with OpCodeTesting with Match
4749
stateOut.accessedAddresses should contain(addr)
4850
}
4951
}
52+
53+
test(EXTCODECOPY) { op =>
54+
val table = Table[UInt256, Boolean, BigInt](
55+
("size", "accessed", "expectedGas"),
56+
(0, false, G_cold_account_access),
57+
(0, true, G_warm_storage_read),
58+
(1, false, G_cold_account_access + G_copy * 1),
59+
(1, true, G_warm_storage_read + G_copy * 1),
60+
(32, false, G_cold_account_access + G_copy * 1),
61+
(32, true, G_warm_storage_read + G_copy * 1),
62+
(33, false, G_cold_account_access + G_copy * 2),
63+
(33, true, G_warm_storage_read + G_copy * 2),
64+
(Two ** 16, false, G_cold_account_access + G_copy * 2048),
65+
(Two ** 16, true, G_warm_storage_read + G_copy * 2048),
66+
(Two ** 16 + 1, false, G_cold_account_access + G_copy * 2049),
67+
(Two ** 16 + 1, true, G_warm_storage_read + G_copy * 2049)
68+
)
69+
70+
forAll(table) { (size, accessed, expectedGas) =>
71+
val initState = getProgramStateGen(
72+
evmConfig = config,
73+
blockNumberGen = getUInt256Gen(Fixtures.MagnetoBlockNumber)
74+
).sample.get
75+
// Pick an address (small, so it fits into memory) that is not on the precompiles list
76+
val addr = getUInt256Gen(max = 1000).map(Address(_)).retryUntil(!initState.accessedAddresses.contains(_)).sample.get
77+
val stackIn = Stack.empty().push(Seq(size, Zero, Zero, addr.toUInt256))
78+
val memIn = Memory.empty.store(addr.toUInt256, Array.fill[Byte](size.toInt)(-1))
79+
val stateIn = initState.withStack(stackIn).withMemory(memIn).copy(gas = expectedGas)
80+
81+
val stateOut = if (accessed) op.execute(stateIn.addAccessedAddress(addr)) else op.execute(stateIn)
82+
83+
verifyGas(expectedGas, stateIn, stateOut, allowOOG = false)
84+
stateOut.accessedAddresses should contain(addr)
85+
}
86+
87+
val maxGas = 2 * (G_cold_account_access + G_copy * 8)
88+
val stateGen = getProgramStateGen(
89+
evmConfig = config,
90+
blockNumberGen = getUInt256Gen(Fixtures.MagnetoBlockNumber),
91+
stackGen = getStackGen(elems = 4, maxUInt = UInt256(256)),
92+
gasGen = getBigIntGen(max = maxGas),
93+
memGen = getMemoryGen(256)
94+
)
95+
96+
forAll(stateGen) { stateIn =>
97+
val stateOut = op.execute(stateIn)
98+
99+
val (Seq(address, offset, _, size), _) = stateIn.stack.pop(4)
100+
val addr = Address(address)
101+
val memCost = config.calcMemCost(stateIn.memory.size, offset, size)
102+
val copyCost = G_copy * wordsForBytes(size)
103+
val expectedGas =
104+
if (stateIn.accessedAddresses.contains(addr))
105+
G_warm_storage_read + memCost + copyCost
106+
else G_cold_account_access + memCost + copyCost
107+
108+
verifyGas(expectedGas, stateIn, stateOut)
109+
}
110+
}
111+
50112
}

0 commit comments

Comments
 (0)