Refactor interpreter dispatch (#65)

* move forks constants, rename errors

* Move vm/utils to vm/interpreter/utils

* initial opcodes refactoring

* Add refactored Comparison & Bitwise Logic Operations

* Add sha3 and address, simplify macro, support pop 0

* balance, origin, caller, callValue

* fix gas copy opcodes gas costs, add callDataLoad/Size/Copy, CodeSize/Copy and gas price opcode

* Update with 30s, 40s, 50s opcodes + impl of balance + stack improvement

* add push, dup, swap, log, create and call operations

* finish opcode implementation

* Add the new dispatching logic

* Pass the opcode test

* Make test_vm_json compile

* halt execution without exceptions for Return, Revert, selfdestruct (fix #62)

* Properly catch and recover from EVM exceptions (stack underflow ...)

* Fix byte op

* Fix jump regressions

* Update for latest devel, don't import old dispatch code as quasiBoolean macro is broken by latest devel

* Fix sha3 regression on empty memory slice and until end of range slice

* Fix padding / range error on expXY_success (gas computation left)

* update logging procs

* Add tracing - expXY_success is not a regression, sload stub was accidentally passing the test

* Reuse the same stub as OO implementation

* Delete previous opcode implementation

* Delete object oriented fork code

* Delete exceptions that were used as control flows

* delete base.nim 🔥, yet another OO remnants

* Delete opcode table

* Enable omputed gotos and compile-time gas fees

* Revert const gasCosts -> generates SIGSEGV

* inline push, swap and dup opcodes

* loggers are now template again, why does this pass new tests?

* Trigger CI rebuild after rocksdb fix https://github.com/status-im/nim-rocksdb/pull/5

* Address review comment on "push" + VMTests in debug mode (not release)

* Address review comment: don't tag fork by default, make opcode impl grepable

* Static compilation fixes after rebasing

* fix the initialization of the VM database

* add a missing import

* Deactivate balance and sload test following #59

* Reactivate stack check (deactivated in #59, necessary to pass tests)

* Merge remaining opcodes implementation from #59

* Merge callDataLoad and codeCopy fixes, todo simplify see #67
This commit is contained in:
Mamy Ratsimbazafy 2018-07-06 09:52:31 +02:00 committed by GitHub
parent 18b7bbb3b0
commit 4b5eada322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 1415 additions and 2324 deletions

View File

@ -310,7 +310,7 @@ OK: 11/12 Fail: 1/12 Skip: 0/12
+ calldataload0.json OK
+ calldataload1.json OK
+ calldataload2.json OK
+ calldataloadSizeTooHigh.json OK
- calldataloadSizeTooHigh.json Fail
+ calldataloadSizeTooHighPartial.json OK
+ calldataload_BigOffset.json OK
+ calldatasize0.json OK
@ -318,7 +318,7 @@ OK: 11/12 Fail: 1/12 Skip: 0/12
+ calldatasize2.json OK
+ caller.json OK
+ callvalue.json OK
- codecopy0.json Fail
+ codecopy0.json OK
+ codecopyZeroMemExpansion.json OK
- codecopy_DataIndexTooHigh.json Fail
+ codesize.json OK
@ -445,7 +445,7 @@ OK: 26/52 Fail: 11/52 Skip: 15/52
+ jumpi_at_the_end.json OK
+ jumpifInsidePushWithJumpDest.json OK
+ jumpifInsidePushWithoutJumpDest.json OK
- kv1.json Fail
+ kv1.json OK
+ log1MemExp.json OK
+ loop_stacklimit_1020.json OK
+ loop_stacklimit_1021.json OK
@ -461,8 +461,8 @@ OK: 26/52 Fail: 11/52 Skip: 15/52
+ mstore0.json OK
+ mstore1.json OK
+ mstore8MemExp.json OK
- mstore8WordToBigError.json Fail
- mstore8_0.json Fail
+ mstore8WordToBigError.json OK
+ mstore8_0.json OK
- mstore8_1.json Fail
+ mstoreMemExp.json OK
+ mstoreWordToBigError.json OK
@ -483,7 +483,7 @@ OK: 26/52 Fail: 11/52 Skip: 15/52
+ swapAt52becameMstore.json OK
+ when.json OK
```
OK: 110/145 Fail: 34/145 Skip: 1/145
OK: 113/145 Fail: 31/145 Skip: 1/145
## vmLogTest
```diff
+ log0_emptyMem.json OK
@ -673,10 +673,10 @@ OK: 0/17 Fail: 0/17 Skip: 17/17
- sha3_memSizeQuadraticCost33.json Fail
- sha3_memSizeQuadraticCost63.json Fail
- sha3_memSizeQuadraticCost64.json Fail
- sha3_memSizeQuadraticCost64_2.json Fail
+ sha3_memSizeQuadraticCost64_2.json OK
- sha3_memSizeQuadraticCost65.json Fail
```
OK: 4/18 Fail: 14/18 Skip: 0/18
OK: 5/18 Fail: 13/18 Skip: 0/18
## vmSystemOperations
```diff
ABAcalls0.json Skip
@ -719,9 +719,9 @@ OK: 4/18 Fail: 14/18 Skip: 0/18
OK: 0/36 Fail: 0/36 Skip: 36/36
## vmTests
```diff
+ arith.json OK
+ boolean.json OK
+ mktx.json OK
- arith.json Fail
- boolean.json Fail
- mktx.json Fail
+ suicide.json OK
```
OK: 4/4 Fail: 0/4 Skip: 0/4
OK: 1/4 Fail: 3/4 Skip: 0/4

View File

@ -5,85 +5,52 @@ import
proc default(t: typedesc): t = discard
# constants
const
UINT_256_MAX*: UInt256 = high(UInt256)
INT_256_MAX_AS_UINT256* = high(Uint256) shr 1
NULLBYTE* = "\x00"
EMPTYWORD* = repeat(NULLBYTE, 32)
UINT160CEILING*: UInt256 = 2.u256.pow(160)
ZERO_ADDRESS* = default(EthAddress)
CREATE_CONTRACT_ADDRESS* = ZERO_ADDRESS
ZERO_HASH32* = Hash256()
STACK_DEPTH_LIMIT* = 1024
let
UINT_256_MAX*: UInt256 = high(UInt256)
INT_256_MAX_AS_UINT256* = cast[Uint256](high(Int256))
NULLBYTE* = "\x00"
EMPTYWORD* = repeat(NULLBYTE, 32)
UINT160CEILING*: UInt256 = 2.u256.pow(160)
ZERO_ADDRESS* = default(EthAddress)
CREATE_CONTRACT_ADDRESS* = ZERO_ADDRESS
ZERO_HASH32* = Hash256()
STACK_DEPTH_LIMIT* = 1024
GAS_LIMIT_EMA_DENOMINATOR* = 1_024
GAS_LIMIT_ADJUSTMENT_FACTOR* = 1_024
GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3
GAS_LIMIT_EMA_DENOMINATOR* = 1_024
GAS_LIMIT_ADJUSTMENT_FACTOR* = 1_024
GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3
GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR* = 2
DIFFICULTY_ADJUSTMENT_DENOMINATOR* = 2_048.u256
DIFFICULTY_MINIMUM* = 131_072.u256
DIFFICULTY_ADJUSTMENT_DENOMINATOR* = 2_048.u256
DIFFICULTY_MINIMUM* = 131_072.u256
BYZANTIUM_DIFFICULTY_ADJUSTMENT_CUTOFF* = 9
BOMB_EXPONENTIAL_PERIOD* = 100_000.u256
BOMB_EXPONENTIAL_FREE_PERIODS* = 2.u256
BOMB_EXPONENTIAL_PERIOD* = 100_000.u256
BOMB_EXPONENTIAL_FREE_PERIODS* = 2.u256
BLOCK_REWARD* = 5.u256 * 2.u256 # denoms.ether
BLOCK_REWARD* = 5.u256 * 2.u256 # denoms.ether
UNCLE_DEPTH_PENALTY_FACTOR* = 8.u256
UNCLE_DEPTH_PENALTY_FACTOR* = 8.u256
MAX_UNCLE_DEPTH* = 6.u256
MAX_UNCLES* = 2.u256
MAX_UNCLE_DEPTH* = 6.u256
MAX_UNCLES* = 2.u256
EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest
EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest
GENESIS_BLOCK_NUMBER* = 0.u256
GENESIS_DIFFICULTY* = 131_072.u256
GENESIS_GAS_LIMIT* = 3_141_592
GENESIS_PARENT_HASH* = ZERO_HASH32
GENESIS_COINBASE* = ZERO_ADDRESS
GENESIS_NONCE* = "\x00\x00\x00\x00\x00\x00\x00B"
GENESIS_MIX_HASH* = ZERO_HASH32
GENESIS_EXTRA_DATA* = ""
GAS_LIMIT_MINIMUM* = 5000
GENESIS_BLOCK_NUMBER* = 0.u256
GENESIS_DIFFICULTY* = 131_072.u256
GENESIS_GAS_LIMIT* = 3_141_592
GENESIS_PARENT_HASH* = ZERO_HASH32
GENESIS_COINBASE* = ZERO_ADDRESS
GENESIS_NONCE* = "\x00\x00\x00\x00\x00\x00\x00B"
GENESIS_MIX_HASH* = ZERO_HASH32
GENESIS_EXTRA_DATA* = ""
GAS_LIMIT_MINIMUM* = 5000
BLANK_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
EMPTY_SHA3* = "883f7328a6c30727a655daff17eba3a86049871bc7839a5b71e2bc26a99c4d4c".toDigest
BLANK_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
EMPTY_SHA3* = "883f7328a6c30727a655daff17eba3a86049871bc7839a5b71e2bc26a99c4d4c".toDigest
GAS_MOD_EXP_QUADRATIC_DENOMINATOR* = 20.u256
MAX_PREV_HEADER_DEPTH* = 256.toBlockNumber
FORK_ICEAGE_BLKNUM* = 200_000.u256
FORK_HOMESTED_BLKNUM* = 1_150_000.u256
FORK_DAO_BLKNUM* = 1_920_000.u256
FORK_TANGERINE_WHISTLE_BLKNUM* = 2_463_000.u256
FORK_SPURIOUS_DRAGON_BLKNUM* = 2_675_000.u256
FORK_BYZANTIUM_BLKNUM* = 4_370_000.u256
# TODO: Move the below to a new utils unit?
type
Fork = enum fkUnknown, fkFrontier, fkIceAge, fkHomested, fkDao, fkTangerineWhistle, fkSpuriousDragon, fkByzantium
UInt256Pair = tuple[a: Uint256, b: Uint256]
proc `..`*(a, b: Uint256): UInt256Pair = (a, b)
proc contains*(ab: UInt256Pair, v: UInt256): bool =
return v >= ab[0] and v <= ab[1]
proc toFork*(blockNumber: UInt256): Fork =
# TODO - Refactoring: superseded by newNimbusVM for the time being #https://github.com/status-im/nimbus/pull/37
# TODO - Refactoring: redundant with `chain.nim` getVM
result = fkUnknown
let one = u256(1)
if blockNumber in u256(0)..FORK_ICEAGE_BLKNUM - one: result = fkFrontier
elif blockNumber in FORK_ICEAGE_BLKNUM..FORK_HOMESTED_BLKNUM - one: result = fkIceAge
elif blockNumber in FORK_HOMESTED_BLKNUM..FORK_DAO_BLKNUM - one: result = fkHomested
elif blockNumber in FORK_DAO_BLKNUM..FORK_TANGERINE_WHISTLE_BLKNUM - one: result = fkDao
elif blockNumber in FORK_TANGERINE_WHISTLE_BLKNUM..FORK_SPURIOUS_DRAGON_BLKNUM - one: result = fkTangerineWhistle
elif blockNumber in FORK_SPURIOUS_DRAGON_BLKNUM..FORK_BYZANTIUM_BLKNUM - one: result = fkSpuriousDragon
else:
if blockNumber >= FORK_BYZANTIUM_BLKNUM: result = fkByzantium # Update for constantinople when announced
GAS_MOD_EXP_QUADRATIC_DENOMINATOR* = 20.u256
MAX_PREV_HEADER_DEPTH* = 256.toBlockNumber
MaxCallDepth* = 1024

View File

@ -119,10 +119,12 @@ proc setNonce*(db: var AccountStateDB, address: EthAddress, nonce: UInt256) =
db.setAccount(address, account)
proc getNonce*(db: AccountStateDB, address: EthAddress): UInt256 =
# TODO it is very strange that we require a var param here
validateCanonicalAddress(address, title="Storage Address")
let account = db.getAccount(address)
return account.nonce
account.nonce
proc toByteRange_Unnecessary*(h: KeccakHash): ByteRange =
## XXX: Another proc used to mark unnecessary conversions it the code

View File

@ -9,9 +9,6 @@ type
EVMError* = object of Exception
## Base error class for all evm errors.
VMNotFound* = object of EVMError
## No VM available for the provided block number.
BlockNotFound* = object of EVMError
## The block with the given number/hash does not exist.
@ -24,9 +21,6 @@ type
ValidationError* = object of EVMError
## Error to signal something does not pass a validation check.
Halt* = object of EVMError
## Raised by opcode function to halt vm execution.
VMError* = object of EVMError
## Class of errors which can be raised during VM execution.
erasesReturnData*: bool
@ -57,9 +51,6 @@ type
ContractCreationCollision* = object of VMError
## Error signaling that there was an address collision during contract creation.
Revert* = object of VMError
## Error used by the REVERT opcode
WriteProtection* = object of VMError
## Error raised if an attempt to modify the state database is made while
## operating inside of a STATICCALL context.
@ -78,8 +69,3 @@ proc makeVMError*: ref VMError =
new(result)
result.burnsGas = true
result.erasesReturnData = true
proc makeRevert*(): ref Revert =
new(result)
result.burnsGas = false
result.erasesReturnData = false

View File

@ -32,7 +32,7 @@ template trace*(l: Logger, msg: string) =
when DEBUG:
l.log(msg, fgBlue)
proc getLogger*(name: string): Logger =
proc getLogger*(name: string): Logger {.inline.}=
result = Logger(name: name)
# proc disableLogging* =

View File

@ -43,7 +43,7 @@ proc validateStackItem*(value: string) =
raise newException(ValidationError,
&"Invalid stack item: expected 32 bytes, got {value.len}: value is {value}")
proc validateStackItem*(value: string | seq[byte]) =
proc validateStackItem*(value: openarray[byte]) =
if value.len > 32:
raise newException(ValidationError,
&"Invalid stack item: expected 32 bytes, got {value.len}: value is {value}")

View File

@ -1,64 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../logging, ../constants, ../errors, ../transaction, ../vm_types,
../block_types, ../vm_state, ../vm_state_transactions, ../db/db_chain, ../utils/header,
./computation
type
VMkind* = enum
## List of VMs forks (py-evm vm_class) of the Ethereum network
# TODO: used in Chain.vmsByRange: can we store the runtimetime in a seq/tuple instead?
vmkFrontier, vmkHomestead, vmkTangerineWhistle, vmkSpuriousDragon, vmkByzantium
VM* = ref object of RootObj
# The VM class represents the Chain rules for a specific protocol definition
# such as the Frontier or Homestead network. Defining an Chain defining
# individual VM classes for each fork of the protocol rules within that
# network
chainDB*: BaseChainDB
isStateless*: bool
state*: BaseVMState
`block`*: Block
# TODO - Refactoring: superseded by newNimbusVM for the time being #https://github.com/status-im/nimbus/pull/37
# proc newVM*(header: BlockHeader, chainDB: BaseChainDB): VM =
# new(result)
# result.chainDB = chainDB
method name*(vm: VM): string =
"VM"
method addTransaction*(vm: var VM, transaction: BaseTransaction, computation: BaseComputation): Block =
# Add a transaction to the given block and save the block data into chaindb
# var receipt = vm.state.makeReceipt(transaction, computation)
# var transactionIdx = len(vm.`block`.transactions)
# TODO
return Block()
method applyTransaction*(vm: var VM, transaction: BaseTransaction): (BaseComputation, Block) =
# Apply the transaction to the vm in the current block
if vm.isStateless:
var (computation, b, trieData) = vm.state.applyTransaction(
transaction,
vm.`block`,
isStateless=true)
vm.`block` = b
result = (computation, b)
# Persist changed transaction and receipt key-values to self.chaindb.
else:
var (computation, _, _) = vm.state.applyTransaction(
transaction,
vm.`block`,
isStateless=false)
discard vm.addTransaction(transaction, computation)
result = (computation, vm.`block`)

View File

@ -45,6 +45,7 @@ proc newCodeStreamFromUnescaped*(code: string): CodeStream =
newCodeStream(codeBytes)
proc read*(c: var CodeStream, size: int): seq[byte] =
# TODO: use openarray[bytes]
if c.pc + size - 1 < c.bytes.len:
result = c.bytes[c.pc .. c.pc + size - 1]
c.pc += size
@ -60,18 +61,17 @@ proc readVmWord*(c: var CodeStream, n: int): UInt256 =
let last = min(c.pc + n, c.bytes.len)
let toWrite = last - c.pc
for i in 0 ..< toWrite : result_bytes[i] = c.bytes[last - i - 1]
for j in toWrite ..< 32: result_bytes[j] = 0
c.pc = last
proc len*(c: CodeStream): int =
len(c.bytes)
proc next*(c: var CodeStream): Op =
var nextOpcode = c.read(1)
if nextOpcode.len != 0:
return Op(nextOpcode[0])
if c.pc != c.bytes.len:
result = Op(c.bytes[c.pc])
inc c.pc
else:
return Op.STOP
result = Stop
iterator items*(c: var CodeStream): Op =
var nextOpcode = c.next()
@ -83,9 +83,10 @@ proc `[]`*(c: CodeStream, offset: int): Op =
Op(c.bytes[offset])
proc peek*(c: var CodeStream): Op =
var currentPc = c.pc
result = c.next()
c.pc = currentPc
if c.pc <= c.bytes.len:
result = Op(c.bytes[c.pc])
else:
result = Stop
proc updatePc*(c: var CodeStream, value: int) =
c.pc = min(value, len(c))
@ -100,7 +101,7 @@ when false:
finally:
cs.pc = anchorPc
proc isValidOpcode*(c: var CodeStream, position: int): bool =
proc isValidOpcode*(c: CodeStream, position: int): bool =
if position >= len(c):
return false
if position in c.invalidPositions:

View File

@ -9,19 +9,11 @@ import
strformat, strutils, sequtils, macros, terminal, math, tables,
eth_common, byteutils,
../constants, ../errors, ../validation, ../vm_state, ../logging, ../vm_types,
./interpreter/[opcode_values,gas_meter, gas_costs],
./code_stream, ./memory, ./message, ./stack,
./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks],
./code_stream, ./memory, ./message, ./stack
# TODO further refactoring of gas cost
./forks/f20150730_frontier/frontier_vm_state,
./forks/f20161018_tangerine_whistle/tangerine_vm_state
method newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation {.base.}=
raise newException(ValueError, "Must be implemented by subclasses")
# TODO refactor that
method newBaseComputation*(vmState: FrontierVMState, message: Message): BaseComputation =
new(result)
proc newBaseComputation*(vmState: BaseVMState, blockNumber: UInt256, message: Message): BaseComputation =
new result
result.vmState = vmState
result.msg = message
result.memory = Memory()
@ -32,34 +24,12 @@ method newBaseComputation*(vmState: FrontierVMState, message: Message): BaseComp
result.logEntries = @[]
result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr
result.rawOutput = "0x"
result.gasCosts = BaseGasCosts
result.gasCosts = blockNumber.toFork.forkToSchedule
method newBaseComputation*(vmState: TangerineVMState, message: Message): BaseComputation =
new(result)
result.vmState = vmState
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.gasMeter = newGasMeter(message.gas)
result.children = @[]
result.accountsToDelete = initTable[EthAddress, EthAddress]()
result.logEntries = @[]
result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr
result.rawOutput = "0x"
result.gasCosts = TangerineGasCosts
method logger*(computation: BaseComputation): Logger =
proc logger*(computation: BaseComputation): Logger =
logging.getLogger("vm.computation.BaseComputation")
method applyMessage*(c: var BaseComputation): BaseComputation =
# Execution of an VM message
raise newException(ValueError, "Must be implemented by subclasses")
method applyCreateMessage(c: var BaseComputation): BaseComputation =
# Execution of an VM message to create a new contract
raise newException(ValueError, "Must be implemented by subclasses")
method isOriginComputation*(c: BaseComputation): bool =
proc isOriginComputation*(c: BaseComputation): bool =
# Is this computation the computation initiated by a transaction
c.msg.isOrigin
@ -69,13 +39,13 @@ template isSuccess*(c: BaseComputation): bool =
template isError*(c: BaseComputation): bool =
not c.isSuccess
method shouldBurnGas*(c: BaseComputation): bool =
proc shouldBurnGas*(c: BaseComputation): bool =
c.isError and c.error.burnsGas
method shouldEraseReturnData*(c: BaseComputation): bool =
proc shouldEraseReturnData*(c: BaseComputation): bool =
c.isError and c.error.erasesReturnData
method prepareChildMessage*(
proc prepareChildMessage*(
c: var BaseComputation,
gas: GasInt,
to: EthAddress,
@ -96,51 +66,16 @@ method prepareChildMessage*(
code,
childOptions)
method output*(c: BaseComputation): string =
proc output*(c: BaseComputation): string =
if c.shouldEraseReturnData:
""
else:
c.rawOutput
method `output=`*(c: var BaseComputation, value: string) =
proc `output=`*(c: var BaseComputation, value: string) =
c.rawOutput = value
macro generateChildBaseComputation*(t: typed, vmState: typed, childMsg: typed): untyped =
var typ = repr(getType(t)[1]).split(":", 1)[0]
var name = ident(&"new{typ}")
var typName = ident(typ)
result = quote:
block:
var c: `typName`
if childMsg.isCreate:
var child = `name`(`vmState`, `childMsg`)
c = child.applyCreateMessage()
else:
var child = `name`(`vmState`, `childMsg`)
c = child.applyMessage()
c
method addChildBaseComputation*(c: var BaseComputation, childBaseComputation: BaseComputation) =
if childBaseComputation.isError:
if childBaseComputation.msg.isCreate:
c.returnData = childBaseComputation.output
elif childBaseComputation.shouldBurnGas:
c.returnData = ""
else:
c.returnData = childBaseComputation.output
else:
if childBaseComputation.msg.isCreate:
c.returnData = ""
else:
c.returnData = childBaseComputation.output
c.children.add(childBaseComputation)
method applyChildBaseComputation*(c: var BaseComputation, childMsg: Message): BaseComputation =
var childBaseComputation = generateChildBaseComputation(c, c.vmState, childMsg)
c.addChildBaseComputation(childBaseComputation)
result = childBaseComputation
method registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) =
proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) =
validateCanonicalAddress(beneficiary, title="self destruct beneficiary address")
if c.msg.storageAddress in c.accountsToDelete:
@ -149,144 +84,40 @@ method registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddre
"registered for deletion multiple times")
c.accountsToDelete[c.msg.storageAddress] = beneficiary
method addLogEntry*(c: var BaseComputation, account: EthAddress, topics: seq[UInt256], data: string) =
proc addLogEntry*(c: var BaseComputation, account: EthAddress, topics: seq[UInt256], data: string) =
validateCanonicalAddress(account, title="log entry address")
c.logEntries.add((account, topics, data))
# many methods are basically TODO, but they still return valid values
# in order to test some existing code
method getAccountsForDeletion*(c: BaseComputation): seq[(string, string)] =
proc getAccountsForDeletion*(c: BaseComputation): seq[(string, string)] =
# TODO
if c.isError:
result = @[]
else:
result = @[]
method getLogEntries*(c: BaseComputation): seq[(string, seq[UInt256], string)] =
proc getLogEntries*(c: BaseComputation): seq[(string, seq[UInt256], string)] =
# TODO
if c.isError:
result = @[]
else:
result = @[]
method getGasRefund*(c: BaseComputation): GasInt =
proc getGasRefund*(c: BaseComputation): GasInt =
if c.isError:
result = 0
else:
result = c.gasMeter.gasRefunded + c.children.mapIt(it.getGasRefund()).foldl(a + b, 0'i64)
method getGasUsed*(c: BaseComputation): GasInt =
proc getGasUsed*(c: BaseComputation): GasInt =
if c.shouldBurnGas:
result = c.msg.gas
else:
result = max(0, c.msg.gas - c.gasMeter.gasRemaining)
method getGasRemaining*(c: BaseComputation): GasInt =
proc getGasRemaining*(c: BaseComputation): GasInt =
if c.shouldBurnGas:
result = 0
else:
result = c.gasMeter.gasRemaining
#
# Context Manager API
#
template inComputation*(c: untyped, handler: untyped): untyped =
# use similarly to the python manager
#
# inComputation(computation):
# stuff
`c`.logger.debug(
"COMPUTATION STARTING: gas: $1 | from: $2 | to: $3 | value: $4 | depth: $5 | static: $6" % [
$`c`.msg.gas,
toHex(`c`.msg.sender),
toHex(`c`.msg.to),
$`c`.msg.value,
$`c`.msg.depth,
if c.msg.isStatic: "y" else: "n"])
try:
`handler`
c.logger.debug(
"COMPUTATION SUCCESS: from: $1 | to: $2 | value: $3 | depth: $4 | static: $5 | gas-used: $6 | gas-remaining: $7" % [
toHex(c.msg.sender),
toHex(c.msg.to),
$c.msg.value,
$c.msg.depth,
if c.msg.isStatic: "y" else: "n",
$(c.msg.gas - c.gasMeter.gasRemaining),
$c.gasMeter.gasRemaining])
except VMError:
`c`.logger.debug(
"COMPUTATION ERROR: gas: $1 | from: $2 | to: $3 | value: $4 | depth: $5 | static: $6 | error: $7" % [
$`c`.msg.gas,
toHex(`c`.msg.sender),
toHex(`c`.msg.to),
$c.msg.value,
$c.msg.depth,
if c.msg.isStatic: "y" else: "n",
getCurrentExceptionMsg()])
`c`.error = Error(info: getCurrentExceptionMsg())
if c.shouldBurnGas:
c.gasMeter.consumeGas(
c.gasMeter.gasRemaining,
reason="Zeroing gas due to VM Exception: $1" % getCurrentExceptionMsg())
method getOpcodeFn*(computation: var BaseComputation, op: Op): Opcode =
# TODO use isValidOpcode and remove the Op --> Opcode indirection
if computation.opcodes.len > 0 and computation.opcodes.hasKey(op):
OpCode(kind: op, runLogic: computation.opcodes[op])
else:
raise newException(InvalidInstruction,
&"Invalid opcode {op}")
# Super dirty fix for https://github.com/status-im/nimbus/issues/46
# Pending https://github.com/status-im/nimbus/issues/36
# Disentangle opcode logic
from ./interpreter/opcodes_impl/call import runLogic, BaseCall
template run*(opcode: Opcode, computation: var BaseComputation) =
# Hook for performing the actual VM execution
# opcode.consumeGas(computation)
if opcode.kind == Op.Call: # Super dirty fix for https://github.com/status-im/nimbus/issues/46
# TODO remove this branch
runLogic(BaseCall(opcode), computation)
elif computation.gasCosts[opcode.kind].kind != GckFixed:
opcode.runLogic(computation)
else:
computation.gasMeter.consumeGas(computation.gasCosts[opcode.kind].cost, reason = $opcode.kind)
opcode.runLogic(computation)
method logger*(opcode: Opcode): Logger =
logging.getLogger(&"vm.opcode.{opcode.kind}")
macro applyComputation*(t: typed, vmState: untyped, message: untyped): untyped =
# Perform the computation that would be triggered by the VM message
# c.applyComputation(vmState, message)
var typ = repr(getType(t)[1]).split(":", 1)[0]
var name = ident(&"new{typ}")
var typName = ident(typ)
result = quote:
block:
var res: `typName`
var c = `t` # `name`(`vmState`, `message`)
var handler = proc: `typName` =
# TODO
# if `message`.codeAddress in c.precompiles:
# c.precompiles[`message`.codeAddress].run(c)
# return c
for op in c.code:
var opcode = c.getOpcodeFn(op)
c.logger.trace(
"OPCODE: 0x$1 ($2) | pc: $3" % [opcode.kind.int.toHex(2), $opcode.kind, $max(0, c.code.pc - 1)])
try:
opcode.run(c)
except Halt:
break
c.logger.log($c.stack & "\n\n", fgGreen)
return c
inComputation(c):
res = handler()
c

View File

@ -1 +0,0 @@
Folder to be deleted

View File

@ -1,63 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../../../logging, ../../../constants, ../../../errors, ../../../transaction,
../../../block_types,
../../../utils/header
type
FrontierBlock* = ref object of Block
# bloomFilter*: BloomFilter
# header*: BlockHeader
transactions*: seq[BaseTransaction]
# transactionClass*: Any
stateRoot*: cstring
# fields*: seq[(string, Function)]
# cachedRlp: cstring
# uncles*: void
# import
# rlp, rlp.sedes, eth_bloom, evm.constants, evm.rlp.receipts, evm.rlp.blocks,
# evm.rlp.headers, evm.utils.keccak, transactions
proc makeFrontierBlock*(header: BlockHeader; transactions: seq[BaseTransaction]; uncles: void): FrontierBlock =
new result
if transactions.len == 0:
result.transactions = @[]
# if uncles is None:
# uncles = @[]
# result.bloomFilter = BloomFilter(header.bloom)
# method number*(self: FrontierBlock): int =
# return self.header.blockNumber
# method hash*(self: FrontierBlock): cstring =
# return self.header.hash
# method getTransactionClass*(cls: typedesc): typedesc =
# return cls.transactionClass
# method getReceipts*(self: FrontierBlock; chaindb: BaseChainDB): seq[Receipt] =
# return chaindb.getReceipts(self.header, Receipt)
# method fromHeader*(cls: typedesc; header: BlockHeader; chaindb: BaseChainDB): FrontierBlock =
# ## Returns the block denoted by the given block header.
# if header.unclesHash == EMPTYUNCLEHASH:
# var uncles = @[]
# else:
# uncles = chaindb.getBlockUncles(header.unclesHash)
# var transactions = chaindb.getBlockTransactions(header, cls.getTransactionClass())
# return cls()
# proc makeFrontierBlock*(): FrontierBlock =
# result.transactionClass = FrontierTransaction
# result.fields = @[("header", BlockHeader),
# ("transactions", CountableList(transactionClass)),
# ("uncles", CountableList(BlockHeader))]
# result.bloomFilter = nil

View File

@ -1,32 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat,
eth_common/eth_types,
../../../constants, ../../../errors, ../../../vm_state, ../../../transaction, ../../../utils/header
proc validateFrontierTransaction*(vmState: BaseVmState, transaction: BaseTransaction) =
let gasCost = u256(transaction.gas * transaction.gasPrice)
var senderBalance: UInt256
# inDB(vmState.stateDB(readOnly=true):
# senderBalance = db.getBalance(transaction.sender)
senderBalance = gasCost # TODO
if senderBalance < gasCost:
raise newException(ValidationError, &"Sender account balance cannot afford txn gas: {transaction.sender}")
let totalCost = transaction.value + gasCost
if senderBalance < totalCost:
raise newException(ValidationError, "Sender account balance cannot afford txn")
if vmState.blockHeader.gasUsed + transaction.gas > vmState.blockHeader.gasLimit:
raise newException(ValidationError, "Transaction exceeds gas limit")
# inDB(vmState.stateDb(readOnly=true):
# if db.getNonce(transaction.sender) != transaction.nonce:
# raise newException(ValidationError, "Invalid transaction nonce")

View File

@ -1,38 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
eth_common/eth_types,
../../../logging, ../../../constants, ../../../errors,
../../../block_types,
../../../vm/[base, stack], ../../../db/db_chain, ../../../utils/header,
./frontier_block, ./frontier_vm_state, ./frontier_validation
type
FrontierVM* = ref object of VM
method name*(vm: FrontierVM): string =
"FrontierVM"
method getBlockReward(vm: FrontierVM): UInt256 =
BLOCK_REWARD
method getUncleReward(vm: FrontierVM, blockNumber: BlockNumber, uncle: Block): UInt256 =
BLOCK_REWARD * (UNCLE_DEPTH_PENALTY_FACTOR + uncle.blockNumber - blockNumber) div UNCLE_DEPTH_PENALTY_FACTOR
method getNephewReward(vm: FrontierVM): UInt256 =
vm.getBlockReward() div 32
proc newFrontierVM*(header: BlockHeader, chainDB: BaseChainDB): FrontierVM =
new(result)
result.chainDB = chainDB
result.isStateless = true
result.state = newFrontierVMState()
result.state.chaindb = result.chainDB
result.state.blockHeader = header
result.`block` = makeFrontierBlock(header, @[])

View File

@ -1,170 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../../../logging, ../../../constants, ../../../errors, ../../../vm_state,
../../../utils/header, ../../../db/db_chain
type
FrontierVMState* = ref object of BaseVMState
# receipts*:
# computationClass*: Any
# accessLogs*: AccessLogs
proc newFrontierVMState*: FrontierVMState =
new(result)
result.prevHeaders = @[]
result.name = "FrontierVM"
result.accessLogs = newAccessLogs()
# result.blockHeader = # TODO: ...
# import
# py2nim_helpers, __future__, rlp, evm, evm.constants, evm.exceptions, evm.rlp.logs,
# evm.rlp.receipts, evm.vm.message, evm.vm_state, evm.utils.address,
# evm.utils.hexadecimal, evm.utils.keccak, evm.validation, computation, constants,
# validation
# type
# FrontierVMState* = object of Function
# prevHeaders*: seq[BlockHeader]
# receipts*: void
# computationClass*: Any
# _chaindb*: BaseChainDB
# accessLogs*: AccessLogs
# blockHeader*: BlockHeader
# proc _executeFrontierTransaction*(vmState: FrontierVMState;
# transaction: FrontierTransaction): FrontierComputation =
# transaction.validate()
# validateFrontierTransaction(vmState, transaction)
# var gasFee = transaction.gas * transaction.gasPrice
# with vmState.stateDb(),
# stateDb.deltaBalance(transaction.sender, -1 * gasFee)
# stateDb.incrementNonce(transaction.sender)
# var messageGas = transaction.gas - transaction.intrinsicGas
# if transaction.to == constants.CREATECONTRACTADDRESS:
# var
# contractAddress = generateContractAddress(transaction.sender,
# stateDb.getNonce(transaction.sender) - 1)
# data = cstring""
# code = transaction.data
# else:
# contractAddress = None
# data = transaction.data
# code = stateDb.getCode(transaction.to)
# vmState.logger.info("TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s",
# encodeHex(transaction.sender), encodeHex(transaction.to),
# transaction.value, transaction.gas, transaction.gasPrice,
# transaction.s, transaction.r, transaction.v,
# encodeHex(keccak(transaction.data)))
# var message = Message()
# if message.isCreate:
# with vmState.stateDb(),
# var isCollision = stateDb.accountHasCodeOrNonce(contractAddress)
# if isCollision:
# var computation = vmState.getComputation(message)
# computation._error = ContractCreationCollision("Address collision while creating contract: {0}".format(
# encodeHex(contractAddress)))
# vmState.logger.debug("Address collision while creating contract: %s",
# encodeHex(contractAddress))
# else:
# computation = vmState.getComputation(message).applyCreateMessage()
# else:
# computation = vmState.getComputation(message).applyMessage()
# var numDeletions = len(computation.getAccountsForDeletion())
# if numDeletions:
# computation.gasMeter.refundGas(REFUNDSELFDESTRUCT * numDeletions)
# var
# gasRemaining = computation.getGasRemaining()
# gasRefunded = computation.getGasRefund()
# gasUsed = transaction.gas - gasRemaining
# gasRefund = min(gasRefunded, gasUsed div 2)
# gasRefundAmount = gasRefund + gasRemaining * transaction.gasPrice
# if gasRefundAmount:
# vmState.logger.debug("TRANSACTION REFUND: %s -> %s", gasRefundAmount,
# encodeHex(message.sender))
# with vmState.stateDb(),
# stateDb.deltaBalance(message.sender, gasRefundAmount)
# var transactionFee = transaction.gas - gasRemaining - gasRefund *
# transaction.gasPrice
# vmState.logger.debug("TRANSACTION FEE: %s -> %s", transactionFee,
# encodeHex(vmState.coinbase))
# with vmState.stateDb(),
# stateDb.deltaBalance(vmState.coinbase, transactionFee)
# with vmState.stateDb(),
# for account, beneficiary in computation.getAccountsForDeletion():
# vmState.logger.debug("DELETING ACCOUNT: %s", encodeHex(account))
# stateDb.setBalance(account, 0)
# stateDb.deleteAccount(account)
# return computation
# proc _makeFrontierReceipt*(vmState: FrontierVMState;
# transaction: FrontierTransaction;
# computation: FrontierComputation): Receipt =
# var
# logs = ## py2nim can't generate code for
# ## Log(address, topics, data)
# gasRemaining = computation.getGasRemaining()
# gasRefund = computation.getGasRefund()
# txGasUsed = transaction.gas - gasRemaining -
# min(gasRefund, transaction.gas - gasRemaining div 2)
# gasUsed = vmState.blockHeader.gasUsed + txGasUsed
# receipt = Receipt()
# return receipt
# method executeTransaction*(self: FrontierVMState; transaction: FrontierTransaction): (
# , ) =
# var computation = _executeFrontierTransaction(self, transaction)
# return (computation, self.blockHeader)
# method makeReceipt*(self: FrontierVMState; transaction: FrontierTransaction;
# computation: FrontierComputation): Receipt =
# var receipt = _makeFrontierReceipt(self, transaction, computation)
# return receipt
# method validateBlock*(self: FrontierVMState; block: FrontierBlock): void =
# if notblock.isGenesis:
# var parentHeader = self.parentHeader
# self._validateGasLimit(block)
# validateLengthLte(block.header.extraData, 32)
# if block.header.timestamp < parentHeader.timestamp:
# raise newException(ValidationError, "`timestamp` is before the parent block\'s timestamp.\\n- block : {0}\\n- parent : {1}. ".format(
# block.header.timestamp, parentHeader.timestamp))
# elif block.header.timestamp == parentHeader.timestamp:
# raise ValidationError("`timestamp` is equal to the parent block\'s timestamp\\n- block : {0}\\n- parent: {1}. ".format(
# block.header.timestamp, parentHeader.timestamp))
# if len(block.uncles) > MAXUNCLES:
# raise newException(ValidationError, "Blocks may have a maximum of {0} uncles. Found {1}.".format(
# MAXUNCLES, len(block.uncles)))
# for uncle in block.uncles:
# self.validateUncle(block, uncle)
# if notself.isKeyExists(block.header.stateRoot):
# raise newException(ValidationError, "`state_root` was not found in the db.\\n- state_root: {0}".format(
# block.header.stateRoot))
# var localUncleHash = keccak(rlp.encode(block.uncles))
# if localUncleHash != block.header.unclesHash:
# raise newException(ValidationError, "`uncles_hash` and block `uncles` do not match.\\n - num_uncles : {0}\\n - block uncle_hash : {1}\\n - header uncle_hash: {2}".format(
# len(block.uncles), localUncleHash, block.header.uncleHash))
# method _validateGasLimit*(self: FrontierVMState; block: FrontierBlock): void =
# var gasLimit = block.header.gasLimit
# if gasLimit < GASLIMITMINIMUM:
# raise newException(ValidationError, "Gas limit {0} is below minimum {1}".format(
# gasLimit, GASLIMITMINIMUM))
# if gasLimit > GASLIMITMAXIMUM:
# raise newException(ValidationError, "Gas limit {0} is above maximum {1}".format(
# gasLimit, GASLIMITMAXIMUM))
# var
# parentGasLimit = self.parentHeader.gasLimit
# diff = gasLimit - parentGasLimit
# if diff > parentGasLimit // GASLIMITADJUSTMENTFACTOR:
# raise newException(ValidationError, "Gas limit {0} difference to parent {1} is too big {2}".format(
# gasLimit, parentGasLimit, diff))
# proc makeFrontierVMState*(): FrontierVMState =
# result.computationClass = FrontierComputation

View File

@ -1,21 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../../../logging, ../../../constants, ../../../errors, ../../../transaction,
../../../block_types,
../../../utils/header
type
TangerineBlock* = ref object of Block
transactions*: seq[BaseTransaction]
stateRoot*: cstring
proc makeTangerineBlock*(header: BlockHeader; transactions: seq[BaseTransaction]; uncles: void): TangerineBlock =
new result
if transactions.len == 0:
result.transactions = @[]

View File

@ -1,32 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat,
eth_common/eth_types,
../../../constants, ../../../errors, ../../../vm_state, ../../../transaction, ../../../utils/header
proc validateTangerineTransaction*(vmState: BaseVmState, transaction: BaseTransaction) =
let gasCost = u256(transaction.gas * transaction.gasPrice)
var senderBalance: UInt256
# inDB(vmState.stateDB(readOnly=true):
# senderBalance = db.getBalance(transaction.sender)
senderBalance = gasCost # TODO
if senderBalance < gasCost:
raise newException(ValidationError, &"Sender account balance cannot afford txn gas: {transaction.sender}")
let totalCost = transaction.value + gasCost
if senderBalance < totalCost:
raise newException(ValidationError, "Sender account balance cannot afford txn")
if vmState.blockHeader.gasUsed + transaction.gas > vmState.blockHeader.gasLimit:
raise newException(ValidationError, "Transaction exceeds gas limit")
# inDB(vmState.stateDb(readOnly=true):
# if db.getNonce(transaction.sender) != transaction.nonce:
# raise newException(ValidationError, "Invalid transaction nonce")

View File

@ -1,39 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
eth_common/eth_types,
../../../logging, ../../../constants, ../../../errors,
../../../block_types,
../../../vm/[base, stack], ../../../db/db_chain, ../../../utils/header,
./tangerine_block, ./tangerine_vm_state, ./tangerine_validation
type
TangerineVM* = ref object of VM
method name*(vm: TangerineVM): string =
"TangerineVM"
method getBlockReward(vm: TangerineVM): UInt256 =
BLOCK_REWARD
method getUncleReward(vm: TangerineVM, blockNumber: UInt256, uncle: Block): UInt256 =
BLOCK_REWARD * (UNCLE_DEPTH_PENALTY_FACTOR + uncle.blockNumber - blockNumber) div UNCLE_DEPTH_PENALTY_FACTOR
method getNephewReward(vm: TangerineVM): UInt256 =
vm.getBlockReward() div 32
proc newTangerineVM*(header: BlockHeader, chainDB: BaseChainDB): TangerineVM =
new(result)
result.chainDB = chainDB
result.isStateless = true
result.state = newTangerineVMState()
result.state.chaindb = result.chainDB
result.state.blockHeader = header
result.`block` = makeTangerineBlock(header, @[])

View File

@ -1,23 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../../../logging, ../../../constants, ../../../errors, ../../../vm_state,
../../../utils/header, ../../../db/db_chain
type
TangerineVMState* = ref object of BaseVMState
# receipts*:
# computationClass*: Any
# accessLogs*: AccessLogs
proc newTangerineVMState*: TangerineVMState =
new(result)
result.prevHeaders = @[]
result.name = "TangerineVM"
result.accessLogs = newAccessLogs()
# result.blockHeader = # TODO: ...

View File

@ -6,19 +6,19 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
./interpreter/[opcode_values, gas_meter, opcode_table],
./interpreter/[opcode_values, gas_meter],
./interpreter/vm_forks
from utils/utils_numeric import bigEndianToInt
from ./interpreter/utils/utils_numeric import bigEndianToInt
import # Used in vm_types. Beware of recursive dependencies
./code_stream, ./computation, ./stack, ./message
./code_stream, ./computation, ./stack, ./message, interpreter_dispatch
export
opcode_values, gas_meter, opcode_table,
opcode_values, gas_meter,
vm_forks
export utils_numeric.bigEndianToInt
export
code_stream, computation, stack, message
code_stream, computation, stack, message, interpreter_dispatch

View File

@ -15,3 +15,10 @@ From https://www.etherchain.org/hardForks
From https://ethereum.stackexchange.com/a/28409
![](forks_list.png)
Tangerine Whistles introduced new gas costs
Byzantium introduced opcodes:
- REVERT (EIP 140)
- RETURNDATASIZE and RETURNDATACOPY (EIP 211)
- STATICCALL (EIP 214)

View File

@ -7,8 +7,8 @@
import
math, eth_common/eth_types,
../utils/[macros_gen_opcodes, utils_numeric],
./opcode_values
./utils/[macros_gen_opcodes, utils_numeric],
./opcode_values, ./vm_forks
# Gas Fee Schedule
# Yellow Paper Appendix G - https://ethereum.github.io/yellowpaper/paper.pdf
@ -158,11 +158,10 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp
result += static(FeeSchedule[GasSha3]) +
static(FeeSchedule[GasSha3Word]) * (memLength).wordCount
func `prefix gasCopy`(value: Uint256): GasInt {.nimcall.} =
## Value is the size of the input to the CallDataCopy/CodeCopy/ReturnDataCopy function
func `prefix gasCopy`(currentMemSize, memOffset, memLength: Natural): GasInt {.nimcall.} =
result = static(FeeSchedule[GasVeryLow]) +
static(FeeSchedule[GasCopy]) * value.toInt.wordCount
static(FeeSchedule[GasCopy]) * memLength.wordCount
result += `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
func `prefix gasExtCodeCopy`(value: Uint256): GasInt {.nimcall.} =
## Value is the size of the input to the CallDataCopy/CodeCopy/ReturnDataCopy function
@ -371,14 +370,14 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp
CallValue: fixed GasBase,
CallDataLoad: fixed GasVeryLow,
CallDataSize: fixed GasBase,
CallDataCopy: dynamic `prefix gasCopy`,
CallDataCopy: memExpansion `prefix gasCopy`,
CodeSize: fixed GasBase,
CodeCopy: dynamic `prefix gasCopy`,
CodeCopy: memExpansion `prefix gasCopy`,
GasPrice: fixed GasBase,
ExtCodeSize: fixed GasExtcode,
ExtCodeCopy: dynamic `prefix gasExtCodeCopy`,
ReturnDataSize: fixed GasBase,
ReturnDataCopy: dynamic `prefix gasCopy`,
ReturnDataCopy: memExpansion `prefix gasCopy`,
# 40s: Block Information
Blockhash: fixed GasBlockhash,
@ -480,7 +479,7 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp
Log4: memExpansion `prefix gasLog4`,
# f0s: System operations
Create: fixed GasCreate,
Create: fixed GasCreate, # TODO, dynamic cost
Call: complex `prefix gasCall`,
CallCode: complex `prefix gasCall`,
Return: memExpansion `prefix gasHalt`,
@ -553,3 +552,9 @@ const
gasCosts(BaseGasFees, base, BaseGasCosts)
gasCosts(TangerineGasFees, tangerine, TangerineGasCosts)
proc forkToSchedule*(fork: Fork): GasCosts =
if fork < FkTangerine:
BaseGasCosts
else:
TangerineGasCosts

View File

@ -1,171 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
tables, eth_common/eth_types,
../../vm_types, ./opcode_values,
opcodes_impl/[arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid, call, system_ops]
const
OpLogic*: Table[Op, proc(computation: var BaseComputation){.nimcall.}] = {
# 0s: Stop and Arithmetic Operations
Stop: stop,
Add: arithmetic.add,
Mul: mul,
Sub: sub,
Div: divide,
Sdiv: sdiv,
Mod: modulo,
Smod: smod,
Addmod: arithmetic.addmod,
Mulmod: arithmetic.mulmod,
Exp: arithmetic.exp,
SignExtend: signextend,
# 10s: Comparison & Bitwise Logic Operations
Lt: lt,
Gt: gt,
Slt: slt,
Sgt: sgt,
Eq: eq,
IsZero: comparison.isZero,
And: andOp,
Or: orOp,
Xor: xorOp,
Not: notOp,
Byte: byteOp,
# 20s: SHA3
Sha3: sha3op,
# 30s: Environmental Information
Address: context.address,
Balance: balance,
Origin: context.origin,
Caller: caller,
CallValue: callValue,
CallDataLoad: callDataLoad,
CallDataSize: callDataSize,
CallDataCopy: callDataCopy,
CodeSize: codeSize,
CodeCopy: codeCopy,
GasPrice: gasPrice, # TODO this wasn't used previously
ExtCodeSize: extCodeSize,
ExtCodeCopy: extCodeCopy,
ReturnDataSize: returnDataSize, # TODO this wasn't used previously
ReturnDataCopy: returnDataCopy,
# 40s: Block Information
Blockhash: block_ops.blockhash,
Coinbase: block_ops.coinbase,
Timestamp: block_ops.timestamp,
Number: block_ops.number,
Difficulty: block_ops.difficulty,
GasLimit: block_ops.gaslimit,
# 50s: Stack, Memory, Storage and Flow Operations
Pop: stack_ops.pop,
Mload: mload,
Mstore: mstore,
Mstore8: mstore8,
Sload: sload,
Sstore: sstore,
Jump: jump,
JumpI: jumpi,
Pc: pc,
Msize: msize,
Gas: flow.gas,
JumpDest: jumpDest,
# 60s & 70s: Push Operations
Push1: push1,
Push2: push2,
Push3: push3,
Push4: push4,
Push5: push5,
Push6: push6,
Push7: push7,
Push8: push8,
Push9: push9,
Push10: push10,
Push11: push11,
Push12: push12,
Push13: push13,
Push14: push14,
Push15: push15,
Push16: push16,
Push17: push17,
Push18: push18,
Push19: push19,
Push20: push20,
Push21: push21,
Push22: push22,
Push23: push23,
Push24: push24,
Push25: push25,
Push26: push26,
Push27: push27,
Push28: push28,
Push29: push29,
Push30: push30,
Push31: push31,
Push32: push32,
# 80s: Duplication Operations
Dup1: dup1,
Dup2: dup2,
Dup3: dup3,
Dup4: dup4,
Dup5: dup5,
Dup6: dup6,
Dup7: dup7,
Dup8: dup8,
Dup9: dup9,
Dup10: dup10,
Dup11: dup11,
Dup12: dup12,
Dup13: dup13,
Dup14: dup14,
Dup15: dup15,
Dup16: dup16,
# 90s: Exchange Operations
Swap1: swap1,
Swap2: swap2,
Swap3: swap3,
Swap4: swap4,
Swap5: swap5,
Swap6: swap6,
Swap7: swap7,
Swap8: swap8,
Swap9: swap9,
Swap10: swap10,
Swap11: swap11,
Swap12: swap12,
Swap13: swap13,
Swap14: swap14,
Swap15: swap15,
Swap16: swap16,
# a0s: Logging Operations
Log0: log0,
Log1: log1,
Log2: log2,
Log3: log3,
Log4: log4,
# f0s: System operations
# Create: create,
# Call: call,
# CallCode: callCode,
Return: returnOp,
# DelegateCall: delegateCall,
# StaticCall: staticCall,
Op.Revert: revert,
Invalid: invalidOp,
SelfDestruct: selfDestruct
}.toTable

View File

@ -5,7 +5,7 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ../utils/macros_gen_opcodes
import ./utils/macros_gen_opcodes
fill_enum_holes:
type
@ -19,7 +19,7 @@ fill_enum_holes:
# because the intermediate sum (or multiplication) might roll over if
# intermediate result is greater or equal 2^256
Op* {.pure.} = enum
Op* = enum
# 0s: Stop and Arithmetic Operations
Stop = 0x00, # Halts execution.
Add = 0x01, # Addition operation.
@ -31,7 +31,7 @@ fill_enum_holes:
Smod = 0x07, # Signed modulo remainder operation.
Addmod = 0x08, # Modulo addition operation.
Mulmod = 0x09, # Modulo multiplication operation.
Exp = 0x0A, # Exponential operation
Exp = 0x0A, # Exponentiation operation
SignExtend = 0x0B, # Extend length of twos complement signed integer.
# 10s: Comparison & Bitwise Logic Operations

View File

@ -0,0 +1,784 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat, times, ranges,
stint, nimcrypto, ranges/typedranges, eth_common,
./utils/[macros_procs_opcodes, utils_numeric],
./gas_meter, ./gas_costs, ./opcode_values, ./vm_forks,
../memory, ../message, ../stack, ../code_stream, ../computation,
../../vm_state, ../../errors, ../../constants, ../../vm_types, ../../logging,
../../db/[db_chain, state_db],
../../utils/[bytes, padding, address] # TODO remove those dependencies
# ##################################
# Syntactic sugar
template push(x: typed) {.dirty.} =
## Push an expression on the computation stack
computation.stack.push x
# ##################################
# 0s: Stop and Arithmetic Operations
op add, inline = true, lhs, rhs:
## 0x01, Addition
push: lhs + rhs
op mul, inline = true, lhs, rhs:
## 0x02, Multiplication
push: lhs * rhs
op sub, inline = true, lhs, rhs:
## 0x03, Substraction
push: lhs - rhs
op divide, inline = true, lhs, rhs:
## 0x04, Division
push:
if rhs == 0: zero(Uint256) # EVM special casing of div by 0
else: lhs div rhs
op sdiv, inline = true, lhs, rhs:
## 0x05, Signed division
push:
if rhs == 0: zero(Uint256)
else:
pseudoSignedToUnsigned(
lhs.unsignedToPseudoSigned div rhs.unsignedToPseudoSigned
)
op modulo, inline = true, lhs, rhs:
## 0x06, Modulo
push:
if rhs == 0: zero(Uint256)
else: lhs mod rhs
op smod, inline = true, lhs, rhs:
## 0x07, Signed modulo
push:
if rhs == 0: zero(UInt256)
else:
pseudoSignedToUnsigned(
lhs.unsignedToPseudoSigned mod rhs.unsignedToPseudoSigned
)
op addmod, inline = true, lhs, rhs, modulus:
## 0x08, Modulo addition
## Intermediate computations do not roll over at 2^256
push:
if modulus == 0: zero(UInt256) # EVM special casing of div by 0
else: addmod(lhs, rhs, modulus)
op mulmod, inline = true, lhs, rhs, modulus:
## 0x09, Modulo multiplication
## Intermediate computations do not roll over at 2^256
push:
if modulus == 0: zero(UInt256) # EVM special casing of div by 0
else: mulmod(lhs, rhs, modulus)
op exp, inline = true, base, exponent:
## 0x0A, Exponentiation
computation.gasMeter.consumeGas(
computation.gasCosts[Exp].d_handler(exponent),
reason="EXP: exponent bytes"
)
push:
if base == 0: zero(UInt256)
else: base.pow(exponent)
op signExtend, inline = false, bits, value:
## 0x0B, Sign extend
## Extend length of twos complement signed integer.
var res: UInt256
if bits <= 31.u256:
let
testBit = bits.toInt * 8 + 7
bitPos = (1 shl testBit)
mask = u256(bitPos - 1)
if not isZero(value and bitPos.u256):
res = value or (not mask)
else:
res = value and mask
else:
res = value
push: res
# ##########################################
# 10s: Comparison & Bitwise Logic Operations
op lt, inline = true, lhs, rhs:
## 0x10, Less-than comparison
push: (lhs < rhs).uint.u256
op gt, inline = true, lhs, rhs:
## 0x11, Greater-than comparison
push: (lhs > rhs).uint.u256
op slt, inline = true, lhs, rhs:
## 0x12, Signed less-than comparison
push: (cast[Int256](lhs) < cast[Int256](rhs)).uint.u256
op sgt, inline = true, lhs, rhs:
## 0x13, Signed greater-than comparison
push: (cast[Int256](lhs) > cast[Int256](rhs)).uint.u256
op eq, inline = true, lhs, rhs:
## 0x14, Signed greater-than comparison
push: (lhs == rhs).uint.u256
op isZero, inline = true, value:
## 0x15, Check if zero
push: value.isZero.uint.u256
op andOp, inline = true, lhs, rhs:
## 0x16, Bitwise AND
push: lhs and rhs
op orOp, inline = true, lhs, rhs:
## 0x17, Bitwise AND
push: lhs or rhs
op xorOp, inline = true, lhs, rhs:
## 0x18, Bitwise AND
push: lhs xor rhs
op notOp, inline = true, value:
## 0x19, Check if zero
push: value.not
op byteOp, inline = true, position, value:
## 0x20, Retrieve single byte from word.
let pos = position.toInt
push:
if pos >= 32 or pos < 0: zero(Uint256)
else:
when system.cpuEndian == bigEndian:
cast[array[32, byte]](value)[pos].u256
else:
cast[array[32, byte]](value)[31 - pos].u256
# ##########################################
# 20s: SHA3
op sha3, inline = true, startPos, length:
## 0x20, Compute Keccak-256 hash.
let (pos, len) = (startPos.toInt, length.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[Op.Sha3].m_handler(computation.memory.len, pos, len),
reason="SHA3: word gas cost"
)
computation.memory.extend(pos, len)
let endRange = min(pos + len, computation.memory.len - 1)
push:
keccak256.digest computation.memory.bytes.toOpenArray(pos, endRange)
# ##########################################
# 30s: Environmental Information
# TODO - simplify: https://github.com/status-im/nimbus/issues/67
proc writePaddedResult(mem: var Memory,
data: openarray[byte],
memPos, dataPos, len: Natural,
paddingValue = 0.byte) =
mem.extend(memPos, len)
let dataEndPosition = dataPos + len - 1
if dataEndPosition < data.len:
mem.write(memPos, data[dataPos .. dataEndPosition])
else:
var presentElements = data.len - dataPos
if presentElements > 0:
mem.write(memPos, data.toOpenArray(dataPos, data.len - 1))
else:
presentElements = 0
# Note, we don't need to write padding bytes
# mem.extend already pads with zero properly
op address, inline = true:
## 0x30, Get address of currently executing account.
push: computation.msg.storageAddress
op balance, inline = true:
## 0x31, Get balance of the given account.
let address = computation.stack.popAddress()
push: computation.vmState.readOnlyStateDB.getBalance(address)
op origin, inline = true:
## 0x32, Get execution origination address.
push: computation.msg.origin
op caller, inline = true:
## 0x33, Get caller address.
push: computation.msg.sender
op callValue, inline = true:
## 0x34, Get deposited value by the instruction/transaction
## responsible for this execution
push: computation.msg.value
op callDataLoad, inline = false, startPos:
## 0x35, Get input data of current environment
# TODO simplification: https://github.com/status-im/nimbus/issues/67
let dataPos = startPos.toInt
if dataPos >= computation.msg.data.len:
push: 0
return
let dataEndPosition = dataPos + 31
if dataEndPosition < computation.msg.data.len:
computation.stack.push(computation.msg.data[dataPos .. dataEndPosition])
else:
var bytes: array[32, byte]
var presentBytes = min(computation.msg.data.len - dataPos, 32)
if presentBytes > 0:
copyMem(addr bytes[0], addr computation.msg.data[dataPos], presentBytes)
else:
presentBytes = 0
for i in presentBytes ..< 32: bytes[i] = 0
computation.stack.push(bytes)
op callDataSize, inline = true:
## 0x36, Get size of input data in current environment.
push: computation.msg.data.len.u256
op callDataCopy, inline = false, memStartPos, copyStartPos, size:
## 0x37, Copy input data in current environment to memory.
# TODO tests: https://github.com/status-im/nimbus/issues/67
let (memPos, copyPos, len) = (memStartPos.toInt, copyStartPos.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[CallDataCopy].m_handler(memPos, copyPos, len),
reason="CallDataCopy fee")
computation.memory.extend(memPos, len)
# If the data does not take 32 bytes, pad with zeros
let lim = min(computation.msg.data.len, copyPos + len)
let padding = copyPos + len - lim
# Note: when extending, extended memory is zero-ed, we only need to offset with padding value
# Also memory.write handles the case where copyPos+padding is out of bounds
computation.memory.write(memPos):
computation.msg.data.toOpenArray(copyPos+padding, copyPos+lim)
op codesize, inline = true:
## 0x38, Get size of code running in current environment.
push: computation.code.len
op codecopy, inline = false, memStartPos, copyStartPos, size:
## 0x39, Copy code running in current environment to memory.
# TODO tests: https://github.com/status-im/nimbus/issues/67
let (memPos, copyPos, len) = (memStartPos.toInt, copyStartPos.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[CodeCopy].m_handler(memPos, copyPos, len),
reason="CodeCopy fee")
computation.memory.writePaddedResult(computation.code.bytes, memPos, copyPos, len)
op gasprice, inline = true:
## 0x3A, Get price of gas in current environment.
push: computation.msg.gasPrice
op extCodeSize, inline = true:
## 0x3b, Get size of an account's code
let account = computation.stack.popAddress()
let codeSize = computation.vmState.readOnlyStateDB.getCode(account).len
push uint(codeSize)
op extCodeCopy, inline = true:
## 0x3c, Copy an account's code to memory.
let account = computation.stack.popAddress()
let (memStartPos, codeStartPos, size) = computation.stack.popInt(3)
let (memPos, codePos, len) = (memStartPos.toInt, codeStartPos.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[CodeCopy].m_handler(memPos, codePos, len),
reason="ExtCodeCopy fee")
let codeBytes = computation.vmState.readOnlyStateDB.getCode(account)
computation.memory.writePaddedResult(codeBytes.toOpenArray, memPos, codePos, len)
op returnDataSize, inline = true:
## 0x3d, Get size of output data from the previous call from the current environment.
push: computation.returnData.len
op returnDataCopy, inline = false, memStartPos, copyStartPos, size:
## 0x3e, Copy output data from the previous call to memory.
let (memPos, copyPos, len) = (memStartPos.toInt, copyStartPos.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[CodeCopy].m_handler(memPos, copyPos, len),
reason="ExtCodeCopy fee")
if copyPos + len > computation.returnData.len:
# TODO Geth additionally checks copyPos + len < 64
# Parity uses a saturating addition
# Yellow paper mentions μs[1] + i are not subject to the 2^256 modulo.
raise newException(OutOfBoundsRead,
"Return data length is not sufficient to satisfy request. Asked \n" &
&"for data from index {copyStartPos} to {copyStartPos + size}. Return data is {computation.returnData.len} in \n" &
"length")
computation.memory.extend(memPos, len)
computation.memory.write(memPos):
computation.returnData.toOpenArray(copyPos, copyPos+len)
# ##########################################
# 40s: Block Information
op blockhash, inline = true, blockNumber:
## 0x40, Get the hash of one of the 256 most recent complete blocks.
push: computation.vmState.getAncestorHash(blockNumber)
op coinbase, inline = true:
## 0x41, Get the block's beneficiary address.
push: computation.vmState.coinbase
op timestamp, inline = true:
## 0x42, Get the block's timestamp.
push: computation.vmState.timestamp.toUnix
op blocknumber, inline = true:
## 0x43, Get the block's number.
push: computation.vmState.blockNumber
op difficulty, inline = true:
## 0x44, Get the block's difficulty
push: computation.vmState.difficulty
op gasLimit, inline = true:
## 0x45, Get the block's gas limit
push: computation.vmState.gasLimit
# ##########################################
# 50s: Stack, Memory, Storage and Flow Operations
op pop, inline = true:
## 0x50, Remove item from stack.
discard computation.stack.popInt()
op mload, inline = true, memStartPos:
## 0x51, Load word from memory
let memPos = memStartPos.toInt
computation.gasMeter.consumeGas(
computation.gasCosts[MLoad].m_handler(computation.memory.len, memPos, 32),
reason="MLOAD: GasVeryLow + memory expansion"
)
computation.memory.extend(memPos, 32)
push: computation.memory.read(memPos, 32) # TODO, should we convert to native endianness?
op mstore, inline = true, memStartPos, value:
## 0x52, Save word to memory
let memPos = memStartPos.toInt
computation.gasMeter.consumeGas(
computation.gasCosts[MStore].m_handler(computation.memory.len, memPos, 32),
reason="MSTORE: GasVeryLow + memory expansion"
)
computation.memory.extend(memPos, 32)
computation.memory.write(memPos, value.toByteArrayBE) # is big-endian correct? Parity/Geth do convert
op mstore8, inline = true, memStartPos, value:
## 0x53, Save byte to memory
let memPos = memStartPos.toInt
computation.gasMeter.consumeGas(
computation.gasCosts[MStore].m_handler(computation.memory.len, memPos, 1),
reason="MSTORE8: GasVeryLow + memory expansion"
)
computation.memory.extend(memPos, 1)
computation.memory.write(memPos, [value.toByteArrayBE[0]])
op sload, inline = true, slot:
## 0x54, Load word from storage.
let (value, found) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot)
if found:
push: value
else:
# TODO: raise exception?
discard
op sstore, inline = false, slot, value:
## 0x55, Save word to storage.
let (currentValue, existing) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot)
let
gasParam = GasParams(kind: Op.Sstore, s_isStorageEmpty: not existing)
(gasCost, gasRefund) = computation.gasCosts[Sstore].c_handler(currentValue, gasParam)
computation.gasMeter.consumeGas(gasCost, &"SSTORE: {computation.msg.storageAddress}[{slot}] -> {value} ({currentValue})")
if gasRefund > 0:
computation.gasMeter.refundGas(gasRefund)
computation.vmState.mutateStateDB:
db.setStorage(computation.msg.storageAddress, slot, value)
op jump, inline = true, jumpTarget:
## 0x56, Alter the program counter
let jt = jumpTarget.toInt
computation.code.pc = jt
let nextOpcode = computation.code.peek
if nextOpcode != JUMPDEST:
raise newException(InvalidJumpDestination, "Invalid Jump Destination")
# TODO: next check seems redundant
if not computation.code.isValidOpcode(jt):
raise newException(InvalidInstruction, "Jump resulted in invalid instruction")
# TODO: what happens if there is an error, rollback?
op jumpI, inline = true, jumpTarget, testedValue:
## 0x57, Conditionally alter the program counter.
if testedValue != 0:
let jt = jumpTarget.toInt
computation.code.pc = jt
let nextOpcode = computation.code.peek
if nextOpcode != JUMPDEST:
raise newException(InvalidJumpDestination, "Invalid Jump Destination")
# TODO: next check seems redundant
if not computation.code.isValidOpcode(jt):
raise newException(InvalidInstruction, "Jump resulted in invalid instruction")
op pc, inline = true:
## 0x58, Get the value of the program counter prior to the increment corresponding to this instruction.
push: max(computation.code.pc - 1, 0)
op msize, inline = true:
## 0x59, Get the size of active memory in bytes.
push: computation.memory.len
op gas, inline = true:
## 0x5a, Get the amount of available gas, including the corresponding reduction for the cost of this instruction.
push: computation.gasMeter.gasRemaining
op jumpDest, inline = true:
## 0x5b, Mark a valid destination for jumps. This operation has no effect on machine state during execution.
discard
# ##########################################
# 60s & 70s: Push Operations.
# 80s: Duplication Operations
# 90s: Exchange Operations
# a0s: Logging Operations
genPush()
genDup()
genSwap()
genLog()
# ##########################################
# f0s: System operations.
op create, inline = false, value, startPosition, size:
## 0xf0, Create a new account with associated code.
# TODO: Forked create for Homestead
let (memPos, len) = (startPosition.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[CodeCopy].m_handler(computation.memory.len, memPos, len),
reason="Create fee")
computation.memory.extend(memPos, len)
##### getBalance type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: <unknown>.}
# computation.vmState.db(readOnly=true):
# when ForkName >= FkHomestead: # TODO this is done in Geth but not Parity and Py-EVM
# let insufficientFunds = db.getBalance(computation.msg.storageAddress) < value # TODO check gas balance rollover
# let stackTooDeep = computation.msg.depth >= MaxCallDepth
# # TODO: error message
# if insufficientFunds or stackTooDeep:
# push: 0
# return
# else:
# let stackTooDeep = computation.msg.depth >= MaxCallDepth
# if stackTooDeep:
# push: 0
# return
let callData = computation.memory.read(memPos, len)
## TODO dynamic gas that depends on remaining gas
##### getNonce type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: <unknown>.}
# computation.vmState.db(readOnly=true):
# let creationNonce = db.getNonce(computation.msg.storageAddress)
# db.incrementNonce(computation.msg.storageAddress)
let contractAddress = ZERO_ADDRESS # generateContractAddress(computation.msg.storageAddress, creationNonce)
let isCollision = false # TODO: db.accountHasCodeOrNonce ...
if isCollision:
computation.vmState.logger.debug("Address collision while creating contract: " & contractAddress.toHex)
push: 0
return
let childMsg = prepareChildMessage(
computation,
gas = 0, # TODO refactor gas
to = CREATE_CONTRACT_ADDRESS,
value = value,
data = @[],
code = callData.toString,
options = MessageOptions(createAddress: contractAddress)
)
# let childComputation = applyChildBaseComputation(computation, childMsg)
var childComputation: BaseComputation # TODO - stub
new childComputation
childComputation.gasMeter = newGasMeter(0) # TODO GasMeter should be a normal object.
if childComputation.isError:
push: 0
else:
push: contractAddress
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
proc callParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt()
let codeAddress = computation.stack.popAddress()
let (value,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5)
let to = computation.msg.storageAddress
let sender = computation.msg.storageAddress
result = (gas,
value,
to,
sender,
codeAddress,
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
true, # should_transfer_value,
computation.msg.isStatic)
proc callCodeParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt()
let to = computation.stack.popAddress()
let (value,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5)
result = (gas,
value,
to,
ZERO_ADDRESS, # sender
ZERO_ADDRESS, # code_address
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
true, # should_transfer_value,
computation.msg.isStatic)
proc delegateCallParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt()
let codeAddress = computation.stack.popAddress()
let (memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4)
let to = computation.msg.storageAddress
let sender = computation.msg.storageAddress
let value = computation.msg.value
result = (gas,
value,
to,
sender,
codeAddress,
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
false, # should_transfer_value,
computation.msg.isStatic)
proc staticCallParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt()
let to = computation.stack.popAddress()
let (memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4)
result = (gas,
0.u256, # value
to,
ZERO_ADDRESS, # sender
ZERO_ADDRESS, # codeAddress
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
false, # should_transfer_value,
true) # is_static
template genCall(callName: untyped): untyped =
op callName, inline = false:
## CALL, 0xf1, Message-Call into an account
## CALLCODE, 0xf2, Message-call into this account with an alternative account's code.
## DELEGATECALL, 0xf4, Message-call into this account with an alternative account's code, but persisting the current values for sender and value.
## STATICCALL, 0xfa, Static message-call into an account.
# TODO: forked calls for Homestead
let (gas, value, to, sender,
codeAddress,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize,
shouldTransferValue,
isStatic) = `callName Params`(computation)
let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.toInt, memoryInputSize.toInt, memoryOutputStartPosition.toInt, memoryOutputSize.toInt)
let (gasCost, childMsgGas) = computation.gasCosts[Op.Call].c_handler(
value,
GasParams(kind: Call,
c_isNewAccount: true, # TODO stub
c_gasBalance: 0,
c_contractGas: 0,
c_currentMemSize: computation.memory.len,
c_memOffset: 0, # TODO make sure if we pass the largest mem requested
c_memLength: 0 # or an addition of mem requested
))
computation.memory.extend(memInPos, memInLen)
computation.memory.extend(memOutPos, memOutLen)
let callData = computation.memory.read(memInPos, memInLen)
##### getBalance type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: <unknown>.}
# computation.vmState.db(readOnly = true):
# let senderBalance = db.getBalance(computation.msg.storageAddress) # TODO check gas balance rollover
let insufficientFunds = false # shouldTransferValue and senderBalance < value
let stackTooDeep = computation.msg.depth >= MaxCallDepth
if insufficientFunds or stackTooDeep:
computation.returnData = @[]
var errMessage: string
if insufficientFunds:
let senderBalance = -1 # TODO workaround
# Note: for some reason we can't use strformat here, we get undeclared identifiers
errMessage = &"Insufficient Funds: have: " & $senderBalance & "need: " & $value
elif stackTooDeep:
errMessage = "Stack Limit Reached"
else:
raise newException(VMError, "Invariant: Unreachable code path")
# computation.logger.debug(&"failure: {errMessage}") # TODO: Error: expression 'logger' has no type (or is ambiguous)
computation.gasMeter.returnGas(childMsgGas)
push: 0
return
##### getCode type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: <unknown>.}
# computation.vmState.db(readOnly = true):
# let code = if codeAddress != ZERO_ADDRESS: db.getCode(codeAddress)
# else: db.getCode(to)
let code = "0x" # This is a stub hack, newCodeStreamFromUnescaped expects length 2 at least
var childMsg = prepareChildMessage(
computation,
childMsgGas,
to,
value,
callData,
code,
MessageOptions(
shouldTransferValue: shouldTransferValue,
isStatic: isStatic)
)
if sender != ZERO_ADDRESS:
childMsg.sender = sender
# let childComputation = applyChildBaseComputation(computation, childMsg)
var childComputation: BaseComputation # TODO - stub
new childComputation
childComputation.gasMeter = newGasMeter(0) # TODO GasMeter should be a normal object.
if childComputation.isError:
push: 0
else:
push: 1
if not childComputation.shouldEraseReturnData:
let actualOutputSize = min(memOutLen, childComputation.output.len)
computation.memory.write(
memOutPos,
childComputation.output.toBytes[0 ..< actualOutputSize])
if not childComputation.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
genCall(call)
genCall(callCode)
genCall(delegateCall)
genCall(staticCall)
op returnOp, inline = false, startPos, size:
## 0xf3, Halt execution returning output data.
let (pos, len) = (startPos.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[Return].m_handler(computation.memory.len, pos, len),
reason = "RETURN"
)
computation.memory.extend(pos, len)
let output = computation.memory.read(pos, len)
computation.output = output.toString
op revert, inline = false, startPos, size:
## 0xf0, Halt execution reverting state changes but returning data and remaining gas.
let (pos, len) = (startPos.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[Revert].m_handler(computation.memory.len, pos, len),
reason = "REVERT"
)
computation.memory.extend(pos, len)
let output = computation.memory.read(pos, len).toString
computation.output = output
op selfDestruct, inline = false:
## 0xff Halt execution and register account for later deletion.
let beneficiary = computation.stack.popAddress()
## TODO
computation.registerAccountForDeletion(beneficiary)

View File

@ -1,117 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strutils,
eth_common/eth_types,
helpers, ./impl_std_import
proc add*(computation: var BaseComputation) =
# Addition
let (left, right) = computation.stack.popInt(2)
let res = left + right
pushRes()
proc addmod*(computation: var BaseComputation) =
# Modulo Addition
let (left, right, modulus) = computation.stack.popInt(3)
let res = if modulus.isZero: zero(Uint256) # EVM special casing of div by 0
else: addmod(left, right, modulus)
pushRes()
proc sub*(computation: var BaseComputation) =
# Subtraction
let (left, right) = computation.stack.popInt(2)
let res = left - right
pushRes()
proc modulo*(computation: var BaseComputation) =
# Modulo
let (value, modulus) = computation.stack.popInt(2)
let res = if modulus.isZero: zero(Uint256) # EVM special casing of div by 0
else: value mod modulus
pushRes()
proc smod*(computation: var BaseComputation) =
# Signed Modulo
let (value, modulus) = computation.stack.popInt(2)
let res = if modulus.isZero: zero(Uint256)
else: pseudoSignedToUnsigned(
unsignedToPseudoSigned(value) mod unsignedToPseudoSigned(modulus)
)
pushRes()
proc mul*(computation: var BaseComputation) =
# Multiplication
let (left, right) = computation.stack.popInt(2)
let res = left * right
pushRes()
proc mulmod*(computation: var BaseComputation) =
# Modulo Multiplication
let (left, right, modulus) = computation.stack.popInt(3)
let res = if modulus.isZero: zero(Uint256)
else: mulmod(left, right, modulus)
pushRes()
proc divide*(computation: var BaseComputation) =
# Division
let (numerator, denominator) = computation.stack.popInt(2)
let res = if denominator.isZero: zero(Uint256)
else: numerator div denominator
pushRes()
proc sdiv*(computation: var BaseComputation) =
# Signed Division
let (value, divisor) = computation.stack.popInt(2)
let res = if divisor.isZero: zero(Uint256)
else: pseudoSignedToUnsigned(
unsignedToPseudoSigned(value) div unsignedToPseudoSigned(divisor)
)
pushRes()
# no curry
proc exp*(computation: var BaseComputation) =
# Exponentiation
let (base, exponent) = computation.stack.popInt(2)
computation.gasMeter.consumeGas(
computation.gasCosts[Exp].d_handler(exponent),
reason="EXP: exponent bytes"
)
let res = if base.isZero: 0.u256 # 0^0 is 0 in py-evm
else: base.pow(exponent)
pushRes()
proc signextend*(computation: var BaseComputation) =
# Signed Extend
let (bits, value) = computation.stack.popInt(2)
var res: UInt256
if bits <= 31.u256:
let testBit = bits.toInt * 8 + 7
let bitPos = (1 shl testBit)
let mask = u256(bitPos - 1)
if not (value and bitPos).isZero:
res = value or (not mask)
else:
res = value and mask
else:
res = value
pushRes()

View File

@ -1,27 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
eth_common/eth_types,
../constants, ../computation, ../vm/stack, ../vm_state
proc blockhash*(computation: var BaseComputation) =
var blockNumber = computation.stack.popInt()
var blockHash = computation.vmState.getAncestorHash(blockNumber)
computation.stack.push(blockHash)
proc coinbase*(computation: var BaseComputation) =
computation.stack.push(computation.vmState.coinbase)
proc timestamp*(computation: var BaseComputation) =
computation.stack.push(computation.vmState.timestamp.int256)
proc difficulty*(computation: var BaseComputation) =
computation.stack.push(computation.vmState.difficulty)
proc gaslimit*(computation: var BaseComputation) =
computation.stack.push(computation.vmState.gasLimit)

View File

@ -1,37 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
times, eth_common/eth_types, ./impl_std_import
{.this: computation.}
{.experimental.}
using
computation: var BaseComputation
proc blockhash*(computation) =
let blockNumber = vmWordToBlockNumber stack.popInt()
let blockHash = vmState.getAncestorHash(blockNumber)
stack.push(blockHash)
proc coinbase*(computation) =
stack.push(vmState.coinbase)
proc timestamp*(computation) =
# TODO: EthTime is an alias of Time, which is a distinct int64 so can't use u256(int64)
# This may have implications for different platforms.
stack.push(vmState.timestamp.toUnix.uint64.u256)
proc number*(computation) =
stack.push(blockNumberToVmWord vmState.blockNumber)
proc difficulty*(computation) =
stack.push(vmState.difficulty)
proc gaslimit*(computation) =
stack.push(vmState.gasLimit.u256)

View File

@ -1,213 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat, eth_common,
# ./impl_std_import # Cannot do that due to recursive dependencies
# .../vm/interpreter/opcodes_impl/impl_std_import.nim imports .../vm/computation.nim
# .../vm/computation.nim imports .../vm/interpreter/opcodes_impl/call.nim
# .../vm/interpreter/opcodes_impl/call.nim imports .../vm/interpreter/opcodes_impl/impl_std_import.nim
../../../constants, ../../../vm_types, ../../../errors, ../../../logging,
../../../utils/bytes,
../../computation, ../../stack, ../../memory, ../../message,
../opcode_values, ../gas_meter, ../gas_costs
type
# TODO most of these are for gas handling
BaseCall* = ref object of Opcode
Call* = ref object of BaseCall
CallCode* = ref object of BaseCall
DelegateCall* = ref object of BaseCall
CallEIP150* = ref object of Call
CallCodeEIP150* = ref object of CallCode
DelegateCallEIP150* = ref object of DelegateCall
CallEIP161* = ref object of CallEIP150 # TODO: Refactoring - put that in VM forks
# Byzantium
StaticCall* = ref object of CallEIP161 # TODO: Refactoring - put that in VM forks
CallByzantium* = ref object of CallEIP161 # TODO: Refactoring - put that in VM forks
using
computation: var BaseComputation
method callParams*(call: BaseCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) {.base.} =
raise newException(NotImplementedError, "Must be implemented subclasses")
method runLogic*(call: BaseCall, computation) =
let (gas, value, to, sender,
codeAddress,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize,
shouldTransferValue,
isStatic) = call.callParams(computation)
let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.toInt, memoryInputSize.toInt, memoryOutputStartPosition.toInt, memoryOutputSize.toInt)
let (gasCost, childMsgGas) = computation.gasCosts[Op.Call].c_handler(
value,
GasParams() # TODO - stub
)
computation.memory.extend(memInPos, memInLen)
computation.memory.extend(memOutPos, memOutLen)
let callData = computation.memory.read(memInPos, memInLen)
# TODO: Pre-call checks
# with computation.vm_state.state_db(read_only=True) as state_db:
# sender_balance = state_db.get_balance(computation.msg.storage_address)
let senderBalance = 0.u256
let insufficientFunds = shouldTransferValue and senderBalance < value
let stackTooDeep = computation.msg.depth + 1 > STACK_DEPTH_LIMIT
if insufficientFunds or stackTooDeep:
computation.returnData = ""
var errMessage: string
if insufficientFunds:
errMessage = &"Insufficient Funds: have: {senderBalance} | need: {value}"
elif stackTooDeep:
errMessage = "Stack Limit Reached"
else:
raise newException(VMError, "Invariant: Unreachable code path")
computation.logger.debug(&"{call.kind} failure: {errMessage}")
computation.gasMeter.returnGas(childMsgGas)
computation.stack.push(0.u256)
else:
# TODO: with
# with computation.vm_state.state_db(read_only=True) as state_db:
# if code_address:
# code = state_db.get_code(code_address)
# else:
# code = state_db.get_code(to)
let code = ""
let childMsg = computation.prepareChildMessage(
childMsgGas,
to,
value,
callData,
code,
MessageOptions(
shouldTransferValue: shouldTransferValue,
isStatic: isStatic))
if sender != ZERO_ADDRESS:
childMsg.sender = sender
# let childComputation = computation.applyChildComputation(childMsg)
# TODO
var childComputation: BaseComputation
if childComputation.isError:
computation.stack.push(0.u256)
else:
computation.stack.push(1.u256)
if not childComputation.shouldEraseReturnData:
let actualOutputSize = min(memOutLen, childComputation.output.len)
computation.memory.write(
memOutPos,
childComputation.output.toBytes[0 ..< actualOutputSize])
if not childComputation.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
method callParams(call: CallCode, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt()
let to = computation.stack.popAddress()
let (value,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5)
result = (gas,
value,
to,
ZERO_ADDRESS, # sender
ZERO_ADDRESS, # code_address
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
true, # should_transfer_value,
computation.msg.isStatic)
method callParams(call: Call, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt()
let codeAddress = computation.stack.popAddress()
let (value,
memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5)
let to = computation.msg.storageAddress
let sender = computation.msg.storageAddress
result = (gas,
value,
to,
sender,
codeAddress,
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
true, # should_transfer_value,
computation.msg.isStatic)
method callParams(call: DelegateCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt()
let codeAddress = computation.stack.popAddress()
let (memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4)
let to = computation.msg.storageAddress
let sender = computation.msg.storageAddress
let value = computation.msg.value
result = (gas,
value,
to,
sender,
codeAddress,
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
false, # should_transfer_value,
computation.msg.isStatic)
method callParams(call: StaticCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt()
let to = computation.stack.popAddress()
let (memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4)
result = (gas,
0.u256, # value
to,
ZERO_ADDRESS, # sender
ZERO_ADDRESS, # codeAddress
memoryInputStartPosition,
memoryInputSize,
memoryOutputStartPosition,
memoryOutputSize,
false, # should_transfer_value,
true) # is_static
method callParams(call: CallByzantium, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
result = procCall callParams(call, computation)
if computation.msg.isStatic and result[1] != 0:
raise newException(WriteProtection, "Cannot modify state while inside of a STATICCALL context")

View File

@ -1,54 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
helpers, ./impl_std_import
quasiBoolean(lt, `<`) # Lesser Comparison
quasiBoolean(gt, `>`) # Greater Comparison
quasiBoolean(slt, `<`, signed=true) # Signed Lesser Comparison
quasiBoolean(sgt, `>`, signed=true) # Signed Greater Comparison
quasiBoolean(eq, `==`) # Equality
proc andOp*(computation: var BaseComputation) =
let (lhs, rhs) = computation.stack.popInt(2)
computation.stack.push(lhs and rhs)
proc orOp*(computation: var BaseComputation) =
let (lhs, rhs) = computation.stack.popInt(2)
computation.stack.push(lhs or rhs)
proc xorOp*(computation: var BaseComputation) =
let (lhs, rhs) = computation.stack.popInt(2)
computation.stack.push(lhs xor rhs)
# TODO use isZero from Stint
proc iszero*(computation: var BaseComputation) =
var value = computation.stack.popInt()
var res = if value == 0: 1.u256 else: 0.u256
pushRes()
proc notOp*(computation: var BaseComputation) =
var value = computation.stack.popInt()
var res = UINT_256_MAX - value
pushRes()
# TODO: seems like there is an implementation or a comment issue
# this is not a bitwise "and" or the "byte" instruction
proc byteOp*(computation: var BaseComputation) =
# Bitwise And
var (position, value) = computation.stack.popInt(2)
var res = if position >= 32.u256: 0.u256 else: (value div (256.u256.pow(31'u64 - position.toInt.uint64))) mod 256
pushRes()

View File

@ -1,155 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat,
ranges/typedranges,
./impl_std_import,
../../../db/state_db
proc balance*(computation: var BaseComputation) =
let address = computation.stack.popAddress()
let balance = computation.vmState.readOnlyStateDB.getBalance(address)
computation.stack.push balance
proc origin*(computation: var BaseComputation) =
computation.stack.push(computation.msg.origin)
proc address*(computation: var BaseComputation) =
computation.stack.push(computation.msg.storageAddress)
proc caller*(computation: var BaseComputation) =
computation.stack.push(computation.msg.sender)
proc callValue*(computation: var BaseComputation) =
computation.stack.push(computation.msg.value)
proc writePaddedResult(mem: var Memory,
data: openarray[byte],
memPos, dataPos, len: Natural,
paddingValue = 0.byte) =
mem.extend(memPos, len)
let dataEndPosition = dataPos + len - 1
if dataEndPosition < data.len:
mem.write(memPos, data[dataPos .. dataEndPosition])
else:
var presentElements = data.len - dataPos
if presentElements > 0:
mem.write(memPos, data.toOpenArray(dataPos, data.len - 1))
else:
presentElements = 0
mem.writePaddingBytes(memPos + presentElements,
len - presentElements,
paddingValue)
proc callDataLoad*(computation: var BaseComputation) =
# Load call data into memory
let origDataPos = computation.stack.popInt
if origDataPos >= computation.msg.data.len:
computation.stack.push(0)
return
let
dataPos = origDataPos.toInt
dataEndPosition = dataPos + 31
if dataEndPosition < computation.msg.data.len:
computation.stack.push(computation.msg.data[dataPos .. dataEndPosition])
else:
var bytes: array[32, byte]
var presentBytes = min(computation.msg.data.len - dataPos, 32)
if presentBytes > 0:
copyMem(addr bytes[0], addr computation.msg.data[dataPos], presentBytes)
else:
presentBytes = 0
for i in presentBytes ..< 32: bytes[i] = 0
computation.stack.push(bytes)
proc callDataSize*(computation: var BaseComputation) =
let size = computation.msg.data.len.u256
computation.stack.push(size)
proc callDataCopy*(computation: var BaseComputation) =
let (memStartPosition,
calldataStartPosition,
size) = computation.stack.popInt(3)
computation.gasMeter.consumeGas(
computation.gasCosts[CallDataCopy].d_handler(size),
reason="CALLDATACOPY fee")
let (memPos, callPos, len) = (memStartPosition.toInt, calldataStartPosition.toInt, size.toInt)
computation.memory.writePaddedResult(computation.msg.data,
memPos, callPos, len)
proc codeSize*(computation: var BaseComputation) =
let size = computation.code.len.u256
computation.stack.push(size)
proc codeCopy*(computation: var BaseComputation) =
let (memStartPosition,
codeStartPosition,
size) = computation.stack.popInt(3)
computation.gasMeter.consumeGas(
computation.gasCosts[CodeCopy].d_handler(size),
reason="CODECOPY: word gas cost")
let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)
computation.memory.writePaddedResult(computation.code.bytes, memPos, codePos, len)
proc gasPrice*(computation: var BaseComputation) =
computation.stack.push(computation.msg.gasPrice.u256)
proc extCodeSize*(computation: var BaseComputation) =
let account = computation.stack.popAddress()
let codeSize = computation.vmState.readOnlyStateDB.getCode(account).len
computation.stack.push uint(codeSize)
proc extCodeCopy*(computation: var BaseComputation) =
let account = computation.stack.popAddress()
let (memStartPosition, codeStartPosition, size) = computation.stack.popInt(3)
computation.gasMeter.consumeGas(
computation.gasCosts[ExtCodeCopy].d_handler(size),
reason="EXTCODECOPY: word gas cost"
)
let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)
let codeBytes = computation.vmState.readOnlyStateDB.getCode(account)
computation.memory.writePaddedResult(codeBytes.toOpenArray, memPos, codePos, len)
proc returnDataSize*(computation: var BaseComputation) =
let size = computation.returnData.len.u256
computation.stack.push(size)
proc returnDataCopy*(computation: var BaseComputation) =
let (memStartPosition, returnDataStartPosition, size) = computation.stack.popInt(3)
computation.gasMeter.consumeGas(
computation.gasCosts[ReturnDataCopy].d_handler(size),
reason="RETURNDATACOPY fee"
)
let (memPos, returnPos, len) = (memStartPosition.toInt, returnDataStartPosition.toInt, size.toInt)
if returnPos + len > computation.returnData.len:
raise newException(OutOfBoundsRead,
"Return data length is not sufficient to satisfy request. Asked \n" &
&"for data from index {returnDataStartPosition} to {returnDataStartPosition + size}. Return data is {computation.returnData.len} in \n" &
"length")
computation.memory.extend(memPos, len)
let value = ($computation.returnData)[returnPos ..< returnPos + len]
computation.memory.write(memPos, len, value)

View File

@ -1,33 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
macros, strformat, ./impl_std_import
macro dupXX(position: static[int]): untyped =
let name = ident(&"dup{position}")
result = quote:
proc `name`*(computation: var BaseComputation) =
computation.stack.dup(`position`)
dupXX(1)
dupXX(2)
dupXX(3)
dupXX(4)
dupXX(5)
dupXX(6)
dupXX(7)
dupXX(8)
dupXX(9)
dupXX(10)
dupXX(11)
dupXX(12)
dupXX(13)
dupXX(14)
dupXX(15)
dupXX(16)

View File

@ -1,56 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat, ./impl_std_import
{.this: computation.}
{.experimental.}
using
computation: var BaseComputation
proc stop*(computation) =
raise newException(Halt, "STOP")
proc jump*(computation) =
let jumpDest = stack.popInt.toInt
code.pc = jumpDest
let nextOpcode = code.peek()
if nextOpcode != JUMPDEST:
raise newException(InvalidJumpDestination, "Invalid Jump Destination")
if not code.isValidOpcode(jumpDest):
raise newException(InvalidInstruction, "Jump resulted in invalid instruction")
proc jumpi*(computation) =
let (jumpDest, checkValue) = stack.popInt(2)
if checkValue > 0:
code.pc = jumpDest.toInt
let nextOpcode = code.peek()
if nextOpcode != JUMPDEST:
raise newException(InvalidJumpDestination, "Invalid Jump Destination")
if not code.isValidOpcode(jumpDest.toInt):
raise newException(InvalidInstruction, "Jump resulted in invalid instruction")
proc jumpdest*(computation) =
discard
proc pc*(computation) =
let pc = max(code.pc - 1, 0).u256
stack.push(pc)
proc gas*(computation) =
let gasRemaining = gasMeter.gasRemaining
stack.push(gasRemaining.u256)

View File

@ -1,41 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
macros,
eth_common/eth_types
template pushRes*: untyped =
computation.stack.push(res)
macro quasiBoolean*(name: untyped, op: untyped, signed: untyped = nil, nonzero: untyped = nil): untyped =
var signedNode = newEmptyNode()
var finishSignedNode = newEmptyNode()
let resNode = ident("res")
var leftNode = ident("left")
var rightNode = ident("right")
var actualLeftNode = leftNode
var actualRightNode = rightNode
if not signed.isNil:
actualLeftNode = ident("leftSigned")
actualRightNode = ident("rightSigned")
signedNode = quote:
let `actualLeftNode` = cast[Int256](`leftNode`)
let `actualRightNode` = cast[Int256](`rightNode`)
var test = if nonzero.isNil:
quote:
`op`(`actualLeftNode`, `actualRightNode`)
else:
quote:
`op`(`actualLeftNode`, `actualRightNode`) != 0
result = quote:
proc `name`*(computation: var BaseComputation) =
var (`leftNode`, `rightNode`) = computation.stack.popInt(2)
`signedNode`
var `resNode` = if `test`: 1.u256 else: 0.u256
computation.stack.push(`resNode`)

View File

@ -1,22 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
eth_common/eth_types, stint/lenient_stint,
../../../constants, ../../../vm_state, ../../../vm_types, ../../../vm_types,
../../../errors, ../../../logging, ../../../utils/padding, ../../../utils/bytes,
../../stack, ../../computation, ../../stack, ../../memory, ../../message,
../../code_stream, ../../utils/utils_numeric,
../opcode_values, ../gas_meter, ../gas_costs
export
eth_types, lenient_stint,
constants, vm_state, vm_types, vm_types,
errors, logging, padding, bytes,
stack, computation, stack, memory, message,
code_stream, utils_numeric,
opcode_values, gas_meter, gas_costs

View File

@ -1,12 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
./impl_std_import
proc invalidOp*(computation: var BaseComputation) =
raise newException(InvalidInstruction, "Invalid opcode")

View File

@ -1,71 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat, macros,
./impl_std_import
{.this: computation.}
{.experimental.}
using
computation: var BaseComputation
macro logXX(topicCount: static[int]): untyped =
if topicCount < 0 or topicCount > 4:
error(&"Invalid log topic len {topicCount} Must be 0, 1, 2, 3, or 4")
return
let name = ident(&"log{topicCount}")
let computation = ident("computation")
let topics = ident("topics")
let topicsTuple = ident("topicsTuple")
let len = ident("len")
let memPos = ident("memPos")
result = quote:
proc `name`*(`computation`: var BaseComputation) =
let (memStartPosition, size) = `computation`.stack.popInt(2)
let (`memPos`, `len`) = (memStartPosition.toInt, size.toInt)
var `topics`: seq[UInt256]
var topicCode: NimNode
if topicCount == 0:
topicCode = quote:
`topics` = @[]
elif topicCount > 1:
topicCode = quote:
let `topicsTuple` = `computation`.stack.popInt(`topicCount`)
topicCode = nnkStmtList.newTree(topicCode)
for z in 0 ..< topicCount:
let topicPush = quote:
`topics`.add(`topicsTuple`[`z`])
topicCode.add(topicPush)
else:
topicCode = quote:
`topics` = @[`computation`.stack.popInt()]
result.body.add(topicCode)
let OpName = ident(&"Log{topicCount}")
let logicCode = quote do:
`computation`.gasMeter.consumeGas(
`computation`.gasCosts[`OpName`].m_handler(`computation`.memory.len, `memPos`, `len`),
reason="Memory expansion, Log topic and data gas cost")
`computation`.memory.extend(`memPos`, `len`)
let logData = `computation`.memory.read(`memPos`, `len`).toString
`computation`.addLogEntry(
account = `computation`.msg.storageAddress,
topics = `topics`,
data = log_data)
result.body.add(logicCode)
logXX(0)
logXX(1)
logXX(2)
logXX(3)
logXX(4)

View File

@ -1,57 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
./impl_std_import
{.this: computation.}
{.experimental.}
using
computation: var BaseComputation
# TODO template handler
proc mstore*(computation) =
let start = stack.popInt().toInt
let normalizedValue = stack.popInt().toByteArrayBE
computation.gasMeter.consumeGas(
computation.gasCosts[MStore].m_handler(computation.memory.len, start, 32),
reason="MSTORE: GasVeryLow + memory expansion"
)
memory.extend(start, 32)
memory.write(start, normalizedValue)
proc mstore8*(computation) =
let start = stack.popInt().toInt
let value = stack.popInt()
let normalizedValue = (value and 0xff).toByteArrayBE
computation.gasMeter.consumeGas(
computation.gasCosts[MStore8].m_handler(computation.memory.len, start, 1),
reason="MSTORE8: GasVeryLow + memory expansion"
)
memory.extend(start, 1)
memory.write(start, [normalizedValue[0]])
proc mload*(computation) =
let start = stack.popInt().toInt
computation.gasMeter.consumeGas(
computation.gasCosts[MLoad].m_handler(computation.memory.len, start, 32),
reason="MLOAD: GasVeryLow + memory expansion"
)
memory.extend(start, 32)
let value = memory.read(start, 32)
stack.push(value)
proc msize*(computation) =
stack.push(memory.len.u256)

View File

@ -1,24 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
nimcrypto,
./impl_std_import, ./helpers
proc sha3op*(computation: var BaseComputation) =
let (startPosition, size) = computation.stack.popInt(2)
let (pos, len) = (startPosition.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[Op.Sha3].m_handler(computation.memory.len, pos, len),
reason="SHA3: word gas cost"
)
computation.memory.extend(pos, len)
var res = keccak256.digest("") # TODO: stub
pushRes()

View File

@ -1,59 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat, macros, ./impl_std_import
{.this: computation.}
{.experimental.}
using
computation: var BaseComputation
proc pop*(computation) =
discard stack.popInt()
macro pushXX(size: static[int]): untyped =
let computation = ident("computation")
let value = ident("value")
let name = ident(&"push{size}")
result = quote:
proc `name`*(`computation`: var BaseComputation) =
`computation`.stack.push `computation`.code.readVmWord(`size`)
pushXX(1)
pushXX(2)
pushXX(3)
pushXX(4)
pushXX(5)
pushXX(6)
pushXX(7)
pushXX(8)
pushXX(9)
pushXX(10)
pushXX(11)
pushXX(12)
pushXX(13)
pushXX(14)
pushXX(15)
pushXX(16)
pushXX(17)
pushXX(18)
pushXX(19)
pushXX(20)
pushXX(21)
pushXX(22)
pushXX(23)
pushXX(24)
pushXX(25)
pushXX(26)
pushXX(27)
pushXX(28)
pushXX(29)
pushXX(30)
pushXX(31)
pushXX(32)

View File

@ -1,44 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
./impl_std_import, strformat,
../../../utils/header,
../../../db/[db_chain, state_db]
{.this: computation.}
{.experimental.}
using
computation: var BaseComputation
proc sstore*(computation) =
let (slot, value) = stack.popInt(2)
var (currentValue, existing) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot)
let
gasParam = GasParams(kind: Op.Sstore, s_isStorageEmpty: not existing)
(gasCost, gasRefund) = computation.gasCosts[Sstore].c_handler(currentValue, gasParam)
computation.gasMeter.consumeGas(gasCost, &"SSTORE: {computation.msg.storageAddress}[{slot}] -> {value} ({currentValue})")
if gasRefund > 0:
computation.gasMeter.refundGas(gasRefund)
computation.vmState.mutateStateDB:
db.setStorage(computation.msg.storageAddress, slot, value)
proc sload*(computation) =
let slot = stack.popInt()
let (value, found) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot)
if found:
computation.stack.push value
else:
# XXX: raise exception?
discard

View File

@ -1,33 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
macros, strformat, ./impl_std_import
macro swapXX(position: static[int]): untyped =
let name = ident(&"swap{position}")
result = quote:
proc `name`*(computation: var BaseComputation) =
computation.stack.swap(`position`)
swapXX(0)
swapXX(1)
swapXX(2)
swapXX(3)
swapXX(4)
swapXX(5)
swapXX(6)
swapXX(7)
swapXX(8)
swapXX(9)
swapXX(10)
swapXX(11)
swapXX(12)
swapXX(13)
swapXX(14)
swapXX(15)
swapXX(16)

View File

@ -1,177 +0,0 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat,
./call, ./impl_std_import,
byteutils, eth_common
{.this: computation.}
{.experimental.}
using
computation: var BaseComputation
type
Create* = ref object of Opcode
CreateEIP150* = ref object of Create # TODO: Refactoring - put that in VM forks
CreateByzantium* = ref object of CreateEIP150 # TODO: Refactoring - put that in VM forks
# method maxChildGasModifier(create: Create, gas: GasInt): GasInt {.base.} =
# gas
method runLogic*(create: Create, computation) =
# computation.gasMeter.consumeGas(computation.gasCosts[create.gasCost(computation)], reason = $create.kind) # TODO: Refactoring create gas costs
let (value, startPosition, size) = computation.stack.popInt(3)
let (pos, len) = (startPosition.toInt, size.toInt)
computation.memory.extend(pos, len)
# TODO: with ZZZZ
# with computation.vm_state.state_db(read_only=True) as state_db:
# insufficient_funds = state_db.get_balance(
# computation.msg.storage_address) < value
# stack_too_deep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT
# if insufficient_funds or stack_too_deep:
# computation.stack.push(0)
# return
let callData = computation.memory.read(pos, len)
# TODO refactor gas
# let createMsgGas = create.maxChildGasModifier(computation.gasMeter.gasRemaining)
# computation.gasMeter.consumeGas(createMsgGas, reason="CREATE")
# TODO: with
# with computation.vm_state.state_db() as state_db:
# creation_nonce = state_db.get_nonce(computation.msg.storage_address)
# state_db.increment_nonce(computation.msg.storage_address)
# contract_address = generate_contract_address(
# computation.msg.storage_address,
# creation_nonce,
# )
# is_collision = state_db.account_has_code_or_nonce(contract_address)
let contractAddress = ZERO_ADDRESS
let isCollision = false
if isCollision:
computation.vmState.logger.debug(&"Address collision while creating contract: {contractAddress.toHex}")
computation.stack.push(0.u256)
return
let childMsg = computation.prepareChildMessage(
gas=0, # TODO refactor gas
to=CREATE_CONTRACT_ADDRESS,
value=value,
data=cast[seq[byte]](@[]),
code=callData.toString,
options=MessageOptions(createAddress: contractAddress))
# let childComputation = computation.applyChildComputation(childMsg)
var childComputation: BaseComputation
if childComputation.isError:
computation.stack.push(0.u256)
else:
computation.stack.push(contractAddress)
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
# TODO refactor gas
# method maxChildGasModifier(create: CreateEIP150, gas: GasInt): GasInt =
# maxChildGasEIP150(gas)
method runLogic*(create: CreateByzantium, computation) =
if computation.msg.isStatic:
raise newException(WriteProtection, "Cannot modify state while inside of a STATICCALL context")
procCall runLogic(create, computation)
proc selfdestructEIP150(computation) =
let beneficiary = stack.popAddress()
# TODO: with ZZZZ
# with computation.vm_state.state_db(read_only=True) as state_db:
# if not state_db.account_exists(beneficiary):
# computation.gas_meter.consume_gas(
# constants.GAS_SELFDESTRUCT_NEWACCOUNT,
# reason=mnemonics.SELFDESTRUCT,
# )
# _selfdestruct(computation, beneficiary)
proc selfdestructEIP161(computation) =
let beneficiary = stack.popAddress()
# TODO: with ZZZZ
# with computation.vm_state.state_db(read_only=True) as state_db:
# is_dead = (
# not state_db.account_exists(beneficiary) or
# state_db.account_is_empty(beneficiary)
# )
# if is_dead and state_db.get_balance(computation.msg.storage_address):
# computation.gas_meter.consume_gas(
# constants.GAS_SELFDESTRUCT_NEWACCOUNT,
# reason=mnemonics.SELFDESTRUCT,
# )
# _selfdestruct(computation, beneficiary)
proc selfdestruct(computation; beneficiary: EthAddress) =
discard # TODO: with ZZZZ
# with computation.vm_state.state_db() as state_db:
# local_balance = state_db.get_balance(computation.msg.storage_address)
# beneficiary_balance = state_db.get_balance(beneficiary)
# # 1st: Transfer to beneficiary
# state_db.set_balance(beneficiary, local_balance + beneficiary_balance)
# # 2nd: Zero the balance of the address being deleted (must come after
# # sending to beneficiary in case the contract named itself as the
# # beneficiary.
# state_db.set_balance(computation.msg.storage_address, 0)
# computation.vm_state.logger.debug(
# "SELFDESTRUCT: %s (%s) -> %s",
# encode_hex(computation.msg.storage_address),
# local_balance,
# encode_hex(beneficiary))
# 3rd: Register the account to be deleted
computation.registerAccountForDeletion(beneficiary)
raise newException(Halt, "SELFDESTRUCT")
proc returnOp*(computation) =
let (startPosition, size) = stack.popInt(2)
let (pos, len) = (startPosition.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[Return].m_handler(computation.memory.len, pos, len),
reason = "RETURN"
)
computation.memory.extend(pos, len)
let output = memory.read(pos, len)
computation.output = output.toString
raise newException(Halt, "RETURN")
proc revert*(computation) =
let (startPosition, size) = stack.popInt(2)
let (pos, len) = (startPosition.toInt, size.toInt)
computation.gasMeter.consumeGas(
computation.gasCosts[Op.Revert].m_handler(computation.memory.len, pos, len),
reason = "REVERT"
)
computation.memory.extend(pos, len)
let output = memory.read(pos, len).toString
computation.output = output
raise newException(Revert, $output)
proc selfdestruct*(computation) =
let beneficiary = stack.popAddress()
selfdestruct(computation, beneficiary)
raise newException(Halt, "SELFDESTRUCT")

View File

@ -5,6 +5,9 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# ##################################################################
# Macros to facilitate opcode enum and table creation
import macros, strformat, strutils
# Due to https://github.com/nim-lang/Nim/issues/8007, we can't

View File

@ -0,0 +1,157 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# ##################################################################
# Macros to facilitate opcode procs creation
import
macros, strformat, stint,
../../computation, ../../stack, ../../code_stream,
../../../constants, ../../../vm_types
proc pop(tree: var NimNode): NimNode =
## Returns the last value of a NimNode and remove it
result = tree[tree.len-1]
tree.del(tree.len-1)
macro op*(procname: untyped, inline: static[bool], stackParams_body: varargs[untyped]): untyped =
## Usage:
## .. code-block:: nim
## op add, inline = true, lhs, rhs:
## push:
## lhs + rhs
# TODO: Unfortunately due to varargs[untyped] consuming all following parameters,
# we can't have a nicer macro signature `stackParams: varargs[untyped], body: untyped`
# see https://github.com/nim-lang/Nim/issues/5855 and are forced to "pop"
let computation = newIdentNode("computation")
var stackParams = stackParams_body
# 1. Separate stackParams and body with pop
let body = newStmtList().add stackParams.pop
# 3. let (x, y, z) = computation.stack.popInt(3)
let len = stackParams.len
var popStackStmt = nnkVarTuple.newTree()
if len != 0:
for params in stackParams:
popStackStmt.add newIdentNode(params.ident)
popStackStmt.add newEmptyNode()
popStackStmt.add quote do:
`computation`.stack.popInt(`len`)
popStackStmt = nnkStmtList.newTree(
nnkLetSection.newTree(popStackStmt)
)
else:
popStackStmt = nnkDiscardStmt.newTree(newEmptyNode())
# 4. Generate the proc
# TODO: replace by func to ensure no side effects
if inline:
result = quote do:
proc `procname`*(`computation`: var BaseComputation) {.inline.} =
`popStackStmt`
`body`
else:
result = quote do:
proc `procname`*(`computation`: var BaseComputation) =
`popStackStmt`
`body`
macro genPush*(): untyped =
# TODO: avoid allocating a seq[byte], transforming to a string, stripping char
func genName(size: int): NimNode = ident(&"push{size}")
result = newStmtList()
for size in 1 .. 32:
let name = genName(size)
result.add quote do:
func `name`*(computation: var BaseComputation) {.inline.}=
## Push `size`-byte(s) on the stack
computation.stack.push computation.code.readVmWord(`size`)
macro genDup*(): untyped =
func genName(position: int): NimNode = ident(&"dup{position}")
result = newStmtList()
for pos in 1 .. 16:
let name = genName(pos)
result.add quote do:
func `name`*(computation: var BaseComputation) {.inline.}=
computation.stack.dup(`pos`)
macro genSwap*(): untyped =
func genName(position: int): NimNode = ident(&"swap{position}")
result = newStmtList()
for pos in 1 .. 16:
let name = genName(pos)
result.add quote do:
func `name`*(computation: var BaseComputation) {.inline.}=
computation.stack.swap(`pos`)
proc logImpl(topicCount: int): NimNode =
# TODO: use toopenArray to avoid some string allocations
if topicCount < 0 or topicCount > 4:
error(&"Invalid log topic len {topicCount} Must be 0, 1, 2, 3, or 4")
return
let name = ident(&"log{topicCount}")
let computation = ident("computation")
let topics = ident("topics")
let topicsTuple = ident("topicsTuple")
let len = ident("len")
let memPos = ident("memPos")
result = quote:
proc `name`*(`computation`: var BaseComputation) =
let (memStartPosition, size) = `computation`.stack.popInt(2)
let (`memPos`, `len`) = (memStartPosition.toInt, size.toInt)
var `topics`: seq[UInt256]
var topicCode: NimNode
if topicCount == 0:
topicCode = quote:
`topics` = @[]
elif topicCount > 1:
topicCode = quote:
let `topicsTuple` = `computation`.stack.popInt(`topicCount`)
topicCode = nnkStmtList.newTree(topicCode)
for z in 0 ..< topicCount:
let topicPush = quote:
`topics`.add(`topicsTuple`[`z`])
topicCode.add(topicPush)
else:
topicCode = quote:
`topics` = @[`computation`.stack.popInt()]
result.body.add(topicCode)
let OpName = ident(&"Log{topicCount}")
let logicCode = quote do:
`computation`.gasMeter.consumeGas(
`computation`.gasCosts[`OpName`].m_handler(`computation`.memory.len, `memPos`, `len`),
reason="Memory expansion, Log topic and data gas cost")
`computation`.memory.extend(`memPos`, `len`)
let logData = `computation`.memory.read(`memPos`, `len`).toString
addLogEntry(
`computation`,
account = `computation`.msg.storageAddress,
topics = `topics`,
data = log_data)
result.body.add(logicCode)
macro genLog*(): untyped =
result = newStmtList()
for i in 0..4:
result.add logImpl(i)

View File

@ -8,7 +8,7 @@
import
strformat, strutils, sequtils, endians, macros,
eth_common/eth_types, rlp,
../../constants, ../../utils/padding
../../../constants, ../../../utils/padding
# some methods based on py-evm utils/numeric
@ -18,15 +18,15 @@ proc bigEndianToInt*(value: openarray[byte]): UInt256 =
else:
readUintBE[256](padLeft(@value, 32, 0.byte))
proc log256*(value: UInt256): Natural =
(255 - value.countLeadingZeroBits) div 8 # Compilers optimize to `shr 3`
proc log256*(value: UInt256): Natural {.inline.}=
(255 - value.countLeadingZeroBits) shr 3 # div 8
proc unsignedToPseudoSigned*(value: UInt256): UInt256 =
proc unsignedToPseudoSigned*(value: UInt256): UInt256 {.inline.}=
result = value
if value > INT_256_MAX_AS_UINT256:
result -= INT_256_MAX_AS_UINT256
proc pseudoSignedToUnsigned*(value: UInt256): UInt256 =
proc pseudoSignedToUnsigned*(value: UInt256): UInt256 {.inline.}=
result = value
if value > INT_256_MAX_AS_UINT256:
result += INT_256_MAX_AS_UINT256

View File

@ -5,22 +5,46 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
eth_common/eth_types,
../../db/db_chain, ../../constants,
../../utils/header,
../base,
../forks/f20150730_frontier/frontier_vm,
../forks/f20161018_tangerine_whistle/tangerine_vm
import stint
# Note (mamy): refactoring is in progress (2018-05-23), this is redundant with
# - `Chain` in src/chain.nim, to be honest I don't understand the need of this abstraction at the moment
# - `toFork` in src/constant. This is temporary until more VMs are implemented
type
Fork* = enum
# FkGenesis
FkFrontier,
FkThawing,
FkHomestead,
FkDao,
FkTangerine,
FkSpurious,
FkByzantium
proc newNimbusVM*(header: BlockHeader, chainDB: BaseChainDB): VM =
UInt256Pair = tuple[a: Uint256, b: Uint256]
# TODO: deal with empty BlockHeader
if header.blockNumber < FORK_TANGERINE_WHISTLE_BLKNUM:
result = newFrontierVM(header, chainDB)
let forkBlocks: array[Fork, Uint256] = [
FkFrontier: 1.u256, # 30/07/2015 19:26:28
FkThawing: 200_000.u256, # 08/09/2015 01:33:09
FkHomestead: 1_150_000.u256, # 14/03/2016 20:49:53
FkDao: 1_920_000.u256, # 20/07/2016 17:20:40
FkTangerine: 2_463_000.u256, # 18/10/2016 17:19:31
FkSpurious: 2_675_000.u256, # 22/11/2016 18:15:44
FkByzantium: 4_370_000.u256 # 16/10/2017 09:22:11
]
proc toFork*(blockNumber: UInt256): Fork =
# TODO: uint256 comparison is probably quite expensive
# hence binary search is probably worth it earlier than
# linear search
# TODO: all toFork usage currently incurs comparison to get the fork and then another comparison to
# go to the ultimate needed result.
# Genesis block 0 also uses the Frontier code path
if blockNumber < forkBlocks[FkThawing]: FkFrontier
elif blockNumber < forkBlocks[FkHomestead]: FkThawing
elif blockNumber < forkBlocks[FkDao]: FkHomestead
elif blockNumber < forkBlocks[FkTangerine]: FkDao
elif blockNumber < forkBlocks[FkSpurious]: FkTangerine
elif blockNumber < forkBlocks[FkByzantium]: FkSpurious
else:
result = newTangerineVM(header, chainDB)
FkByzantium # Update for constantinople when announced

View File

@ -0,0 +1,231 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
tables, macros,
./interpreter/[opcode_values, opcodes_impl, vm_forks, gas_costs, gas_meter, utils/macros_gen_opcodes],
./code_stream,
../vm_types, ../errors,
../logging, ./stack, ./computation, terminal # Those are only needed for logging
func invalidInstruction*(computation: var BaseComputation) {.inline.} =
raise newException(ValueError, "Invalid instruction, received an opcode not implemented in the current fork.")
let FrontierOpDispatch {.compileTime.}: array[Op, NimNode] = block:
fill_enum_table_holes(Op, newIdentNode"invalidInstruction"):
[
Stop: newIdentNode "toBeReplacedByBreak",
Add: newIdentNode "add",
Mul: newIdentNode "mul",
Sub: newIdentNode "sub",
Div: newIdentNode "divide",
Sdiv: newIdentNode "sdiv",
Mod: newIdentNode "modulo",
Smod: newIdentNode "smod",
Addmod: newIdentNode "addmod",
Mulmod: newIdentNode "mulmod",
Exp: newIdentNode "exp",
SignExtend: newIdentNode "signExtend",
# 10s: Comparison & Bitwise Logic Operations
Lt: newIdentNode "lt",
Gt: newIdentNode "gt",
Slt: newIdentNode "slt",
Sgt: newIdentNode "sgt",
Eq: newIdentNode "eq",
IsZero: newIdentNode "isZero",
And: newIdentNode "andOp",
Or: newIdentNode "orOp",
Xor: newIdentNode "xorOp",
Not: newIdentNode "notOp",
Byte: newIdentNode "byteOp",
# 20s: SHA3
Sha3: newIdentNode "sha3",
# 30s: Environmental Information
Address: newIdentNode "address",
Balance: newIdentNode "balance",
Origin: newIdentNode "origin",
Caller: newIdentNode "caller",
CallValue: newIdentNode "callValue",
CallDataLoad: newIdentNode "callDataLoad",
CallDataSize: newIdentNode "callDataSize",
CallDataCopy: newIdentNode "callDataCopy",
CodeSize: newIdentNode "codeSize",
CodeCopy: newIdentNode "codeCopy",
GasPrice: newIdentNode "gasPrice",
ExtCodeSize: newIdentNode "extCodeSize",
ExtCodeCopy: newIdentNode "extCodeCopy",
# ReturnDataSize: introduced in Byzantium
# ReturnDataCopy: introduced in Byzantium
# 40s: Block Information
Blockhash: newIdentNode "blockhash",
Coinbase: newIdentNode "coinbase",
Timestamp: newIdentNode "timestamp",
Number: newIdentNode "blockNumber",
Difficulty: newIdentNode "difficulty",
GasLimit: newIdentNode "gasLimit",
# 50s: Stack, Memory, Storage and Flow Operations
Pop: newIdentNode "pop",
Mload: newIdentNode "mload",
Mstore: newIdentNode "mstore",
Mstore8: newIdentNode "mstore8",
Sload: newIdentNode "sload",
Sstore: newIdentNode "sstore",
Jump: newIdentNode "jump",
JumpI: newIdentNode "jumpI",
Pc: newIdentNode "pc",
Msize: newIdentNode "msize",
Gas: newIdentNode "gas",
JumpDest: newIdentNode "jumpDest",
# 60s & 70s: Push Operations.
Push1: newIdentNode "push1",
Push2: newIdentNode "push2",
Push3: newIdentNode "push3",
Push4: newIdentNode "push4",
Push5: newIdentNode "push5",
Push6: newIdentNode "push6",
Push7: newIdentNode "push7",
Push8: newIdentNode "push8",
Push9: newIdentNode "push9",
Push10: newIdentNode "push10",
Push11: newIdentNode "push11",
Push12: newIdentNode "push12",
Push13: newIdentNode "push13",
Push14: newIdentNode "push14",
Push15: newIdentNode "push15",
Push16: newIdentNode "push16",
Push17: newIdentNode "push17",
Push18: newIdentNode "push18",
Push19: newIdentNode "push19",
Push20: newIdentNode "push20",
Push21: newIdentNode "push21",
Push22: newIdentNode "push22",
Push23: newIdentNode "push23",
Push24: newIdentNode "push24",
Push25: newIdentNode "push25",
Push26: newIdentNode "push26",
Push27: newIdentNode "push27",
Push28: newIdentNode "push28",
Push29: newIdentNode "push29",
Push30: newIdentNode "push30",
Push31: newIdentNode "push31",
Push32: newIdentNode "push32",
# 80s: Duplication Operations
Dup1: newIdentNode "dup1",
Dup2: newIdentNode "dup2",
Dup3: newIdentNode "dup3",
Dup4: newIdentNode "dup4",
Dup5: newIdentNode "dup5",
Dup6: newIdentNode "dup6",
Dup7: newIdentNode "dup7",
Dup8: newIdentNode "dup8",
Dup9: newIdentNode "dup9",
Dup10: newIdentNode "dup10",
Dup11: newIdentNode "dup11",
Dup12: newIdentNode "dup12",
Dup13: newIdentNode "dup13",
Dup14: newIdentNode "dup14",
Dup15: newIdentNode "dup15",
Dup16: newIdentNode "dup16",
# 90s: Exchange Operations
Swap1: newIdentNode "swap1",
Swap2: newIdentNode "swap2",
Swap3: newIdentNode "swap3",
Swap4: newIdentNode "swap4",
Swap5: newIdentNode "swap5",
Swap6: newIdentNode "swap6",
Swap7: newIdentNode "swap7",
Swap8: newIdentNode "swap8",
Swap9: newIdentNode "swap9",
Swap10: newIdentNode "swap10",
Swap11: newIdentNode "swap11",
Swap12: newIdentNode "swap12",
Swap13: newIdentNode "swap13",
Swap14: newIdentNode "swap14",
Swap15: newIdentNode "swap15",
Swap16: newIdentNode "swap16",
# a0s: Logging Operations
Log0: newIdentNode "log0",
Log1: newIdentNode "log1",
Log2: newIdentNode "log2",
Log3: newIdentNode "log3",
Log4: newIdentNode "log4",
# f0s: System operations
Create: newIdentNode "create",
Call: newIdentNode "call",
CallCode: newIdentNode "callCode",
Return: newIdentNode "returnOp",
DelegateCall: newIdentNode "delegateCall",
# StaticCall: introduced in Byzantium
# Revert: introduced in Byzantium
# Invalid: newIdentNode "invalid",
SelfDestruct: newIdentNode "selfDestruct"
]
proc opTableToCaseStmt(opTable: array[Op, NimNode], computation: NimNode): NimNode =
let instr = genSym(nskVar)
result = nnkCaseStmt.newTree(instr)
# Add a branch for each (opcode, proc) pair
# We dispatch to the next instruction at the end of each branch
for op, opImpl in opTable.pairs:
let branchStmt = block:
if op == Stop:
quote do: break
else:
let asOp = quote do: Op(`op`) # TODO: unfortunately when passing to runtime, ops are transformed into int
if BaseGasCosts[op].kind == GckFixed:
quote do:
`computation`.gasMeter.consumeGas(`computation`.gasCosts[`asOp`].cost, reason = $`asOp`)
`opImpl`(`computation`)
`instr` = `computation`.code.next()
else:
quote do:
`opImpl`(`computation`)
when `asOp` in {Return, Revert, SelfDestruct}:
break
else:
`instr` = `computation`.code.next()
result.add nnkOfBranch.newTree(
newIdentNode($op),
branchStmt
)
# Wrap the case statement in while true + computed goto
result = quote do:
var `instr` = `computation`.code.next()
while true:
{.computedGoto.}
`computation`.logger.log($`computation`.stack & "\n\n", fgGreen)
`result`
macro genFrontierDispatch(computation: BaseComputation): untyped =
result = opTableToCaseStmt(FrontierOpDispatch, computation)
proc frontierVM(computation: var BaseComputation) =
genFrontierDispatch(computation)
proc executeOpcodes*(computation: var BaseComputation) =
let fork = computation.vmState.blockHeader.blockNumber.toFork
try:
case fork
of FkFrontier: computation.frontierVM()
else:
raise newException(ValueError, "not implemented fork: " & $fork)
except VMError:
computation.error = Error(info: getCurrentExceptionMsg())

View File

@ -9,7 +9,7 @@ import
sequtils,
eth_common/eth_types,
../constants, ../errors, ../logging, ../validation, ../utils/bytes,
./utils/utils_numeric
./interpreter/utils/utils_numeric
type
Memory* = ref object
@ -39,6 +39,7 @@ proc newMemory*(size: Natural): Memory =
result.extend(0, size)
proc read*(memory: var Memory, startPos: Natural, size: Natural): seq[byte] =
# TODO: use an openarray[byte]
result = memory.bytes[startPos ..< (startPos + size)]
proc write*(memory: var Memory, startPos: Natural, value: openarray[byte]) =
@ -57,14 +58,6 @@ proc write*(memory: var Memory, startPos: Natural, value: openarray[byte]) =
for z, b in value:
memory.bytes[z + startPos] = b
proc writePaddingBytes*(memory: var Memory,
startPos, numberOfBytes: Natural,
paddingValue = 0.byte) =
let endPos = startPos + numberOfBytes
assert endPos < memory.len
for i in startPos ..< endPos:
memory.bytes[i] = paddingValue
template write*(memory: var Memory, startPos: Natural, size: Natural, value: cstring) =
memory.write(startPos, value.toBytes)
# TODO ~ O(n^3):

View File

@ -7,7 +7,7 @@
import
strformat, strutils, sequtils, macros, rlp, eth_common, nimcrypto,
../errors, ../validation, ./utils/utils_numeric, ../constants, ../logging, .. / utils / bytes
../errors, ../validation, ./interpreter/utils/utils_numeric, ../constants, ../logging, .. / utils / bytes
type
Stack* = ref object of RootObj
@ -24,7 +24,7 @@ proc len*(stack: Stack): int {.inline.} =
len(stack.values)
proc toStackElement(v: UInt256, elem: var StackElement) {.inline.} = elem = v
proc toStackElement(v: uint | int, elem: var StackElement) {.inline.} = elem = v.u256
proc toStackElement(v: uint | int | GasInt, elem: var StackElement) {.inline.} = elem = v.u256
proc toStackElement(v: EthAddress, elem: var StackElement) {.inline.} = elem = bigEndianToInt(v)
proc toStackElement(v: MDigest, elem: var StackElement) {.inline.} = elem = readUintBE[256](v.data)
@ -34,7 +34,7 @@ proc fromStackElement(elem: StackElement, v: var Hash256) {.inline.} = v.data =
proc toStackElement(v: openarray[byte], elem: var StackElement) {.inline.} =
# TODO: This needs to go
# validateStackItem(v)
validateStackItem(v) # This is necessary to pass stack tests
elem = bigEndianToInt(v)
proc pushAux[T](stack: var Stack, value: T) =
@ -42,7 +42,7 @@ proc pushAux[T](stack: var Stack, value: T) =
stack.values.setLen(stack.values.len + 1)
toStackElement(value, stack.values[^1])
proc push*(stack: var Stack, value: uint | UInt256 | EthAddress | Hash256) {.inline.} =
proc push*(stack: var Stack, value: uint | int | GasInt | UInt256 | EthAddress | Hash256) {.inline.} =
pushAux(stack, value)
proc push*(stack: var Stack, value: openarray[byte]) {.inline.} =

View File

@ -37,12 +37,13 @@ proc `$`*(vmState: BaseVMState): string =
else:
result = &"VMState {vmState.name}:\n header: {vmState.blockHeader}\n chaindb: {vmState.chaindb}"
proc newBaseVMState*: BaseVMState =
new(result)
proc newBaseVMState*(header: BlockHeader, chainDB: BaseChainDB): BaseVMState =
new result
result.prevHeaders = @[]
result.name = "BaseVM"
result.accessLogs = newAccessLogs()
# result.blockHeader = # TODO...
result.blockHeader = header
result.chaindb = chainDB
method logger*(vmState: BaseVMState): Logger =
logging.getLogger(&"evm.vmState.{vmState.name}")

View File

@ -24,7 +24,7 @@ type
code*: CodeStream
children*: seq[BaseComputation]
rawOutput*: string
returnData*: string
returnData*: seq[byte]
error*: Error
logEntries*: seq[(EthAddress, seq[UInt256], string)]
shouldEraseReturnData*: bool
@ -45,7 +45,7 @@ type
kind*: Op
runLogic*: proc(computation: var BaseComputation)
GasMeter* = ref object
GasMeter* = ref object # TODO: use a normal object
logger*: Logger
gasRefunded*: GasInt
startGas*: GasInt

View File

@ -5,7 +5,7 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
when false:
when true:
import ./test_code_stream,
./test_gas_meter,
./test_memory,
@ -13,5 +13,5 @@ when false:
./test_opcode,
./test_storage_backends
when true:
when false:
import ./test_vm_json

View File

@ -44,14 +44,15 @@ suite "parse bytecode":
discard codeStream.next
check(codeStream.next == Op.STOP)
test "seek reverts to original position on exit":
var codeStream = newCodeStream("\x01\x02\x30")
check(codeStream.pc == 0)
codeStream.seek(1):
check(codeStream.pc == 1)
check(codeStream.next == Op.MUL)
check(codeStream.pc == 0)
check(codeStream.peek == Op.ADD)
# Seek has been dommented out for future deletion
# test "seek reverts to original position on exit":
# var codeStream = newCodeStream("\x01\x02\x30")
# check(codeStream.pc == 0)
# codeStream.seek(1):
# check(codeStream.pc == 1)
# check(codeStream.next == Op.MUL)
# check(codeStream.pc == 0)
# check(codeStream.peek == Op.ADD)
test "[] returns opcode":
let codeStream = newCodeStream("\x01\x02\x30")

View File

@ -11,7 +11,7 @@ import
../nimbus/utils/[address, padding],
../nimbus/[vm_state, constants],
../nimbus/db/[db_chain, state_db],
../nimbus/vm/base, ../nimbus/transaction
../nimbus/transaction
type
Status* {.pure.} = enum OK, Fail, Skip
@ -125,21 +125,3 @@ proc verifyStateDB*(wantedState: JsonNode, stateDB: AccountStateDB) =
proc getHexadecimalInt*(j: JsonNode): int =
discard parseHex(j.getStr, result)
method newTransaction*(
vm: VM, addr_from, addr_to: EthAddress,
amount: UInt256,
private_key: PrivateKey,
gas_price = 10.u256,
gas = 100000.u256,
data: seq[byte] = @[]
): BaseTransaction =
# TODO: amount should be an Int to deal with negatives
new result
# Todo getStateDB is incomplete
let nonce = vm.state.readOnlyStateDB.getNonce(addr_from)
# TODO
# if !private key: create_unsigned_transaction
# else: create_signed_transaction

View File

@ -22,7 +22,7 @@ suite "memory":
test "write":
var mem = memory32()
# Test that write creates 32byte string == value padded with zeros
mem.write(startPosition = 0, value = @[1.byte, 0.byte, 1.byte, 0.byte])
mem.write(startPos = 0, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.bytes == @[1.byte, 0.byte, 1.byte, 0.byte].concat(repeat(0.byte, 28)))
# test "write rejects invalid position":
@ -48,23 +48,23 @@ suite "memory":
test "write rejects valyes beyond memory size":
expect(ValidationError):
var mem = memory128()
mem.write(startPosition = 128, value = @[1.byte, 0.byte, 1.byte, 0.byte])
mem.write(startPos = 128, value = @[1.byte, 0.byte, 1.byte, 0.byte])
test "extends appropriately extends memory":
var mem = newMemory()
# Test extends to 32 byte array: 0 < (start_position + size) <= 32
mem.extend(startPosition = 0, size = 10)
mem.extend(startPos = 0, size = 10)
check(mem.bytes == repeat(0.byte, 32))
# Test will extend past length if params require: 32 < (start_position + size) <= 64
mem.extend(startPosition = 28, size = 32)
mem.extend(startPos = 28, size = 32)
check(mem.bytes == repeat(0.byte, 64))
# Test won't extend past length unless params require: 32 < (start_position + size) <= 64
mem.extend(startPosition = 48, size = 10)
mem.extend(startPos = 48, size = 10)
check(mem.bytes == repeat(0.byte, 64))
test "read returns correct bytes":
var mem = memory32()
mem.write(startPosition = 5, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPosition = 5, size = 4) == @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPosition = 6, size = 4) == @[0.byte, 1.byte, 0.byte, 0.byte])
check(mem.read(startPosition = 1, size = 3) == @[0.byte, 0.byte, 0.byte])
mem.write(startPos = 5, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPos = 5, size = 4) == @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPos = 6, size = 4) == @[0.byte, 1.byte, 0.byte, 0.byte])
check(mem.read(startPos = 1, size = 3) == @[0.byte, 0.byte, 0.byte])

View File

@ -8,7 +8,7 @@
import
unittest, tables, parseutils,
eth_trie/[types, memdb], eth_common/eth_types,
../nimbus/[constants, vm_types, logging],
../nimbus/[constants, vm_types, logging, vm_state],
../nimbus/vm/interpreter,
../nimbus/utils/header,
../nimbus/db/[db_chain, state_db, backends/memory_backend],
@ -19,12 +19,13 @@ from eth_common import GasInt
proc testCode(code: string, initialGas: GasInt, blockNum: UInt256): BaseComputation =
let header = BlockHeader(blockNumber: blockNum)
var memDb = newMemDB()
var vm = newNimbusVM(header, newBaseChainDB(trieDB memDb))
# coinbase: "",
# difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256,
# blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256,
# gasLimit: fixture{"env"}{"currentGasLimit"}.getHexadecimalInt.u256,
# timestamp: fixture{"env"}{"currentTimestamp"}.getHexadecimalInt)
var vmState = newBaseVMState(header, newBaseChainDB(trieDB memDb))
# coinbase: "",
# difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256,
# blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256,
# gasLimit: fixture{"env"}{"currentGasLimit"}.getHexadecimalInt.u256,
# timestamp: fixture{"env"}{"currentTimestamp"}.getHexadecimalInt)
let message = newMessage(
to=ZERO_ADDRESS, #fixture{"exec"}{"address"}.getStr,
@ -42,12 +43,10 @@ proc testCode(code: string, initialGas: GasInt, blockNum: UInt256): BaseComputat
if DEBUG:
c.displayDecompiled()
var computation = newBaseComputation(vm.state, message)
computation.opcodes = OpLogic # TODO remove this need
computation.precompiles = initTable[string, Opcode]()
result = newBaseComputation(vmState, blockNum, message)
result.precompiles = initTable[string, Opcode]()
computation = computation.applyComputation(vm.state, message)
result = computation
result.executeOpcodes()
suite "opcodes":
test "add":
@ -76,37 +75,39 @@ suite "opcodes":
# assert_store(&ext, 0, "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe");
# }
test "Frontier VM computation - pre-EIP150 gas cost properly applied":
block: # Using Balance (0x31)
var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31",
100_000,
0.u256
)
check: c.gasMeter.gasRemaining == 100000 - 3 - 20 # Starting gas - push32 (verylow) - balance
block: # Using SLOAD (0x54)
var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54",
100_000,
0.u256
)
check: c.gasMeter.gasRemaining == 100000 - 3 - 50 # Starting gas - push32 (verylow) - SLOAD
# TODO balance and sload were previously stubbed. Test must be rewritten to initialize the DB properly
# test "Frontier VM computation - pre-EIP150 gas cost properly applied":
# block: # Using Balance (0x31)
# var c = testCode(
# "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31",
# 100_000,
# 0.u256
# )
# check: c.gasMeter.gasRemaining == 100000 - 3 - 20 # Starting gas - push32 (verylow) - balance
# block: # Using SLOAD (0x54)
# var c = testCode(
# "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54",
# 100_000,
# 0.u256
# )
# check: c.gasMeter.gasRemaining == 100000 - 3 - 50 # Starting gas - push32 (verylow) - SLOAD
test "Tangerine VM computation - post-EIP150 gas cost properly applied":
block: # Using Balance (0x31)
var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31",
100_000,
2_463_000.u256 # Tangerine block
)
check: c.gasMeter.gasRemaining == 100000 - 3 - 400 # Starting gas - push32 (verylow) - balance
# test "Tangerine VM computation - post-EIP150 gas cost properly applied":
# block: # Using Balance (0x31)
# var c = testCode(
# "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31",
# 100_000,
# 2_463_000.u256 # Tangerine block
# )
# check: c.gasMeter.gasRemaining == 100000 - 3 - 400 # Starting gas - push32 (verylow) - balance
block: # Using SLOAD (0x54)
var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54",
100_000,
2_463_000.u256
)
check: c.gasMeter.gasRemaining == 100000 - 3 - 200 # Starting gas - push32 (verylow) - SLOAD
# block: # Using SLOAD (0x54)
# var c = testCode(
# "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54",
# 100_000,
# 2_463_000.u256
# )
# check: c.gasMeter.gasRemaining == 100000 - 3 - 200 # Starting gas - push32 (verylow) - SLOAD

View File

@ -32,6 +32,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
for label, child in fixtures:
fixture = child
break
let fenv = fixture["env"]
var emptyRlpHash = keccak256.digest(rlp.encode("").toOpenArray)
let header = BlockHeader(
@ -42,12 +43,12 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
timestamp: fenv{"currentTimestamp"}.getHexadecimalInt.int64.fromUnix,
stateRoot: emptyRlpHash
)
var memDb = newMemDB()
var vm = newNimbusVM(header, newBaseChainDB(trieDB memDb))
var memDb = newMemDB()
var vmState = newBaseVMState(header, newBaseChainDB(trieDB memDb))
let fexec = fixture["exec"]
var code = ""
vm.state.mutateStateDB:
vmState.mutateStateDB:
setupStateDB(fixture{"pre"}, db)
let address = fexec{"address"}.getStr.parseAddress
code = stringFromBytes db.getCode(address)
@ -70,11 +71,11 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
if DEBUG:
c.displayDecompiled()
var computation = newBaseComputation(vm.state, message)
computation.opcodes = OpLogic
var computation = newBaseComputation(vmState, header.blockNumber, message)
computation.vmState = vmState
computation.precompiles = initTable[string, Opcode]()
computation = computation.applyComputation(vm.state, message)
computation.executeOpcodes()
if not fixture{"post"}.isNil:
# Success checks
@ -130,4 +131,4 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
# Error checks
check(computation.isError)
# TODO postState = fixture{"pre"}