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:
parent
18b7bbb3b0
commit
4b5eada322
24
VMTests.md
24
VMTests.md
|
@ -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
|
||||
|
|
|
@ -5,10 +5,9 @@ import
|
|||
proc default(t: typedesc): t = discard
|
||||
|
||||
# constants
|
||||
|
||||
let
|
||||
const
|
||||
UINT_256_MAX*: UInt256 = high(UInt256)
|
||||
INT_256_MAX_AS_UINT256* = cast[Uint256](high(Int256))
|
||||
INT_256_MAX_AS_UINT256* = high(Uint256) shr 1
|
||||
NULLBYTE* = "\x00"
|
||||
EMPTYWORD* = repeat(NULLBYTE, 32)
|
||||
UINT160CEILING*: UInt256 = 2.u256.pow(160)
|
||||
|
@ -54,36 +53,4 @@ let
|
|||
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
|
||||
|
||||
MaxCallDepth* = 1024
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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* =
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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`)
|
||||
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Folder to be deleted
|
|
@ -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
|
||||
|
|
@ -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")
|
|
@ -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, @[])
|
|
@ -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
|
||||
|
|
@ -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 = @[]
|
|
@ -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")
|
|
@ -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, @[])
|
|
@ -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: ...
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 two’s complement signed integer.
|
||||
|
||||
# 10s: Comparison & Bitwise Logic Operations
|
||||
|
|
|
@ -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 two’s 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)
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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")
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
@ -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)
|
|
@ -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`)
|
|
@ -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
|
|
@ -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")
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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)
|
|
@ -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
|
||||
|
|
@ -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)
|
|
@ -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")
|
||||
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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())
|
|
@ -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):
|
||||
|
|
|
@ -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.} =
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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,7 +19,8 @@ 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))
|
||||
var vmState = newBaseVMState(header, newBaseChainDB(trieDB memDb))
|
||||
|
||||
# coinbase: "",
|
||||
# difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256,
|
||||
# blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256,
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue