First part of port

Translate with py2nim
Work on improving the input, use more nim-ish stuff
This commit is contained in:
Alexander Ivanov 2018-01-15 20:42:40 +02:00
parent 44f6fa5945
commit 8ce5fa1773
14 changed files with 760 additions and 0 deletions

136
src/constants.nim Normal file
View File

@ -0,0 +1,136 @@
import
math, strutils, tables #, eth_utils
type
TypeHint* {.pure.} = enum UInt256, Bytes, Any
Int256* = distinct int # TODO
proc `>`*(a: Int256, b: Int256): bool =
a.int > b.int
proc `<`*(a: Int256, b: Int256): bool =
a.int < b.int
proc `$`*(a: Int256): string =
$(a.int)
# proc `-`*(a: Int256, b: Int256): Int256 {.borrow.}
# proc `+`*(a: Int256, b: Int256): Int256 {.borrow.}
# proc `-=`*(a: var Int256, b: Int256) {.borrow.}
# proc `+=`*(a: var Int256, b: Int256) {.borrow.}
proc `-`*(a: Int256, b: Int256): Int256 =
(a.int - b.int).Int256
proc `+`*(a: Int256, b: Int256): Int256 =
(a.int + b.int).Int256
proc `-=`*(a: var Int256, b: Int256) =
a = (a - b).Int256
proc `+=`*(a: var Int256, b: Int256) =
a = (a + b).Int256
proc repeat(b: cstring, count: int): cstring =
# TODO: faster
var s = $b
result = cstring(repeat(s, count))
const
# UINT256MAX = 2 ^ 256 - 1
# UINT256CEILING = 2 ^ 256
# UINT255MAX = 2 ^ 255 - 1
# UINT255CEILING = 2 ^ 255
NULLBYTE = cstring"\\x00"
EMPTYWORD = repeat(NULLBYTE, 32)
# UINT160CEILING = 2 ^ 160
CREATE_CONTRACT_ADDRESS* = cstring""
ZEROADDRESS = repeat(cstring"\\x00", 20)
ZEROHASH32 = repeat(cstring"\\x00", 20)
STACKDEPTHLIMIT = 1024
GASNULL = 0
GASZERO = 0
GASBASE = 2
GASVERYLOW = 3
GASLOW = 5
GASMID = 8
GASHIGH = 10
GASEXTCODE = 20
GASBALANCE = 20
GASSLOAD = 50
GASJUMPDEST = 1
GASSSET = 20000
GASSRESET = 5000
REFUNDSCLEAR = 15000
GASSELFDESTRUCT = 0
GASSELFDESTRUCTNEWACCOUNT = 25000
GASCREATE = 32000
GASCALL = 40
GASCALLVALUE = 9000
GASCALLSTIPEND = 2300
GASNEWACCOUNT = 25000
GASEXP = 10
GASEXPBYTE = 10
GASMEMORY = 3
GASTXCREATE = 32000
GASTXDATAZERO = 4
GASTXDATANONZERO = 68
GASTX = 21000
GASLOG = 375
GASLOGDATA = 8
GASLOGTOPIC = 375
GASSHA3 = 30
GASSHA3WORD = 6
GASCOPY = 3
GASBLOCKHASH = 20
GASCODEDEPOSIT = 200
GASMEMORYQUADRATICDENOMINATOR = 512
GASSHA256 = 60
GASSHA256WORD = 12
GASRIPEMD160 = 600
GASRIPEMD160WORD = 120
GASIDENTITY = 15
GASIDENTITYWORD = 3
GASECRECOVER = 3000
GASECADD = 500
GASECMUL = 40000
GASECPAIRINGBASE = 100000
GASECPAIRINGPERPOINT = 80000
GASLIMITEMADENOMINATOR = 1024
GASLIMITADJUSTMENTFACTOR = 1024
GASLIMITMINIMUM = 5000
# GASLIMITMAXIMUM = 2 ^ 63 - 1
GASLIMITUSAGEADJUSTMENTNUMERATOR = 3
GASLIMITUSAGEADJUSTMENTDENOMINATOR = 2
DIFFICULTYADJUSTMENTDENOMINATOR = 2048
DIFFICULTYMINIMUM = 131072
BOMBEXPONENTIALPERIOD = 100000
# BOMBEXPONENTIALFREEPERIODS = 2
# BLOCKREWARD = 5 * denoms.ether
UNCLEDEPTHPENALTYFACTOR = 8
MAXUNCLEDEPTH = 6
MAXUNCLES = 2
# SECPK1P = 2 ^ 256 - 2 ^ 32 - 977
SECPK1N = 0
SECPK1A = 0
SECPK1B = 7
SECPK1Gx = 0
SECPK1Gy = 0
SECPK1G = (SECPK1Gx, SECPK1Gy)
EMPTYUNCLEHASH = cstring"\\x1d\\xccM\\xe8\\xde\\xc7]z\\xab\\x85\\xb5g\\xb6\\xcc\\xd4\\x1a\\xd3\\x12E\\x1b\\x94\\x8at\\x13\\xf0\\xa1B\\xfd@\\xd4\\x93G"
GENESISBLOCKNUMBER = 0
GENESISDIFFICULTY = 131072
GENESISGASLIMIT = 3141592
GENESISPARENTHASH = ZEROHASH32
GENESISCOINBASE = ZEROADDRESS
GENESISNONCE = cstring"\\x00\\x00\\x00\\x00\\x00\\x00\\x00B"
GENESISMIXHASH = ZEROHASH32
EMPTYSHA3 = cstring"\\xc5\\xd2F\\x01\\x86\\xf7#<\\x92~}\\xb2\\xdc\\xc7\\x03\\xc0\\xe5\\x00\\xb6S\\xca\\x82';{\\xfa\\xd8\\x04]\\x85\\xa4p"
BLANKROOTHASH = cstring"V\\xe8\\x1f\\x17\\x1b\\xccU\\xa6\\xff\\x83E\\xe6\\x92\\xc0\\xf8n[H\\xe0\\x1b\\x99l\\xad\\xc0\\x01b/\\xb5\\xe3c\\xb4!"
GASMODEXPQUADRATICDENOMINATOR = 20
MAXPREVHEADERDEPTH = 256

71
src/errors.nim Normal file
View File

@ -0,0 +1,71 @@
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.
ParentNotFound* = object of EVMError
## The parent of a given block does not exist.
CanonicalHeadNotFound* = object of EVMError
## The chain has no canonical head.
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
burnsGas*: bool
OutOfGas* = object of VMError
## Error signaling that VM execution has run out of gas.
InsufficientStack* = object of VMError
## Error signaling that the stack is empty.
FullStack* = object of VMError
## Error signaling that the stack is full.
InvalidJumpDestination* = object of VMError
## Error signaling that the jump destination for a JUMPDEST operation is invalid.
InvalidInstruction* = object of VMError
## Error signaling that an opcode is invalid.
InsufficientFunds* = object of VMError
## Error signaling that an account has insufficient funds to transfer the
## requested value.
StackDepthLimit* = object of VMError
## Error signaling that the call stack has exceeded it's maximum allowed depth.
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.
OutOfBoundsRead* = object of VMError
## Error raised to indicate an attempt was made to read data beyond the
## boundaries of the buffer (such as with RETURNDATACOPY)
proc makeVMError*(): VMError =
result.burnsGas = true
result.erasesReturnData = true
proc makeRevert*(): Revert =
result.burnsGas = false
result.erasesReturnData = false

BIN
src/gdb.txt Normal file

Binary file not shown.

1
src/line.csv Normal file
View File

@ -0,0 +1 @@

14
src/logging.nim Normal file
View File

@ -0,0 +1,14 @@
import strformat
type
Logger* = object
name*: string
proc log*(l: Logger, msg: string) =
echo fmt"#{l.name}: {msg}"
proc trace*(l: Logger, msg: string) =
echo fmt"#{l.name}: {msg}"
proc getLogger*(name: string): Logger =
result = Logger(name: name)

1
src/nim.cfg Normal file
View File

@ -0,0 +1 @@
# --warning[XDeclaredButNotUsed]:off

135
src/opcode_values.nim Normal file
View File

@ -0,0 +1,135 @@
const
STOP* = 0
ADD* = 1
MUL* = 2
SUB* = 3
DIV* = 4
SDIV* = 5
MOD* = 6
SMOD* = 7
ADDMOD* = 8
MULMOD* = 9
EXP* = 10
SIGNEXTEND* = 11
LT* = 16
GT* = 17
SLT* = 18
SGT* = 19
EQ* = 20
ISZERO* = 21
AND* = 22
OR* = 23
XOR* = 24
NOT* = 25
BYTE* = 26
SHA3* = 32
ADDRESS* = 48
BALANCE* = 49
ORIGIN* = 50
CALLER* = 51
CALLVALUE* = 52
CALLDATALOAD* = 53
CALLDATASIZE* = 54
CALLDATACOPY* = 55
CODESIZE* = 56
CODECOPY* = 57
GASPRICE* = 58
EXTCODESIZE* = 59
EXTCODECOPY* = 60
RETURNDATASIZE* = 61
RETURNDATACOPY* = 62
BLOCKHASH* = 64
COINBASE* = 65
TIMESTAMP* = 66
NUMBER* = 67
DIFFICULTY* = 68
GASLIMIT* = 69
POP* = 80
MLOAD* = 81
MSTORE* = 82
MSTORE8 = 83
SLOAD* = 84
SSTORE* = 85
JUMP* = 86
JUMPI* = 87
PC* = 88
MSIZE* = 89
GAS* = 90
JUMPDEST* = 91
PUSH1* = 96.byte
PUSH2* = 97.byte
PUSH3* = 98.byte
PUSH4* = 99.byte
PUSH5* = 100.byte
PUSH6* = 101.byte
PUSH7* = 102.byte
PUSH8* = 103.byte
PUSH9* = 104.byte
PUSH10* = 105.byte
PUSH11* = 106.byte
PUSH12* = 107.byte
PUSH13* = 108.byte
PUSH14* = 109.byte
PUSH15* = 110.byte
PUSH16* = 111.byte
PUSH17* = 112.byte
PUSH18* = 113.byte
PUSH19* = 114.byte
PUSH20* = 115.byte
PUSH21* = 116.byte
PUSH22* = 117.byte
PUSH23* = 118.byte
PUSH24* = 119.byte
PUSH25* = 120.byte
PUSH26* = 121.byte
PUSH27* = 122.byte
PUSH28* = 123.byte
PUSH29* = 124.byte
PUSH30* = 125.byte
PUSH31* = 126.byte
PUSH32* = 127.byte
DUP1* = 128
DUP2* = 129
DUP3* = 130
DUP4* = 131
DUP5* = 132
DUP6* = 133
DUP7* = 134
DUP8* = 135
DUP9* = 136
DUP10* = 137
DUP11* = 138
DUP12* = 139
DUP13* = 140
DUP14* = 141
DUP15* = 142
DUP16* = 143
SWAP1* = 144
SWAP2* = 145
SWAP3* = 146
SWAP4* = 147
SWAP5* = 148
SWAP6* = 149
SWAP7* = 150
SWAP8* = 151
SWAP9* = 152
SWAP10* = 153
SWAP11* = 154
SWAP12* = 155
SWAP13* = 156
SWAP14* = 157
SWAP15* = 158
SWAP16* = 159
LOG0* = 160
LOG1* = 161
LOG2* = 162
LOG3* = 163
LOG4* = 164
CREATE* = 240
CALL* = 241
CALLCODE* = 242
RETURN* = 243
DELEGATECALL* = 244
STATICCALL* = 250
REVERT* = 253
SELFDESTRUCT* = 255

5
src/utils_numeric.nim Normal file
View File

@ -0,0 +1,5 @@
proc intToBigEndian*(value: int): cstring =
result = cstring""
proc bigEndianToInt*(value: cstring): int =
result = 0

18
src/validation.nim Normal file
View File

@ -0,0 +1,18 @@
import
strformat,
errors, constants
proc validateCanonicalAddress*(value: cstring, title: string = "Value") =
if len(value) != 20:
raise newException(ValidationError,
fmt"{title} {value} is not a valid canonical address")
proc validateGte*(value: Int256, minimum: int, title: string = "Value") =
if value < minimum.Int256:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")

90
src/vm/code_stream.nim Normal file
View File

@ -0,0 +1,90 @@
import
strformat, strutils, sequtils, sets,
../logging, ../constants, ../opcode_values
# I don't see why would we wrap our in memory stream in something like BytesIO
type
CodeStream* = ref object
bytes: seq[byte]
depthProcessed: int
invalidPositions: HashSet[int]
pc*: int
logger: Logger
proc `$`*(b: byte): string =
$(b.int)
proc newCodeStream*(codeBytes: cstring): CodeStream =
new(result)
result.bytes = codeBytes.mapIt(it.byte)
result.pc = 0
result.invalidPositions = initSet[int]()
result.depthProcessed = 0
result.logger = logging.getLogger("evm.vm.CodeStream")
proc read*(c: var CodeStream, size: int): seq[byte] =
result = c.bytes[c.pc .. c.pc + size - 1]
c.pc += size
proc len*(c: CodeStream): int =
len(c.bytes)
proc next*(c: var CodeStream): byte =
var nextOpcode = c.read(1)
if nextOpcode[0] != 0x0.byte:
return nextOpcode[0]
else:
return opcode_values.STOP
iterator items*(c: var CodeStream): byte =
var nextOpcode = c.next()
while nextOpcode != opcode_values.STOP:
yield nextOpcode
nextOpcode = c.next()
proc `[]`*(c: CodeStream, offset: int): byte =
c.bytes[offset]
proc peek*(c: var CodeStream): byte =
var currentPc = c.pc
result = c.next()
c.pc = currentPc
proc updatePc*(c: var CodeStream, value: int) =
c.pc = min(value, len(c))
template seek*(c: var CodeStream, pc: int, handler: untyped): untyped =
var anchorPc = pc
`c`.pc = pc
try:
var c = `c` {.inject.}
`handler`
finally:
`c`.pc = anchorPc
proc isValidOpcode*(c: var CodeStream, position: int): bool =
if position >= len(c):
return false
if position in c.invalidPositions:
return false
if position <= c.depthProcessed:
return true
else:
var i = c.depthProcessed
while i <= position:
var opcode = c[i]
if opcode >= opcode_values.PUSH1 and opcode <= opcode_values.PUSH32:
var leftBound = (i + 1)
var rightBound = leftBound + (opcode.int - 95)
for z in leftBound ..< rightBound:
c.invalidPositions.incl(z)
i = rightBound
else:
c.depthProcessed = i
i += 1
if position in c.invalidPositions:
return false
else:
return true

40
src/vm/gas_meter.nim Normal file
View File

@ -0,0 +1,40 @@
import
strformat,
../logging, ../errors, ../constants
type
GasMeter* = ref object
logger*: Logger
gasRefunded*: Int256
startGas*: Int256
gasRemaining*: Int256
proc newGasMeter*(startGas: Int256): GasMeter =
new(result)
result.startGas = startGas
result.gasRemaining = result.startGas
result.gasRefunded = 0.Int256
proc consumeGas*(gasMeter: var GasMeter; amount: Int256; reason: string) =
if amount < 0.Int256:
raise newException(ValidationError, "Gas consumption amount must be positive")
if amount > gasMeter.gasRemaining:
raise newException(OutOfGas,
%"Out of gas: Needed {amount} - Remaining {gasMeter.gasRemaining} - Reason: {reason}")
gasMeter.gasRemaining -= amount
gasMeter.logger.trace(
%"GAS CONSUMPTION: {gasMeter.gasRemaining + amount} - {amount} -> {gasMeter.gasRemaining} ({reason})")
proc returnGas*(gasMeter: var GasMeter; amount: Int256) =
if amount < 0.Int256:
raise newException(ValidationError, "Gas return amount must be positive")
gasMeter.gasRemaining += amount
gasMeter.logger.trace(
%"GAS RETURNED: {gasMeter.gasRemaining - amount} + {amount} -> {gasMeter.gasRemaining}")
proc refundGas*(gasMeter: var GasMeter; amount: Int256) =
if amount < 0.Int256:
raise newException(ValidationError, "Gas refund amount must be positive")
gasMeter.gasRefunded += amount
gasMeter.logger.trace(
%"GAS REFUND: {gasMeter.gasRemaining - amount} + {amount} -> {gasMeter.gasRefunded}")

116
src/vm/message.nim Normal file
View File

@ -0,0 +1,116 @@
import
../logging, ../constants, ../validation
type
Message* = ref object
# A message for VM computation
# depth = None
# code = None
# codeAddress = None
# createAddress = None
# shouldTransferValue = None
# isStatic = None
# logger = logging.getLogger("evm.vm.message.Message")
gas*: Int256
gasPrice*: Int256
to*: cstring
sender*: cstring
value*: Int256
data*: cstring
code*: cstring
internalOrigin: cstring
internalCodeAddress: cstring
depth*: Int256
internalStorageAddress: cstring
shouldTransferValue*: bool
isStatic*: bool
proc `origin=`*(message: var Message, value: cstring) =
message.internalOrigin = value
proc `codeAddress=`*(message: var Message, value: cstring) =
message.internalCodeAddress = value
proc `storageAddress=`*(message: var Message, value: cstring) =
message.internalStorageAddress = value
proc newMessage*(
gas: Int256,
gasPrice: Int256,
to: cstring,
sender: cstring,
value: Int256,
data: cstring,
code: cstring,
origin: cstring = nil,
depth: Int256 = 0.Int256,
createAddress: cstring = nil,
codeAddress: cstring = nil,
shouldTransferValue: bool = true,
isStatic: bool = false): Message =
new(result)
result.gas = gas
result.gasPrice = gasPrice
if to != CREATE_CONTRACT_ADDRESS:
validateCanonicalAddress(to, title="Message.to")
result.to = to
validateCanonicalAddress(sender, title="Message.sender")
result.sender = sender
result.value = value
result.data = data
if not origin.isNil:
validateCanonicalAddress(origin, title="Message.origin")
result.internalOrigin = origin
validateGte(depth, minimum=0, title="Message.depth")
result.depth = depth
result.code = code
if not createAddress.isNil:
validateCanonicalAddress(createAddress, title="Message.storage_address")
result.storageAddress = createAddress
if not codeAddress.isNil:
validateCanonicalAddress(codeAddress, title="Message.code_address")
result.codeAddress = codeAddress
result.shouldTransferValue = shouldTransferValue
result.isStatic = isStatic
proc origin*(message: Message): cstring =
if not message.internalOrigin.isNil:
message.internalOrigin
else:
message.sender
proc isOrigin*(message: Message): bool =
message.sender == message.origin
proc codeAddress*(message: Message): cstring =
if not message.internalCodeAddress.isNil:
message.internalCodeAddress
else:
message.to
proc `storageAddress`*(message: Message): cstring =
if not message.internalStorageAddress.isNil:
message.internalStorageAddress
else:
message.to
proc isCreate(message: Message): bool =
message.to == CREATE_CONTRACT_ADDRESS

123
src/vm/stack.nim Normal file
View File

@ -0,0 +1,123 @@
import
strformat,
value, ../errors, ../validation, ../utils_numeric, ../constants, ../logging
type
Stack* = ref object of RootObj
## VM Stack
logger*: Logger
values*: seq[Value]
template ensureStackLimit: untyped =
if len(stack.values) > 1023:
raise newException(FullStack, "Stack limit reached")
method len*(stack: Stack): int =
len(stack.values)
method push*(stack: var Stack; value: Value) =
## Push an item onto the stack
ensureStackLimit()
stack.values.add(value)
method push*(stack: var Stack; value: int) =
## Push an integer onto the stack
ensureStackLimit()
stack.values.add(Value(kind: VInt, i: value))
method push*(stack: var Stack; value: cstring) =
## Push a binary onto the stack
ensureStackLimit()
stack.values.add(Value(kind: VBinary, b: value))
method internalPop(stack: var Stack; numItems: int): seq[Value] =
if len(stack) < numItems:
result = @[]
else:
result = stack.values[^numItems .. ^1]
stack.values = stack.values[0 ..< ^numItems]
template toType(i: int, _: typedesc[int]): int =
i
template toType(i: int, _: typedesc[cstring]): cstring =
intToBigEndian(i)
template toType(b: cstring, _: typedesc[int]): int =
bigEndianToInt(b)
template toType(b: cstring, _: typedesc[cstring]): cstring =
b
method internalPop(stack: var Stack; numItems: int, T: typedesc): seq[T] =
result = @[]
if len(stack) < numItems:
return
for z in 0 ..< numItems:
var value = stack.values.pop()
case value.kind:
of VInt:
result.add(toType(value.i, T))
of VBinary:
result.add(toType(value.b, T))
template ensurePop(elements: untyped, a: untyped): untyped =
if len(`elements`) < `a`:
raise newException(InsufficientStack, "No stack items")
method pop*(stack: var Stack): Value =
## Pop an item off the stack
var elements = stack.internalPop(1)
ensurePop(elements, 1)
result = elements[0]
method pop*(stack: var Stack; numItems: int): seq[Value] =
## Pop many items off the stack
result = stack.internalPop(numItems)
ensurePop(result, numItems)
method popInt*(stack: var Stack): int =
var elements = stack.internalPop(1, int)
ensurePop(elements, 1)
result = elements[0]
method popInt*(stack: var Stack; numItems: int): seq[int] =
result = stack.internalPop(numItems, int)
ensurePop(result, numItems)
method popBinary*(stack: var Stack): cstring =
var elements = stack.internalPop(1, cstring)
ensurePop(elements, 1)
result = elements[0]
method popBinary*(stack: var Stack; numItems: int): seq[cstring] =
result = stack.internalPop(numItems, cstring)
ensurePop(result, numItems)
proc makeStack*(): Stack =
# result.logger = logging.getLogger("evm.vm.stack.Stack")
result.values = @[]
method swap*(stack: var Stack; position: int) =
## Perform a SWAP operation on the stack
var idx = position + 1
if idx < len(stack) + 1:
(stack.values[^1], stack.values[^idx]) = (stack.values[^idx], stack.values[^1])
else:
raise newException(InsufficientStack,
%"Insufficient stack items for SWAP{position}")
method dup*(stack: var Stack; position: int) =
## Perform a DUP operation on the stack
if position < len(stack) + 1:
stack.push(stack.values[^position])
else:
raise newException(InsufficientStack,
%"Insufficient stack items for DUP{position}")

10
src/vm/value.nim Normal file
View File

@ -0,0 +1,10 @@
type
ValueKind* = enum VInt, VBinary
Value* = ref object
case kind*: ValueKind:
of VInt:
i*: int
of VBinary:
b*: cstring