Skip to content

Commit 845350b

Browse files
author
Michał Mrożek
authored
Merge pull request #650 from input-output-hk/etcm-22-structured-definitions-for-net-gas-metering
Etcm 22 structured definitions for net gas metering
2 parents 9958457 + d300578 commit 845350b

File tree

6 files changed

+137
-56
lines changed

6 files changed

+137
-56
lines changed

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

+28-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import akka.util.ByteString
44
import io.iohk.ethereum.crypto.kec256
55
import io.iohk.ethereum.domain.{Account, Address, TxLogEntry, UInt256}
66
import io.iohk.ethereum.domain.UInt256._
7-
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks
7+
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.EtcFork
8+
import io.iohk.ethereum.vm.BlockchainConfigForEvm.{EtcForks, EthForks}
9+
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.EthFork
810

911
// scalastyle:off magic.number
1012
// scalastyle:off number.of.types
@@ -593,9 +595,17 @@ case object MSTORE8 extends OpCode(0x53, 2, 0, _.G_verylow) {
593595

594596
case object SSTORE extends OpCode(0x55, 2, 0, _.G_zero) {
595597
protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
598+
val currentBlockNumber = state.env.blockHeader.number
599+
val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber)
600+
val ethFork = state.config.blockchainConfig.ethForkForBlockNumber(currentBlockNumber)
601+
602+
val eip2200Enabled = isEip2200Enabled(etcFork, ethFork)
603+
val eip1283Enabled = isEip1283Enabled(ethFork)
604+
596605
val (Seq(offset, newValue), stack1) = state.stack.pop(2)
597606
val currentValue = state.storage.load(offset)
598-
val refund: BigInt = if (isEip1283Enabled(state)) {
607+
608+
val refund: BigInt = if (eip2200Enabled || eip1283Enabled) {
599609
val originalValue = state.originalWorld.getStorage(state.ownAddress).load(offset)
600610
if (currentValue != newValue.toBigInt) {
601611
if (originalValue == currentValue) { // fresh slot
@@ -629,16 +639,24 @@ case object SSTORE extends OpCode(0x55, 2, 0, _.G_zero) {
629639
else
630640
0
631641
}
632-
633642
val updatedStorage = state.storage.store(offset, newValue)
634643
state.withStack(stack1).withStorage(updatedStorage).refundGas(refund).step()
635644
}
636645

637646
protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = {
638647
val (Seq(offset, newValue), _) = state.stack.pop(2)
639648
val currentValue = state.storage.load(offset)
640-
if (isEip1283Enabled(state)) {
641-
// https://eips.ethereum.org/EIPS/eip-1283
649+
650+
val currentBlockNumber = state.env.blockHeader.number
651+
val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber)
652+
val ethFork = state.config.blockchainConfig.ethForkForBlockNumber(currentBlockNumber)
653+
654+
val eip2200Enabled = isEip2200Enabled(etcFork, ethFork)
655+
val eip1283Enabled = isEip1283Enabled(ethFork)
656+
657+
if(eip2200Enabled && state.gas <= state.config.feeSchedule.G_callstipend){
658+
state.config.feeSchedule.G_callstipend + 1 // Out of gas error
659+
} else if (eip2200Enabled || eip1283Enabled) {
642660
if (currentValue == newValue.toBigInt) { // no-op
643661
state.config.feeSchedule.G_sload
644662
} else {
@@ -663,10 +681,11 @@ case object SSTORE extends OpCode(0x55, 2, 0, _.G_zero) {
663681

664682
override protected def availableInContext[W <: WorldStateProxy[W, S], S <: Storage[S]]: ProgramState[W, S] => Boolean = !_.staticCtx
665683

666-
private def isEip1283Enabled[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Boolean = {
667-
val blockNumber = state.env.blockHeader.number
668-
state.config.blockchainConfig.ethForkForBlockNumber(blockNumber) == EthForks.Constantinople
669-
}
684+
// https://eips.ethereum.org/EIPS/eip-1283
685+
private def isEip1283Enabled(ethFork: EthFork): Boolean = ethFork == EthForks.Constantinople
686+
687+
// https://eips.ethereum.org/EIPS/eip-2200
688+
private def isEip2200Enabled(etcFork: EtcFork, ethFork: EthFork): Boolean = (ethFork >= EthForks.Istanbul || etcFork >= EtcForks.Phoenix)
670689
}
671690

672691
case object JUMP extends OpCode(0x56, 1, 0, _.G_mid) with ConstGas {

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ object Fixtures {
44

55
val ConstantinopleBlockNumber = 200
66
val PetersburgBlockNumber = 400
7+
val PhoenixBlockNumber = 600
78

89
val blockchainConfig = BlockchainConfigForEvm(
910
// block numbers are irrelevant
@@ -19,7 +20,7 @@ object Fixtures {
1920
atlantisBlockNumber = 0,
2021
aghartaBlockNumber = 0,
2122
petersburgBlockNumber = PetersburgBlockNumber,
22-
phoenixBlockNumber = 0,
23+
phoenixBlockNumber = PhoenixBlockNumber,
2324
chainId = 0x3d.toByte
2425
)
2526

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ object Generators extends ObjectGenerators {
8888
valueGen: Gen[UInt256] = getUInt256Gen(),
8989
blockNumberGen: Gen[UInt256] = getUInt256Gen(0, 300),
9090
evmConfig: EvmConfig = EvmConfig.PhoenixConfigBuilder(blockchainConfig),
91-
returnDataGen: Gen[ByteString] = getByteStringGen(0, 0)
91+
returnDataGen: Gen[ByteString] = getByteStringGen(0, 0),
92+
isTopHeader: Boolean = false
9293
): Gen[PS] =
9394
for {
9495
stack <- stackGen
@@ -102,7 +103,7 @@ object Generators extends ObjectGenerators {
102103
blockPlacement <- getUInt256Gen(0, blockNumber)
103104
returnData <- returnDataGen
104105

105-
blockHeader = exampleBlockHeader.copy(number = blockNumber - blockPlacement)
106+
blockHeader = exampleBlockHeader.copy(number = if(isTopHeader) blockNumber else blockNumber - blockPlacement)
106107

107108
world = MockWorldState(numberOfHashes = blockNumber - 1)
108109
.saveCode(ownerAddr, code)

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

+18-7
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,12 @@ class OpCodeGasSpec extends FunSuite with OpCodeTesting with Matchers with Prope
412412
}
413413

414414
test(SSTORE) { op =>
415-
// Before Constantinople
415+
// Before Constantinople + Petersburg
416+
// Constantinople + Phoenix tested in SSTOREOpCodeGasPostConstantinopleSpec
417+
418+
val petersburgConfig = EvmConfig.PetersburgConfigBuilder(blockchainConfig)
419+
import petersburgConfig.feeSchedule._
420+
416421
val storage = MockStorage.Empty.store(Zero, One)
417422
val table = Table[UInt256, UInt256, BigInt, BigInt](("offset", "value", "expectedGas", "expectedRefund"),
418423
(0, 1, G_sreset, 0),
@@ -423,10 +428,14 @@ class OpCodeGasSpec extends FunSuite with OpCodeTesting with Matchers with Prope
423428

424429
forAll(table) { (offset, value, expectedGas, _) =>
425430
val stackIn = Stack.empty().push(value).push(offset)
426-
val stateIn = getProgramStateGen(blockNumberGen = Gen.frequency(
427-
(1, getUInt256Gen(0, Fixtures.ConstantinopleBlockNumber - 1)),
428-
(1, getUInt256Gen(Fixtures.PetersburgBlockNumber + 1, UInt256.MaxValue))
429-
)).sample.get.withStack(stackIn).withStorage(storage).copy(gas = expectedGas)
431+
val stateIn = getProgramStateGen(
432+
blockNumberGen = Gen.frequency(
433+
(1, getUInt256Gen(0, Fixtures.ConstantinopleBlockNumber - 1)),
434+
(1, getUInt256Gen(Fixtures.PetersburgBlockNumber + 1, Fixtures.PhoenixBlockNumber - 1))
435+
),
436+
evmConfig = petersburgConfig,
437+
isTopHeader = true
438+
).sample.get.withStack(stackIn).withStorage(storage).copy(gas = expectedGas)
430439
val stateOut = op.execute(stateIn)
431440
verifyGas(expectedGas, stateIn, stateOut, allowOOG = false)
432441
}
@@ -435,11 +444,13 @@ class OpCodeGasSpec extends FunSuite with OpCodeTesting with Matchers with Prope
435444
val stateGen = getProgramStateGen(
436445
blockNumberGen = Gen.frequency(
437446
(1, getUInt256Gen(0, Fixtures.ConstantinopleBlockNumber - 1)),
438-
(1, getUInt256Gen(Fixtures.PetersburgBlockNumber + 1, UInt256.MaxValue))
447+
(1, getUInt256Gen(Fixtures.PetersburgBlockNumber + 1, Fixtures.PhoenixBlockNumber - 1))
439448
),
440449
stackGen = getStackGen(elems = 2, maxUInt = Two),
441450
gasGen = getBigIntGen(max = maxGasUsage),
442-
storageGen = getStorageGen(3, getUInt256Gen(max = One))
451+
storageGen = getStorageGen(3, getUInt256Gen(max = One)),
452+
evmConfig = petersburgConfig,
453+
isTopHeader = true
443454
)
444455

445456
forAll(stateGen) { stateIn =>

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ class PrecompiledContractsSpec extends FunSuite with Matchers with PropertyCheck
1515

1616
val vm = new TestVM
1717

18-
def buildContext(recipient: Address, inputData: ByteString, gas: UInt256 = 1000000): PC = {
18+
def buildContext(recipient: Address, inputData: ByteString, gas: UInt256 = 1000000, blockNumber: BigInt = 0): PC = {
1919
val origin = Address(0xcafebabe)
2020

2121
val fakeHeader = BlockHeader(ByteString.empty, ByteString.empty, ByteString.empty, ByteString.empty,
22-
ByteString.empty, ByteString.empty, ByteString.empty, 0, 0, 0, 0, 0, ByteString.empty, ByteString.empty, ByteString.empty)
22+
ByteString.empty, ByteString.empty, ByteString.empty, 0, blockNumber, 0, 0, 0, ByteString.empty, ByteString.empty, ByteString.empty)
2323

2424
val world = MockWorldState().saveAccount(origin, Account.empty())
2525

@@ -257,7 +257,7 @@ class PrecompiledContractsSpec extends FunSuite with Matchers with PropertyCheck
257257
forAll(testData) { (input, expectedResult) =>
258258
val inputArray = Hex.decode(input)
259259
val expectedNumOfRounds = BigInt(1, inputArray.take(4))
260-
val context = buildContext(PrecompiledContracts.Blake2bCompressionAddr, ByteString(inputArray))
260+
val context = buildContext(PrecompiledContracts.Blake2bCompressionAddr, ByteString(inputArray), blockNumber = Fixtures.PhoenixBlockNumber + 1)
261261
val result = vm.run(context)
262262
val gasUsed = context.startGas - result.gasRemaining
263263
gasUsed shouldEqual expectedNumOfRounds

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

+83-34
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,81 @@ import akka.util.ByteString.{empty => bEmpty}
1010
import io.iohk.ethereum.crypto.kec256
1111
import org.bouncycastle.util.encoders.Hex
1212

13-
// EIP-1283
14-
// Spec https://eips.ethereum.org/EIPS/eip-1283
1513
class StoreOpCodeGasPostConstantinopleSpec extends WordSpec with PropertyChecks with Matchers with TestSetup {
1614

17-
val table = Table[String, BigInt, BigInt, BigInt](
18-
("code", "original", "gasUsed", "refund"),
19-
("60006000556000600055", 0, 412, 0),
20-
("60006000556001600055", 0, 20212, 0),
21-
("60016000556000600055", 0, 20212, 19800),
22-
("60016000556002600055", 0, 20212, 0),
23-
("60016000556001600055", 0, 20212, 0),
24-
("60006000556000600055", 1, 5212, 15000),
25-
("60006000556001600055", 1, 5212, 4800),
26-
("60006000556002600055", 1, 5212, 0),
27-
("60026000556000600055", 1, 5212, 15000),
28-
("60026000556003600055", 1, 5212, 0),
29-
("60026000556001600055", 1, 5212, 4800),
30-
("60026000556002600055", 1, 5212, 0),
31-
("60016000556000600055", 1, 5212, 15000),
32-
("60016000556002600055", 1, 5212, 0),
33-
("60016000556001600055", 1, 412, 0),
34-
("600160005560006000556001600055", 0, 40218, 19800),
35-
("600060005560016000556000600055", 1, 10218, 19800)
36-
)
37-
15+
val defaultGaspool = 1000000
3816

17+
// Spec https://eips.ethereum.org/EIPS/eip-1283
3918
"Net gas metering for SSTORE after Constantinople hard fork (EIP-1283)" in {
40-
forAll(table) {
19+
val eip1283table = Table[String, BigInt, BigInt, BigInt](
20+
("code", "original", "gasUsed", "refund"),
21+
("60006000556000600055", 0, 412, 0),
22+
("60006000556001600055", 0, 20212, 0),
23+
("60016000556000600055", 0, 20212, 19800),
24+
("60016000556002600055", 0, 20212, 0),
25+
("60016000556001600055", 0, 20212, 0),
26+
("60006000556000600055", 1, 5212, 15000),
27+
("60006000556001600055", 1, 5212, 4800),
28+
("60006000556002600055", 1, 5212, 0),
29+
("60026000556000600055", 1, 5212, 15000),
30+
("60026000556003600055", 1, 5212, 0),
31+
("60026000556001600055", 1, 5212, 4800),
32+
("60026000556002600055", 1, 5212, 0),
33+
("60016000556000600055", 1, 5212, 15000),
34+
("60016000556002600055", 1, 5212, 0),
35+
("60016000556001600055", 1, 412, 0),
36+
("600160005560006000556001600055", 0, 40218, 19800),
37+
("600060005560016000556000600055", 1, 10218, 19800)
38+
)
39+
40+
forAll(eip1283table) {
4141
(code, original, gasUsed, refund) => {
42-
val result = vm.exec(prepareProgramState(ByteString(Hex.decode(code)), original))
42+
val result = vm.exec(prepareProgramState(ByteString(Hex.decode(code)), original, defaultGaspool, EipToCheck.EIP1283))
4343

4444
result.gasUsed shouldEqual gasUsed
4545
result.gasRefund shouldEqual refund
4646
}
4747
}
4848
}
4949

50+
// Spec https://eips.ethereum.org/EIPS/eip-2200
51+
"Net gas metering for SSTORE after Phoenix hard fork (EIP-2200)" in {
52+
val eip2200table = Table[String, BigInt, BigInt, BigInt, BigInt, Option[ProgramError]](
53+
("code", "original", "gasUsed", "refund", "gaspool", "error"),
54+
("60006000556000600055", 0, 1612, 0, defaultGaspool, None),
55+
("60006000556001600055", 0, 20812, 0, defaultGaspool, None),
56+
("60016000556000600055", 0, 20812, 19200, defaultGaspool, None),
57+
("60016000556002600055", 0, 20812, 0, defaultGaspool, None),
58+
("60016000556001600055", 0, 20812, 0, defaultGaspool, None),
59+
("60006000556000600055", 1, 5812, 15000, defaultGaspool, None),
60+
("60006000556001600055", 1, 5812, 4200, defaultGaspool, None),
61+
("60006000556002600055", 1, 5812, 0, defaultGaspool, None),
62+
("60026000556000600055", 1, 5812, 15000, defaultGaspool, None),
63+
("60026000556003600055", 1, 5812, 0, defaultGaspool, None),
64+
("60026000556001600055", 1, 5812, 4200, defaultGaspool, None),
65+
("60026000556002600055", 1, 5812, 0, defaultGaspool, None),
66+
("60016000556000600055", 1, 5812, 15000, defaultGaspool, None),
67+
("60016000556002600055", 1, 5812, 0, defaultGaspool, None),
68+
("60016000556001600055", 1, 1612, 0, defaultGaspool, None),
69+
("600160005560006000556001600055", 0, 40818, 19200, defaultGaspool, None),
70+
("600060005560016000556000600055", 1, 10818, 19200, defaultGaspool, None),
71+
("6001600055", 1, 2306, 0, 2306, Some(OutOfGas)),
72+
("6001600055", 1, 806, 0, 2307, None)
73+
)
74+
75+
forAll(eip2200table) {
76+
(code, original, gasUsed, refund, gaspool, maybeError) => {
77+
val result = vm.exec(prepareProgramState(ByteString(Hex.decode(code)), original, gaspool, EipToCheck.EIP2200))
78+
79+
result.gasUsed shouldEqual gasUsed
80+
result.gasRefund shouldEqual refund
81+
result.error shouldEqual maybeError
82+
}
83+
}
84+
}
5085
}
5186

5287
trait TestSetup {
53-
val config = EvmConfig.ConstantinopleConfigBuilder(blockchainConfig)
5488
val vm = new TestVM
5589

5690
val senderAddr = Address(0xcafebabeL)
@@ -60,7 +94,7 @@ trait TestSetup {
6094

6195
def defaultWorld: MockWorldState = MockWorldState().saveAccount(senderAddr, senderAcc)
6296

63-
val blockHeader = BlockHeader(
97+
def prepareBlockHeader(blockNumber: BigInt): BlockHeader = BlockHeader(
6498
parentHash = bEmpty,
6599
ommersHash = bEmpty,
66100
beneficiary = bEmpty,
@@ -69,7 +103,7 @@ trait TestSetup {
69103
receiptsRoot = bEmpty,
70104
logsBloom = bEmpty,
71105
difficulty = 1000000,
72-
number = blockchainConfig.constantinopleBlockNumber + 1,
106+
number = blockNumber,
73107
gasLimit = 10000000,
74108
gasUsed = 0,
75109
unixTimestamp = 0,
@@ -78,34 +112,49 @@ trait TestSetup {
78112
nonce = bEmpty
79113
)
80114

81-
def getContext(world: MockWorldState = defaultWorld, inputData: ByteString = bEmpty): PC =
115+
def getContext(world: MockWorldState = defaultWorld, inputData: ByteString = bEmpty, eipToCheck: EipToCheck, gaspool: BigInt): PC =
82116
ProgramContext(
83117
callerAddr = senderAddr,
84118
originAddr = senderAddr,
85119
recipientAddr = None,
86120
gasPrice = 1,
87-
startGas = 1000000,
121+
startGas = gaspool,
88122
inputData = inputData,
89123
value = 100,
90124
endowment = 100,
91125
doTransfer = true,
92-
blockHeader = blockHeader,
126+
blockHeader = eipToCheck.blockHeader,
93127
callDepth = 0,
94128
world = world,
95129
initialAddressesToDelete = Set(),
96-
evmConfig = config,
130+
evmConfig = eipToCheck.config,
97131
originalWorld = world
98132
)
99133

100-
def prepareProgramState(assemblyCode: ByteString, originalValue: BigInt): ProgramState[MockWorldState, MockStorage] = {
134+
def prepareProgramState(assemblyCode: ByteString, originalValue: BigInt, gaspool: BigInt, eipToCheck: EipToCheck): ProgramState[MockWorldState, MockStorage] = {
101135
val newWorld = defaultWorld
102136
.saveAccount(senderAddr, accountWithCode(assemblyCode))
103137
.saveCode(senderAddr, assemblyCode)
104138
.saveStorage(senderAddr, MockStorage(Map(BigInt(0) -> originalValue)))
105139

106-
val context: PC = getContext(newWorld)
140+
val context: PC = getContext(newWorld, eipToCheck = eipToCheck, gaspool = gaspool)
107141
val env = ExecEnv(context, assemblyCode, context.originAddr)
108142

109143
ProgramState(vm, context, env)
110144
}
145+
146+
sealed trait EipToCheck {
147+
val blockHeader: BlockHeader
148+
val config: EvmConfig
149+
}
150+
object EipToCheck {
151+
case object EIP1283 extends EipToCheck {
152+
override val blockHeader: BlockHeader = prepareBlockHeader(blockchainConfig.constantinopleBlockNumber + 1)
153+
override val config: EvmConfig = EvmConfig.ConstantinopleConfigBuilder(blockchainConfig)
154+
}
155+
case object EIP2200 extends EipToCheck {
156+
override val blockHeader: BlockHeader = prepareBlockHeader(blockchainConfig.phoenixBlockNumber + 1)
157+
override val config: EvmConfig = EvmConfig.PhoenixConfigBuilder(blockchainConfig)
158+
}
159+
}
111160
}

0 commit comments

Comments
 (0)