Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions src/main/scala/io/iohk/ethereum/vm/OpCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import akka.util.ByteString
import io.iohk.ethereum.crypto.kec256
import io.iohk.ethereum.domain.{Account, Address, TxLogEntry, UInt256}
import io.iohk.ethereum.domain.UInt256._
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.EtcFork
import io.iohk.ethereum.vm.BlockchainConfigForEvm.{EtcForks, EthForks}
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.EthFork

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

case object SSTORE extends OpCode(0x55, 2, 0, _.G_zero) {
protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
val currentBlockNumber = state.env.blockHeader.number
val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber)
val ethFork = state.config.blockchainConfig.ethForkForBlockNumber(currentBlockNumber)

val eip2200Enabled = isEip2200Enabled(etcFork, ethFork)
val eip1283Enabled = isEip1283Enabled(ethFork)

val (Seq(offset, newValue), stack1) = state.stack.pop(2)
val currentValue = state.storage.load(offset)
val refund: BigInt = if (isEip1283Enabled(state)) {

val refund: BigInt = if (eip2200Enabled || eip1283Enabled) {
val originalValue = state.originalWorld.getStorage(state.ownAddress).load(offset)
if (currentValue != newValue.toBigInt) {
if (originalValue == currentValue) { // fresh slot
Expand Down Expand Up @@ -629,16 +639,24 @@ case object SSTORE extends OpCode(0x55, 2, 0, _.G_zero) {
else
0
}

val updatedStorage = state.storage.store(offset, newValue)
state.withStack(stack1).withStorage(updatedStorage).refundGas(refund).step()
}

protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = {
val (Seq(offset, newValue), _) = state.stack.pop(2)
val currentValue = state.storage.load(offset)
if (isEip1283Enabled(state)) {
// https://eips.ethereum.org/EIPS/eip-1283

val currentBlockNumber = state.env.blockHeader.number
val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber)
val ethFork = state.config.blockchainConfig.ethForkForBlockNumber(currentBlockNumber)

val eip2200Enabled = isEip2200Enabled(etcFork, ethFork)
val eip1283Enabled = isEip1283Enabled(ethFork)

if(eip2200Enabled && state.gas <= state.config.feeSchedule.G_callstipend){
state.config.feeSchedule.G_callstipend + 1 // Out of gas error
} else if (eip2200Enabled || eip1283Enabled) {
if (currentValue == newValue.toBigInt) { // no-op
state.config.feeSchedule.G_sload
} else {
Expand All @@ -663,10 +681,11 @@ case object SSTORE extends OpCode(0x55, 2, 0, _.G_zero) {

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

private def isEip1283Enabled[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Boolean = {
val blockNumber = state.env.blockHeader.number
state.config.blockchainConfig.ethForkForBlockNumber(blockNumber) == EthForks.Constantinople
}
// https://eips.ethereum.org/EIPS/eip-1283
private def isEip1283Enabled(ethFork: EthFork): Boolean = ethFork == EthForks.Constantinople

// https://eips.ethereum.org/EIPS/eip-2200
private def isEip2200Enabled(etcFork: EtcFork, ethFork: EthFork): Boolean = (ethFork >= EthForks.Istanbul || etcFork >= EtcForks.Phoenix)
}

case object JUMP extends OpCode(0x56, 1, 0, _.G_mid) with ConstGas {
Expand Down
3 changes: 2 additions & 1 deletion src/test/scala/io/iohk/ethereum/vm/Fixtures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ object Fixtures {

val ConstantinopleBlockNumber = 200
val PetersburgBlockNumber = 400
val PhoenixBlockNumber = 600

val blockchainConfig = BlockchainConfigForEvm(
// block numbers are irrelevant
Expand All @@ -19,7 +20,7 @@ object Fixtures {
atlantisBlockNumber = 0,
aghartaBlockNumber = 0,
petersburgBlockNumber = PetersburgBlockNumber,
phoenixBlockNumber = 0,
phoenixBlockNumber = PhoenixBlockNumber,
chainId = 0x3d.toByte
)

Expand Down
5 changes: 3 additions & 2 deletions src/test/scala/io/iohk/ethereum/vm/Generators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ object Generators extends ObjectGenerators {
valueGen: Gen[UInt256] = getUInt256Gen(),
blockNumberGen: Gen[UInt256] = getUInt256Gen(0, 300),
evmConfig: EvmConfig = EvmConfig.PhoenixConfigBuilder(blockchainConfig),
returnDataGen: Gen[ByteString] = getByteStringGen(0, 0)
returnDataGen: Gen[ByteString] = getByteStringGen(0, 0),
isTopHeader: Boolean = false
): Gen[PS] =
for {
stack <- stackGen
Expand All @@ -102,7 +103,7 @@ object Generators extends ObjectGenerators {
blockPlacement <- getUInt256Gen(0, blockNumber)
returnData <- returnDataGen

blockHeader = exampleBlockHeader.copy(number = blockNumber - blockPlacement)
blockHeader = exampleBlockHeader.copy(number = if(isTopHeader) blockNumber else blockNumber - blockPlacement)

world = MockWorldState(numberOfHashes = blockNumber - 1)
.saveCode(ownerAddr, code)
Expand Down
25 changes: 18 additions & 7 deletions src/test/scala/io/iohk/ethereum/vm/OpCodeGasSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,12 @@ class OpCodeGasSpec extends FunSuite with OpCodeTesting with Matchers with Prope
}

test(SSTORE) { op =>
// Before Constantinople
// Before Constantinople + Petersburg
// Constantinople + Phoenix tested in SSTOREOpCodeGasPostConstantinopleSpec

val petersburgConfig = EvmConfig.PetersburgConfigBuilder(blockchainConfig)
import petersburgConfig.feeSchedule._

val storage = MockStorage.Empty.store(Zero, One)
val table = Table[UInt256, UInt256, BigInt, BigInt](("offset", "value", "expectedGas", "expectedRefund"),
(0, 1, G_sreset, 0),
Expand All @@ -423,10 +428,14 @@ class OpCodeGasSpec extends FunSuite with OpCodeTesting with Matchers with Prope

forAll(table) { (offset, value, expectedGas, _) =>
val stackIn = Stack.empty().push(value).push(offset)
val stateIn = getProgramStateGen(blockNumberGen = Gen.frequency(
(1, getUInt256Gen(0, Fixtures.ConstantinopleBlockNumber - 1)),
(1, getUInt256Gen(Fixtures.PetersburgBlockNumber + 1, UInt256.MaxValue))
)).sample.get.withStack(stackIn).withStorage(storage).copy(gas = expectedGas)
val stateIn = getProgramStateGen(
blockNumberGen = Gen.frequency(
(1, getUInt256Gen(0, Fixtures.ConstantinopleBlockNumber - 1)),
(1, getUInt256Gen(Fixtures.PetersburgBlockNumber + 1, Fixtures.PhoenixBlockNumber - 1))
),
evmConfig = petersburgConfig,
isTopHeader = true
).sample.get.withStack(stackIn).withStorage(storage).copy(gas = expectedGas)
val stateOut = op.execute(stateIn)
verifyGas(expectedGas, stateIn, stateOut, allowOOG = false)
}
Expand All @@ -435,11 +444,13 @@ class OpCodeGasSpec extends FunSuite with OpCodeTesting with Matchers with Prope
val stateGen = getProgramStateGen(
blockNumberGen = Gen.frequency(
(1, getUInt256Gen(0, Fixtures.ConstantinopleBlockNumber - 1)),
(1, getUInt256Gen(Fixtures.PetersburgBlockNumber + 1, UInt256.MaxValue))
(1, getUInt256Gen(Fixtures.PetersburgBlockNumber + 1, Fixtures.PhoenixBlockNumber - 1))
),
stackGen = getStackGen(elems = 2, maxUInt = Two),
gasGen = getBigIntGen(max = maxGasUsage),
storageGen = getStorageGen(3, getUInt256Gen(max = One))
storageGen = getStorageGen(3, getUInt256Gen(max = One)),
evmConfig = petersburgConfig,
isTopHeader = true
)

forAll(stateGen) { stateIn =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ class PrecompiledContractsSpec extends FunSuite with Matchers with PropertyCheck

val vm = new TestVM

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

val fakeHeader = BlockHeader(ByteString.empty, ByteString.empty, ByteString.empty, ByteString.empty,
ByteString.empty, ByteString.empty, ByteString.empty, 0, 0, 0, 0, 0, ByteString.empty, ByteString.empty, ByteString.empty)
ByteString.empty, ByteString.empty, ByteString.empty, 0, blockNumber, 0, 0, 0, ByteString.empty, ByteString.empty, ByteString.empty)

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

Expand Down Expand Up @@ -257,7 +257,7 @@ class PrecompiledContractsSpec extends FunSuite with Matchers with PropertyCheck
forAll(testData) { (input, expectedResult) =>
val inputArray = Hex.decode(input)
val expectedNumOfRounds = BigInt(1, inputArray.take(4))
val context = buildContext(PrecompiledContracts.Blake2bCompressionAddr, ByteString(inputArray))
val context = buildContext(PrecompiledContracts.Blake2bCompressionAddr, ByteString(inputArray), blockNumber = Fixtures.PhoenixBlockNumber + 1)
val result = vm.run(context)
val gasUsed = context.startGas - result.gasRemaining
gasUsed shouldEqual expectedNumOfRounds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,81 @@ import akka.util.ByteString.{empty => bEmpty}
import io.iohk.ethereum.crypto.kec256
import org.bouncycastle.util.encoders.Hex

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

val table = Table[String, BigInt, BigInt, BigInt](
("code", "original", "gasUsed", "refund"),
("60006000556000600055", 0, 412, 0),
("60006000556001600055", 0, 20212, 0),
("60016000556000600055", 0, 20212, 19800),
("60016000556002600055", 0, 20212, 0),
("60016000556001600055", 0, 20212, 0),
("60006000556000600055", 1, 5212, 15000),
("60006000556001600055", 1, 5212, 4800),
("60006000556002600055", 1, 5212, 0),
("60026000556000600055", 1, 5212, 15000),
("60026000556003600055", 1, 5212, 0),
("60026000556001600055", 1, 5212, 4800),
("60026000556002600055", 1, 5212, 0),
("60016000556000600055", 1, 5212, 15000),
("60016000556002600055", 1, 5212, 0),
("60016000556001600055", 1, 412, 0),
("600160005560006000556001600055", 0, 40218, 19800),
("600060005560016000556000600055", 1, 10218, 19800)
)

val defaultGaspool = 1000000

// Spec https://eips.ethereum.org/EIPS/eip-1283
"Net gas metering for SSTORE after Constantinople hard fork (EIP-1283)" in {
forAll(table) {
val eip1283table = Table[String, BigInt, BigInt, BigInt](
("code", "original", "gasUsed", "refund"),
("60006000556000600055", 0, 412, 0),
("60006000556001600055", 0, 20212, 0),
("60016000556000600055", 0, 20212, 19800),
("60016000556002600055", 0, 20212, 0),
("60016000556001600055", 0, 20212, 0),
("60006000556000600055", 1, 5212, 15000),
("60006000556001600055", 1, 5212, 4800),
("60006000556002600055", 1, 5212, 0),
("60026000556000600055", 1, 5212, 15000),
("60026000556003600055", 1, 5212, 0),
("60026000556001600055", 1, 5212, 4800),
("60026000556002600055", 1, 5212, 0),
("60016000556000600055", 1, 5212, 15000),
("60016000556002600055", 1, 5212, 0),
("60016000556001600055", 1, 412, 0),
("600160005560006000556001600055", 0, 40218, 19800),
("600060005560016000556000600055", 1, 10218, 19800)
)

forAll(eip1283table) {
(code, original, gasUsed, refund) => {
val result = vm.exec(prepareProgramState(ByteString(Hex.decode(code)), original))
val result = vm.exec(prepareProgramState(ByteString(Hex.decode(code)), original, defaultGaspool, EipToCheck.EIP1283))

result.gasUsed shouldEqual gasUsed
result.gasRefund shouldEqual refund
}
}
}

// Spec https://eips.ethereum.org/EIPS/eip-2200
"Net gas metering for SSTORE after Phoenix hard fork (EIP-2200)" in {
val eip2200table = Table[String, BigInt, BigInt, BigInt, BigInt, Option[ProgramError]](
("code", "original", "gasUsed", "refund", "gaspool", "error"),
("60006000556000600055", 0, 1612, 0, defaultGaspool, None),
("60006000556001600055", 0, 20812, 0, defaultGaspool, None),
("60016000556000600055", 0, 20812, 19200, defaultGaspool, None),
("60016000556002600055", 0, 20812, 0, defaultGaspool, None),
("60016000556001600055", 0, 20812, 0, defaultGaspool, None),
("60006000556000600055", 1, 5812, 15000, defaultGaspool, None),
("60006000556001600055", 1, 5812, 4200, defaultGaspool, None),
("60006000556002600055", 1, 5812, 0, defaultGaspool, None),
("60026000556000600055", 1, 5812, 15000, defaultGaspool, None),
("60026000556003600055", 1, 5812, 0, defaultGaspool, None),
("60026000556001600055", 1, 5812, 4200, defaultGaspool, None),
("60026000556002600055", 1, 5812, 0, defaultGaspool, None),
("60016000556000600055", 1, 5812, 15000, defaultGaspool, None),
("60016000556002600055", 1, 5812, 0, defaultGaspool, None),
("60016000556001600055", 1, 1612, 0, defaultGaspool, None),
("600160005560006000556001600055", 0, 40818, 19200, defaultGaspool, None),
("600060005560016000556000600055", 1, 10818, 19200, defaultGaspool, None),
("6001600055", 1, 2306, 0, 2306, Some(OutOfGas)),
("6001600055", 1, 806, 0, 2307, None)
)

forAll(eip2200table) {
(code, original, gasUsed, refund, gaspool, maybeError) => {
val result = vm.exec(prepareProgramState(ByteString(Hex.decode(code)), original, gaspool, EipToCheck.EIP2200))

result.gasUsed shouldEqual gasUsed
result.gasRefund shouldEqual refund
result.error shouldEqual maybeError
}
}
}
}

trait TestSetup {
val config = EvmConfig.ConstantinopleConfigBuilder(blockchainConfig)
val vm = new TestVM

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

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

val blockHeader = BlockHeader(
def prepareBlockHeader(blockNumber: BigInt): BlockHeader = BlockHeader(
parentHash = bEmpty,
ommersHash = bEmpty,
beneficiary = bEmpty,
Expand All @@ -69,7 +103,7 @@ trait TestSetup {
receiptsRoot = bEmpty,
logsBloom = bEmpty,
difficulty = 1000000,
number = blockchainConfig.constantinopleBlockNumber + 1,
number = blockNumber,
gasLimit = 10000000,
gasUsed = 0,
unixTimestamp = 0,
Expand All @@ -78,34 +112,49 @@ trait TestSetup {
nonce = bEmpty
)

def getContext(world: MockWorldState = defaultWorld, inputData: ByteString = bEmpty): PC =
def getContext(world: MockWorldState = defaultWorld, inputData: ByteString = bEmpty, eipToCheck: EipToCheck, gaspool: BigInt): PC =
ProgramContext(
callerAddr = senderAddr,
originAddr = senderAddr,
recipientAddr = None,
gasPrice = 1,
startGas = 1000000,
startGas = gaspool,
inputData = inputData,
value = 100,
endowment = 100,
doTransfer = true,
blockHeader = blockHeader,
blockHeader = eipToCheck.blockHeader,
callDepth = 0,
world = world,
initialAddressesToDelete = Set(),
evmConfig = config,
evmConfig = eipToCheck.config,
originalWorld = world
)

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

val context: PC = getContext(newWorld)
val context: PC = getContext(newWorld, eipToCheck = eipToCheck, gaspool = gaspool)
val env = ExecEnv(context, assemblyCode, context.originAddr)

ProgramState(vm, context, env)
}

sealed trait EipToCheck {
val blockHeader: BlockHeader
val config: EvmConfig
}
object EipToCheck {
case object EIP1283 extends EipToCheck {
override val blockHeader: BlockHeader = prepareBlockHeader(blockchainConfig.constantinopleBlockNumber + 1)
override val config: EvmConfig = EvmConfig.ConstantinopleConfigBuilder(blockchainConfig)
}
case object EIP2200 extends EipToCheck {
override val blockHeader: BlockHeader = prepareBlockHeader(blockchainConfig.phoenixBlockNumber + 1)
override val config: EvmConfig = EvmConfig.PhoenixConfigBuilder(blockchainConfig)
}
}
}