git ssb

0+

wanderer🌟 / js-primea-hypervisor



Commit 3c4a2d1f6298f7d8b4e012acbb8edd52f1bf5203

Merge pull request #64 from ewasm/refractor_async_testing

Implements the Async Interface 
wanderer authored on 12/5/2016, 7:29:55 PM
GitHub committed on 12/5/2016, 7:29:55 PM
Parent: c8eec344b7def6dcff0d4682d181e6324d80b6b8
Parent: 7ac6686f9ca7efa10273c565fc2e749c25a4604e

Files changed

debugInterface.jschanged
environment.jschanged
fakeBlockChain.jschanged
index.jschanged
opcodes.jschanged
package.jsonchanged
testEnvironment.jschanged
tests/interface/address.jsonchanged
tests/interface/address.wastchanged
tests/interface/balance.jsonchanged
tests/interface/balance.wastchanged
tests/interface/basic_gas_ops.jsonchanged
tests/interface/call.jsonchanged
tests/interface/call.wastchanged
tests/interface/callDataCopy.jsonchanged
tests/interface/callDataCopy.wastchanged
tests/interface/callDataSize.jsonchanged
tests/interface/callDataSize.wastchanged
tests/interface/callValue.jsonchanged
tests/interface/callValue.wastchanged
tests/interface/caller.jsonchanged
tests/interface/caller.wastchanged
tests/interface/coinbase.jsonchanged
tests/interface/coinbase.wastchanged
tests/interface/origin.jsonchanged
tests/interface/origin.wastchanged
tests/interface/sstore.jsonchanged
tests/interface/sstore.wastchanged
tests/interface/address.wasmadded
tests/interface/balance.wasmadded
tests/interface/basic_gas_ops.wasmadded
tests/interface/call.wasmadded
tests/interface/callDataCopy.wasmadded
tests/interface/callDataSize.wasmadded
tests/interface/callValue.wasmadded
tests/interface/caller.wasmadded
tests/interface/coinbase.wasmadded
tests/interface/origin.wasmadded
tests/interface/sstore.wasmadded
tests/interfaceRunner.jschanged
tests/buildTests.jsadded
EVMimports.jsadded
address.jsdeleted
block.jsdeleted
design.mddeleted
deps/address.jsadded
deps/block.jsadded
deps/rootVertex.jsadded
deps/transaction.jsadded
deps/u256.jsadded
deps/utils.jsadded
docs/design.mdadded
examples/test.jsadded
interface.jsdeleted
precompile.jsdeleted
test.jsdeleted
precompiles/create.jsadded
precompiles/precompile.jsadded
runBlock.jsadded
runTx.jsadded
transaction.jsdeleted
u256.jsdeleted
utils.jsdeleted
vm.jsadded
debugInterface.jsView
@@ -5,17 +5,17 @@
55 * This expose some functions that can help with debugging wast
66 */
77
88 module.exports = class DebugInterface {
9- constructor (environment) {
10- this.environment = environment
9+ constructor (kernel) {
10+ this.kernel = kernel
1111 }
1212
13- setModule (mod) {
14- this.module = mod
13+ static get name () {
14+ return 'debug'
1515 }
1616
17- get exportTable () {
17+ get exports () {
1818 return {
1919 'print': function (a) {
2020 console.log(a)
2121 },
@@ -31,17 +31,18 @@
3131 const opcode = opcodes(op)
3232 if (opcode.number) {
3333 opcode.name += opcode.number
3434 }
35- console.error(`op: ${opcode.name} gas: ${this.environment.gasLeft}`)
35+ console.error(`op: ${opcode.name} gas: ${this.kernel.environment.gasLeft} sp: ${sp}`)
3636 console.log('-------------stack--------------')
3737 for (let i = sp; i >= 0; i -= 32) {
38- console.log(`${(sp - i) / 32} ${this.getMemoryBuffer(i).reverse().toString('hex')}`)
38+ console.log(`${(sp - i) / 32} ${this.getMemoryBuffer(i).toString('hex')}`)
3939 }
4040 }.bind(this)
4141 }
4242 }
4343
44- getMemoryBuffer (offset) {
45- return new Buffer(this.module.exports.memory.slice(offset, offset + 32))
44+ getMemoryBuffer (offset, length = 32) {
45+ const mem = this.kernel.memory.slice(offset, offset + length)
46+ return Buffer.from(mem).reverse()
4647 }
4748 }
environment.jsView
@@ -1,11 +1,14 @@
1-const U256 = require('./u256.js')
2-const Address = require('./address.js')
3-const Block = require('./block.js')
1+const Vertex = require('merkle-trie')
2+const Store = require('merkle-trie/store')
3+const U256 = require('./deps/u256.js')
4+const Address = require('./deps/address.js')
5+const Block = require('./deps/block.js')
6+// TODO remove fakeblockchain
47 const fakeBlockChain = require('./fakeBlockChain.js')
58
69 module.exports = class Environment {
7- constructor (data) {
10+ constructor (data = {}) {
811 const defaults = {
912 block: new Block(),
1013 blockchain: fakeBlockChain,
1114 // gas tank
@@ -24,54 +27,44 @@
2427 logs: [],
2528 selfDestruct: false,
2629 selfDestructAddress: new Address('0x0000000000000000000000000000000000000000'),
2730 // more output calls
28- returnValue: new Uint8Array()
31+ returnValue: new Uint8Array(),
32+ state: new Vertex({store: new Store()})
2933 }
30-
31- this.state = new Map()
32-
33- Object.assign(this, defaults, data || {})
34+ Object.assign(this, defaults, data)
3435 }
3536
36- addAccount (address, trie) {
37- let account = new Map()
38- account.set('nonce', trie.nonce || new U256(0))
39- account.set('balance', trie.balance || new U256(0))
40- account.set('code', trie.code || new Uint8Array())
41- account.set('storage', trie.storage || new Map())
42- this.parent.state.set(address.toString(), account)
43- }
44-
4537 isAccountPresent (address) {
46- const account = this.state.get(address.toString())
47- if (account) {
48- return true
49- } else {
50- return false
51- }
38+ // const account = this.state.get(address.toString())
39+ // if (account) {
40+ // return true
41+ // } else {
42+ // return false
43+ // }
5244 }
5345
5446 getBalance (address) {
55- const account = this.parent.state.get(address.toString())
56- if (account) {
57- return account.get('balance')
58- } else {
59- return new U256()
60- }
47+ // const account = this.state.get(address.toString())
48+ // if (account) {
49+ // return account.get('balance')
50+ // } else {
51+ // return new U256()
52+ // }
6153 }
6254
6355 getCode (address) {
64- const account = this.parent.state.get(address.toString())
65- if (account) {
66- return account.get('code')
67- } else {
68- return Uint8Array.from(new Buffer([]))
69- }
56+ // const account = this.state.get(address.toString())
57+ // if (account) {
58+ // return account.get('code')
59+ // } else {
60+ // return Uint8Array.from(new Buffer([]))
61+ // }
7062 }
7163
72- getBlockHash (height) {
73- return this.blockchain.getBlock(height).hash()
64+ async getBlockHash (height) {
65+ const block = await this.blockchain.getBlock(height)
66+ return block.hash()
7467 }
7568
7669 set createHandler (value) {
7770 this.createhandler = value
@@ -88,9 +81,9 @@
8881 }
8982
9083 call (gas, address, value, data) {
9184 // FIXME: create a child environment here
92- const ret = this.callhandler({
85+ const ret = this.root.messagehandler({
9386 from: this.address,
9487 to: address,
9588 gasLimit: gas,
9689 value: value,
fakeBlockChain.jsView
@@ -1,12 +1,13 @@
11 const utils = require('ethereumjs-util')
2+const U256 = require('./deps/u256.js')
23
34 module.exports = {
45 getBlock: (n) => {
56 const hash = utils.sha3(new Buffer(utils.bufferToInt(n).toString()))
67 const block = {
78 hash: () => {
8- return hash
9+ return new U256(hash)
910 }
1011 }
1112 return block
1213 }
index.jsView
@@ -1,288 +1,63 @@
1-/**
2- * This implements the Ethereum Kernel
3- * Kernels must implement two methods `codeHandler` and `callHandler` (and `linkHandler` for sharding)
4- * The Kernel Contract handles the following
5- * - Interprocess communications
6- * - Intializing the VM and exposes ROM to it (codeHandler)
7- * - Expose namespace which VM instance exists and Intializes the Environment (callHandler)
8- * - Provides some built in contract (runTx, runBlock)
9- * - Provides resource sharing and limiting via gas
10- *
11- * All State should be stored in the Environment.
12- *
13- */
14-
1+const Vertex = require('merkle-trie')
152 // The Kernel Exposes this Interface to VM instances it makes
16-const Interface = require('./interface.js')
17-
18-// The Kernel Stores all of its state in the Environment. The Interface is used
19-// to by the VM to retrive infromation from the Environment.
3+const Imports = require('./EVMimports.js')
4+const VM = require('./vm.js')
205 const Environment = require('./environment.js')
21-const DebugInterface = require('./debugInterface.js')
22-const Address = require('./address.js')
23-const U256 = require('./u256.js')
24-const Utils = require('./utils.js')
25-const Transaction = require('./transaction.js')
26-const Precompile = require('./precompile.js')
276
28-const identityContract = new Address('0x0000000000000000000000000000000000000004')
29-const meteringContract = new Address('0x000000000000000000000000000000000000000A')
30-const transcompilerContract = new Address('0x000000000000000000000000000000000000000B')
7+module.exports = class Kernel extends Vertex {
8+ constructor (opts = {}) {
9+ opts.code = opts.value || opts.code
10+ super(opts)
3111
32-module.exports = class Kernel {
33- // runs some code in the VM
34- constructor (environment = new Environment()) {
35- this.environment = environment
36-
37- this.environment.addAccount(identityContract, {})
38- this.environment.addAccount(meteringContract, {})
39- this.environment.addAccount(transcompilerContract, {})
40- }
41-
42- // handles running code.
43- // NOTE: it assumes that wasm will raise an exception if something went wrong,
44- // otherwise execution succeeded
45- codeHandler (code, ethInterface = new Interface(new Environment())) {
46- const debugInterface = new DebugInterface(ethInterface.environment)
47- const module = WebAssembly.Module(code)
48- const imports = {
49- 'ethereum': ethInterface.exportTable,
50- 'debug': debugInterface.exportTable,
51-
52- // export this for Rust
53- // FIXME: remove once Rust has proper imports, see https://github.com/ethereum/evm2.0-design/issues/15
54- 'spectest': ethInterface.exportTable,
55-
56- // export this for Binaryen
57- // FIXME: remove once C has proper imports, see https://github.com/ethereum/evm2.0-design/issues/16
58- 'env': ethInterface.exportTable
12+ // if code is bound to this kernel then create the interfaceAPI and the imports
13+ if (opts.code) {
14+ this._vm = new VM(opts.code)
15+ this.imports = buildImports(this._vm, opts.interfaces)
5916 }
60- // add shims
61- imports.ethereum.useGas = ethInterface.shims.exports.useGas
62- imports.ethereum.getGasLeft = ethInterface.shims.exports.getGasLeft
63- imports.ethereum.call = ethInterface.shims.exports.call
6417
65- const instance = WebAssembly.Instance(module, imports)
66-
67- ethInterface.setModule(instance)
68- debugInterface.setModule(instance)
69-
70- if (instance.exports.main) {
71- instance.exports.main()
18+ /**
19+ * Builds a import map with an array of given interfaces
20+ */
21+ function buildImports (api, imports = [Imports]) {
22+ return imports.reduce((obj, InterfaceConstuctor) => {
23+ obj[InterfaceConstuctor.name] = new InterfaceConstuctor(api).exports
24+ return obj
25+ }, {})
7226 }
73- return instance
7427 }
7528
76- // loads code from the merkle trie and delegates the message
77- // Detects if code is EVM or WASM
78- // Detects if the code injection is needed
79- // Detects if transcompilation is needed
80- callHandler (call) {
81- // FIXME: this is here until these two contracts are compiled to WASM
82- // The two special contracts (precompiles now, but will be real ones later)
83- if (call.to.equals(meteringContract)) {
84- return Precompile.meteringInjector(call)
85- } else if (call.to.equals(transcompilerContract)) {
86- return Precompile.transcompiler(call)
87- } else if (call.to.equals(identityContract)) {
88- return Precompile.identity(call)
89- }
90-
91- let account = this.environment.state.get(call.to.toString())
92- if (!account) {
93- throw new Error('Account not found: ' + call.to.toString())
94- }
95-
96- let code = Uint8Array.from(account.get('code'))
97- if (code.length === 0) {
98- throw new Error('Contract not found')
99- }
100-
101- if (!Utils.isWASMCode(code)) {
102- // throw new Error('Not an eWASM contract')
103-
104- // Transcompile code
105- // FIXME: decide if these are the right values here: from: 0, gasLimit: 0, value: 0
106- code = this.callHandler({ from: Address.zero(), to: transcompilerContract, gasLimit: 0, value: new U256(0), data: code }).returnValue
107-
108- if (code[0] === 0) {
109- code = code.slice(1)
110- } else {
111- throw new Error('Transcompilation failed: ' + Buffer.from(code).slice(1).toString())
112- }
113- }
114-
115- // creats a new Kernel
116- const environment = new Environment()
117- environment.parent = this
118-
119- // copy the transaction details
120- environment.code = code
121- environment.address = call.to
122- // FIXME: make distinction between origin and caller
123- environment.origin = call.from
124- environment.caller = call.from
125- environment.callData = call.data
126- environment.callValue = call.value
127- environment.gasLeft = call.gasLimit
128-
129- environment.callHandler = this.callHandler.bind(this)
130- environment.createHandler = this.createHandler.bind(this)
131-
132- const kernel = new Kernel(environment)
133- kernel.codeHandler(code, new Interface(environment))
134-
135- // self destructed
136- if (environment.selfDestruct) {
137- const balance = this.state.get(call.to.toString()).get('balance')
138- const beneficiary = this.state.get(environment.selfDestructAddress)
139- beneficiary.set('balance', beneficiary.get('balance').add(balance))
140- this.state.delete(call.to.toString())
141- }
142-
143- // generate new stateroot
144- // this.environment.state.set(address, { stateRoot: stateRoot })
145-
146- return {
147- executionOutcome: 1, // success
148- gasLeft: new U256(environment.gasLeft),
149- gasRefund: new U256(environment.gasRefund),
150- returnValue: environment.returnValue,
151- selfDestruct: environment.selfDestruct,
152- selfDestructAddress: environment.selfDestructAddress,
153- logs: environment.logs
154- }
29+ /**
30+ * run the kernels code with a given enviroment
31+ * The Kernel Stores all of its state in the Environment. The Interface is used
32+ * to by the VM to retrive infromation from the Environment.
33+ */
34+ async run (environment = new Environment({state: this}), imports = this.imports) {
35+ await this._vm.run(environment, imports)
15536 }
15637
157- createHandler (create) {
158- let code = create.data
159-
160- // Inject metering
161- if (Utils.isWASMCode(code)) {
162- // FIXME: decide if these are the right values here: from: 0, gasLimit: 0, value: 0
163- code = this.callHandler({ from: Address.zero(), to: meteringContract, gasLimit: 0, value: new U256(0), data: code }).returnValue
164-
165- if (code[0] === 0) {
166- code = code.slice(1)
167- } else {
168- throw new Error('Metering injection failed: ' + Buffer.from(code).slice(1).toString())
38+ async messageReceiver (message) {
39+ // let the code handle the message if there is code
40+ if (this.code) {
41+ const environment = new Environment(message)
42+ let result = await this.run(environment)
43+ if (!result.execption) {
44+ this.state = result.state
16945 }
46+ } else if (message.to.length) {
47+ // else forward the message on to the destination contract
48+ let [vertex, done] = await this.state.update(message.to)
49+ message.to = []
50+ await vertex.kernel.messageReceiver(message)
51+ done(vertex)
17052 }
171-
172- let account = this.environment.state.get(create.from.toString())
173- if (!account) {
174- throw new Error('Account not found: ' + create.from.toString())
175- }
176-
177- let address = Utils.newAccountAddress(create.from, account.get('nonce'))
178-
179- this.environment.addAccount(address.toString(), {
180- balance: create.value,
181- code: code
182- })
183-
184- // Run code and take return value as contract code
185- // FIXME: decide if these are the right values here: value: 0, data: ''
186- code = this.callHandler({ from: create.from, to: address, gasLimit: create.gasLimit, value: new U256(0), data: new Uint8Array() }).returnValue
187-
188- // FIXME: special handling for selfdestruct
189-
190- this.environment.state.get(address.toString()).set('code', code)
191-
192- return {
193- executionOutcome: 1, // success
194- gasLeft: new U256(this.environment.gasLeft),
195- gasRefund: new U256(this.environment.gasRefund),
196- accountCreated: address,
197- logs: this.environment.logs
198- }
19953 }
20054
201- // run tx; the tx message handler
202- runTx (tx, environment = new Environment()) {
203- this.environment = environment
204-
205- if (Buffer.isBuffer(tx) || typeof tx === 'string') {
206- tx = new Transaction(tx)
207- if (!tx.valid) {
208- throw new Error('Invalid transaction signature')
209- }
210- }
211-
212- // look up sender
213- let fromAccount = this.environment.state.get(tx.from.toString())
214- if (!fromAccount) {
215- throw new Error('Sender account not found: ' + tx.from.toString())
216- }
217-
218- if (fromAccount.get('nonce').gt(tx.nonce)) {
219- throw new Error(`Invalid nonce: ${fromAccount.get('nonce')} > ${tx.nonce}`)
220- }
221-
222- fromAccount.set('nonce', fromAccount.get('nonce').add(new U256(1)))
223-
224- let isCreation = false
225-
226- // Special case: contract deployment
227- if (tx.to.isZero() && (tx.data.length !== 0)) {
228- console.log('This is a contract deployment transaction')
229- isCreation = true
230- }
231-
232- // This cost will not be refunded
233- let txCost = 21000 + (isCreation ? 32000 : 0)
234- tx.data.forEach((item) => {
235- if (item === 0) {
236- txCost += 4
237- } else {
238- txCost += 68
239- }
55+ copy () {
56+ return new Kernel({
57+ state: this.state.copy(),
58+ code: this.code,
59+ interfaces: this.interfaces,
60+ parent: this.parent
24061 })
241-
242- if (tx.gasLimit.lt(new U256(txCost))) {
243- throw new Error(`Minimum transaction gas limit not met: ${txCost}`)
244- }
245-
246- if (fromAccount.get('balance').lt(tx.gasLimit.mul(tx.gasPrice))) {
247- throw new Error(`Insufficient account balance: ${fromAccount.get('balance').toString()} < ${tx.gasLimit.mul(tx.gasPrice).toString()}`)
248- }
249-
250- // deduct gasLimit * gasPrice from sender
251- fromAccount.set('balance', fromAccount.get('balance').sub(tx.gasLimit.mul(tx.gasPrice)))
252-
253- const handler = isCreation ? this.createHandler.bind(this) : this.callHandler.bind(this)
254- let ret = handler({
255- to: tx.to,
256- from: tx.from,
257- gasLimit: tx.gasLimit - txCost,
258- value: tx.value,
259- data: tx.data
260- })
261-
262- // refund unused gas
263- if (ret.executionOutcome === 1) {
264- fromAccount.set('balance', fromAccount.get('balance').add(tx.gasPrice.mul(ret.gasLeft.add(ret.gasRefund))))
265- }
266-
267- // save new state?
268-
269- return {
270- executionOutcome: ret.executionOutcome,
271- accountCreated: isCreation ? ret.accountCreated : undefined,
272- returnValue: isCreation ? undefined : ret.returnValue,
273- gasLeft: ret.gasLeft,
274- logs: ret.logs
275- }
27662 }
277-
278- // run block; the block message handler
279- runBlock (block, environment = new Environment()) {
280- // verify block then run each tx
281- block.tx.forEach((tx) => {
282- this.runTx(tx, environment)
283- })
284- }
285-
286- // run blockchain
287- // runBlockchain () {}
28863 }
opcodes.jsView
@@ -57,10 +57,10 @@
5757 0x50: ['POP', 2, 1, 0, false],
5858 0x51: ['MLOAD', 3, 1, 1, false],
5959 0x52: ['MSTORE', 3, 2, 0, false],
6060 0x53: ['MSTORE8', 3, 2, 0, false],
61- 0x54: ['SLOAD', 50, 1, 1, true],
62- 0x55: ['SSTORE', 5000, 2, 0, true],
61+ 0x54: ['SLOAD', 0, 1, 1, true],
62+ 0x55: ['SSTORE', 0, 2, 0, true],
6363 0x56: ['JUMP', 8, 1, 0, false],
6464 0x57: ['JUMPI', 10, 2, 0, false],
6565 0x58: ['PC', 2, 0, 1, false],
6666 0x59: ['MSIZE', 2, 0, 1, false],
package.jsonView
@@ -3,9 +3,10 @@
33 "version": "0.0.0",
44 "description": "This is a JS prototype of the eWASM kernal.",
55 "scripts": {
66 "lint": "standard",
7- "test": "node --expose-wasm ./tests/interfaceRunner.js"
7+ "test": "node --harmony --expose-wasm ./tests/interfaceRunner.js",
8+ "build": "node ./tests/buildTests.js"
89 },
910 "repository": {
1011 "type": "git",
1112 "url": "git+https://github.com/ewasm/ewasm-kernel.git"
@@ -23,9 +24,9 @@
2324 "author": "mjbecze <mjbecze@gmail.com>",
2425 "contributors": "Alex Beregszaszi <alex@rtfs.hu>",
2526 "license": "MPL-2.0",
2627 "devDependencies": {
27- "standard": "^8.5.0",
28+ "standard": "*",
2829 "tape": "^4.5.1"
2930 },
3031 "standard": {
3132 "ignore": [
@@ -38,7 +39,8 @@
3839 "dependencies": {
3940 "bn.js": "^4.11.6",
4041 "ethereumjs-block": "^1.2.2",
4142 "ethereumjs-tx": "^1.1.2",
43+ "merkle-trie": "0.0.0",
4244 "ethereumjs-util": "^5.0.0"
4345 }
4446 }
testEnvironment.jsView
@@ -1,82 +1,8 @@
11 const Environment = require('./environment.js')
2-const U256 = require('./u256.js')
3-const Address = require('./address.js')
4-const Block = require('./block.js')
5-const ethUtil = require('ethereumjs-util')
2+const fakeBlockchain = require('./fakeBlockChain')
63
74 module.exports = class TestEnvironment extends Environment {
8- constructor (data) {
9- super()
10-
11- if (typeof data === 'string') {
12- data = JSON.parse(data)
13- }
14-
15- let self = this
16-
17- if (data.accounts) {
18- data.accounts.forEach((account) => {
19- let tmp = account[1]
20- self.addAccount(new Address(account[0]), {
21- balance: new U256(tmp.balance)
22- })
23- })
24- }
25-
26- if (data.address) {
27- self.address = new Address(data.address)
28- }
29-
30- if (data.origin) {
31- self.origin = new Address(data.origin)
32- }
33-
34- if (data.caller) {
35- self.caller = new Address(data.caller)
36- }
37-
38- if (data.callValue) {
39- self.callValue = new U256(data.callValue)
40- }
41-
42- if (data.callData) {
43- self.callData = Uint8Array.from(new Buffer(data.callData, 'hex'))
44- }
45-
46- if (data.gasPrice) {
47- self.gasPrice = data.gasPrice
48- }
49-
50- if (data.gasLeft) {
51- self.gasLeft = data.gasLeft
52- }
53-
54- if (data.block) {
55- let block = {}
56-
57- if (data.block.blockNumber) {
58- block.number = ethUtil.toBuffer(data.block.blockNumber)
59- }
60-
61- if (data.block.gasLimit) {
62- block.gasLimit = ethUtil.toBuffer(data.block.gasLimit)
63- }
64-
65- if (data.block.difficulty) {
66- block.difficulty = ethUtil.toBuffer(data.block.difficulty)
67- }
68-
69- if (data.block.timestamp) {
70- block.timestamp = ethUtil.toBuffer(data.block.timestam)
71- }
72-
73- if (data.block.coinbase) {
74- block.coinbase = ethUtil.toBuffer(data.block.coinbase)
75- }
76-
77- if (Object.keys(block).length > 0) {
78- self.block = new Block({ header: block, transactions: [], uncleHeaders: [] })
79- }
80- }
5+ async getBlockHash (height) {
6+ return fakeBlockchain.getBlock(height).hash()
817 }
828 }
tests/interface/address.jsonView
@@ -1,3 +1,13 @@
11 {
2+ "callValue": "0x00",
3+ "callData": "0x00",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
212 "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
313 }
tests/interface/address.wastView
@@ -2,15 +2,15 @@
22 (module
33 (memory 1)
44
55 (import $address "ethereum" "getAddress" (param i32))
6- (export "test" 0)
6+ (export "main" 0)
77 (export "a" memory)
88 (func
99 (block
1010 ;; loads the address into memory
1111 (call_import $address (i32.const 0))
12- (if (i64.eq (i64.load (i32.const 0)) (i64.const 0xbd9c6f4a2d06c47b))
12+ (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x72a1048901c1485d))
1313 (return)
1414 )
1515 (unreachable)
1616 )
tests/interface/balance.jsonView
@@ -1,9 +1,13 @@
11 {
2- "accounts": [
3- [
4- "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", {
5- "balance": "0x056bc75e2d63100000"
2+ "callValue": "0x00",
3+ "callData": "0x00",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
69 }
7- ]
8- ]
10+ },
11+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
913 }
tests/interface/balance.wastView
@@ -1,17 +1,19 @@
11 ;; address of 5d48c1018904a172886829bbbd9c6f4a2d06c47b has a balance of 0x056bc75e2d63100000 (100 ETH)
22 (module
3- (memory 1 (segment 0 "\7b\c4\06\2d\4a\6f\9c\bd\bb\29\68\88\72\a1\04\89\01\c1\48\5d"))
4- (import $balance "ethereum" "getBalance" (param i32 i32))
3+ (memory 1 (segment 0 "\5d\48\c1\01\89\04\a1\72\88\68\29\bb\bd\9c\6f\4a\2d\06\c4\7b"))
4+ (import $balance "ethereum" "getBalance" (param i32 i32 i32))
55 (export "a" memory)
6- (export "test" 0)
6+ (export "main" 0)
77 (func
8+ (call_import $balance (i32.const 0) (i32.const 0) (i32.const 1))
9+ )
10+
11+ (export "1" 1)
12+ (func
813 (block
9- (call_import $balance (i32.const 0) (i32.const 0))
10- (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x6bc75e2d63100000))
11- (if (i64.eq (i64.load (i32.const 8)) (i64.const 0x05))
12- (return)
13- )
14+ (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x0500000000000000))
15+ (return)
1416 )
1517 (unreachable)
1618 )
1719 )
tests/interface/basic_gas_ops.jsonView
@@ -1,3 +1,14 @@
11 {
2+ "callValue": "0x00",
3+ "callData": "0x00",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
213 "gasLeft": 1000
314 }
tests/interface/call.jsonView
@@ -1,3 +1,13 @@
11 {
2+ "callValue": "0x00",
3+ "callData": "0x00",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
212 "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
313 }
tests/interface/call.wastView
@@ -1,11 +1,11 @@
11 ;; starts with an address of 5d48c1018904a172886829bbbd9c6f4a2d06c47b
22 (module
33 (memory 1)
44
5- (import $call "ethereum" "call" (param i32 i32 i32 i32 i32 i32 i32) (result i32))
6- (export "test" 0)
5+ (import $call "ethereum" "call" (param i32 i32 i32 i32 i32 i32 i32 i32) (result i32))
76 (export "a" memory)
7+ (export "main" 0)
88 (func
99 (block
1010 ;; Memory layout:
1111 ;; 0 - 20 bytes: address (4)
@@ -13,13 +13,16 @@
1313 ;; 52 - 56 bytes: data (0x42004200)
1414 ;; 56 - 60 bytes: result
1515 (i32.store (i32.const 0) (i32.const 0x4))
1616 (i32.store (i32.const 52) (i32.const 0x42004200))
17- (if (i32.eq (call_import $call (i32.const 2000) (i32.const 0) (i32.const 20) (i32.const 52) (i32.const 4) (i32.const 56) (i32.const 4)) (i32.const 0))
18- (if (i32.eq (i32.load (i32.const 56)) (i32.const 0x42004200))
19- (return)
20- )
21- )
22- (unreachable)
17+ (call_import $call (i32.const 2000) (i32.const 0) (i32.const 20) (i32.const 52) (i32.const 4) (i32.const 56) (i32.const 4) (i32.const 1))
2318 )
2419 )
20+
21+ (export "1" 1)
22+ (func (param $result i32)
23+ (if (i32.eq (i32.const 1) (get_local $result))
24+ (return)
25+ )
26+ (unreachable)
27+ )
2528 )
tests/interface/callDataCopy.jsonView
@@ -1,3 +1,13 @@
11 {
2- "callData": "596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721"
2+ "callValue": "0x00",
3+ "callData": "0x596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
313 }
tests/interface/callDataCopy.wastView
@@ -3,13 +3,12 @@
33 (memory 1)
44 (import $callDataCopy "ethereum" "callDataCopy" (param i32 i32 i32))
55
66 (export "memory" memory)
7- (export "test" 0)
7+ (export "main" 0)
88 (func
99 (block
1010 (call_import $callDataCopy (i32.const 0) (i32.const 0) (i32.const 8))
11-
1211 (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x2065726120756f59))
1312 (return)
1413 )
1514 (unreachable)
tests/interface/callDataSize.jsonView
@@ -1,3 +1,13 @@
11 {
2- "callData": "596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721"
2+ "callValue": "0x00",
3+ "callData": "0x596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
313 }
tests/interface/callDataSize.wastView
@@ -1,9 +1,9 @@
11 (module
22 (memory 1)
33 (import $callDataSize "ethereum" "getCallDataSize" (result i64))
44
5- (export "test" 0)
5+ (export "main" 0)
66 (func
77 (block
88 (if (i64.eq (call_import $callDataSize) (i64.const 277))
99 (return)
tests/interface/callValue.jsonView
@@ -1,3 +1,13 @@
11 {
2- "callValue": "0x056bc75e2d63100000"
2+ "callValue": "0x056bc75e2d63100000",
3+ "callData": "0x596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
313 }
tests/interface/callValue.wastView
@@ -3,16 +3,14 @@
33 (memory 1)
44 (import $callValue "ethereum" "getCallValue" (param i32))
55
66 (export "a" memory)
7- (export "test" 0)
7+ (export "main" 0)
88 (func
99 (block
1010 (call_import $callValue (i32.const 0))
11- (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x6bc75e2d63100000))
12- (if (i64.eq (i64.load (i32.const 8)) (i64.const 0x05))
13- (return)
14- )
11+ (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x0500000000000000))
12+ (return)
1513 )
1614 (unreachable)
1715 )
1816 )
tests/interface/caller.jsonView
@@ -1,3 +1,14 @@
11 {
2- "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
2+ "callValue": "0x00",
3+ "callData": "0x00",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
13+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
314 }
tests/interface/caller.wastView
@@ -2,15 +2,15 @@
22 (module
33 (memory 1)
44 (import $caller "ethereum" "getCaller" (param i32))
55
6- (export "test" 0)
6+ (export "main" 0)
77 (export "a" memory)
88 (func
99 (block
1010 ;; loads the caller into memory
1111 (call_import $caller (i32.const 0))
12- (if (i64.eq (i64.load (i32.const 0)) (i64.const 0xbd9c6f4a2d06c47b))
12+ (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x72a1048901c1485d))
1313 (return)
1414 )
1515 (unreachable)
1616 )
tests/interface/coinbase.jsonView
@@ -1,5 +1,13 @@
11 {
2- "block": {
3- "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
4- }
2+ "callValue": "0x00",
3+ "callData": "0x00",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
513 }
tests/interface/coinbase.wastView
@@ -2,15 +2,15 @@
22 (module
33 (memory 1)
44
55 (import $coinbase "ethereum" "getBlockCoinbase" (param i32))
6- (export "test" 0)
6+ (export "main" 0)
77 (export "a" memory)
88 (func
99 (block
1010 ;; loads the coinbase into memory
1111 (call_import $coinbase (i32.const 0))
12- (if (i64.eq (i64.load (i32.const 0)) (i64.const 0xbd9c6f4a2d06c47b))
12+ (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x72a1048901c1485d))
1313 (return)
1414 )
1515 (unreachable)
1616 )
tests/interface/origin.jsonView
@@ -1,3 +1,14 @@
11 {
2- "origin": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
2+ "callValue": "0x00",
3+ "callData": "0x00",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "origin": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
13+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
314 }
tests/interface/origin.wastView
@@ -2,15 +2,15 @@
22 (module
33 (memory 1)
44 (import $origin "ethereum" "getTxOrigin" (param i32))
55
6- (export "test" 0)
6+ (export "main" 0)
77 (export "a" memory)
88 (func
99 (block
1010 ;; loads the address into memory
1111 (call_import $origin (i32.const 0))
12- (if (i64.eq (i64.load (i32.const 0)) (i64.const 0xbd9c6f4a2d06c47b))
12+ (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x72a1048901c1485d))
1313 (return)
1414 )
1515 (unreachable)
1616 )
tests/interface/sstore.jsonView
@@ -1,2 +1,14 @@
11 {
2+ "callValue": "0x00",
3+ "callData": "0x00",
4+ "state": {
5+ "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
6+ "balance": "0x056bc75e2d63100000",
7+ "code": "0x00",
8+ "nonce": "0x00"
9+ }
10+ },
11+ "origin": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
13+ "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
214 }
tests/interface/sstore.wastView
@@ -1,28 +1,33 @@
11 ;; starts with an caller of 5d48c1018904a172886829bbbd9c6f4a2d06c47b
22 (module
33 (memory 1)
4- (import $sstore "ethereum" "storageStore" (param i32 i32))
5- (import $sload "ethereum" "storageLoad" (param i32 i32))
4+ (import $sstore "ethereum" "storageStore" (param i32 i32 i32))
5+ (import $sload "ethereum" "storageLoad" (param i32 i32 i32))
66
7- (export "test" 0)
7+ (export "main" 0)
88 (export "a" memory)
99 (func
1010 (local $temp i64)
1111 (block
1212 ;; should roundtrip store and load a value from storage
1313 (i64.store (i32.const 0) (i64.const 173553719826446289))
14- (call_import $sstore (i32.const 64) (i32.const 0))
15- (call_import $sload (i32.const 64) (i32.const 64))
14+ (call_import $sstore (i32.const 64) (i32.const 0) (i32.const 1))
15+ )
16+ )
1617
18+ (export "1" 1)
19+ (func
20+ (block
21+ (call_import $sload (i32.const 64) (i32.const 64) (i32.const 2))
22+ )
23+ )
24+
25+ (export "2" 2)
26+ (func
27+ (block
1728 (if (i64.ne (i64.load (i32.const 64)) (i64.const 173553719826446289))
1829 (unreachable))
1930
20- (i64.store (i32.const 128) (i64.const 173553719826446289))
21- (call_import $sstore (i32.const 64) (i32.const 128))
22- (i64.store (i32.const 128) (i64.const 173559826446289))
23- (call_import $sload (i32.const 64) (i32.const 64))
24- (if (i64.ne (i64.load (i32.const 64)) (i64.const 173553719826446289))
25- (unreachable))
2631 )
2732 )
2833 )
tests/interface/address.wasmView
@@ -1,0 +1,3 @@
1+asm type@@importethereum
2+getAddressfunctionmemoryexportmaincode +ݐ�������h 
3+
tests/interface/balance.wasmView
@@ -1,0 +1,6 @@
1+asm type
2+@@importethereum
3+getBalancefunctionmemoryexport
4+main1code%
5++��������h 
6+data]H���r�h)���oJ-�{
tests/interface/basic_gas_ops.wasmView
@@ -1,0 +1,4 @@
1+asm type @@@import'ethereumuseGasethereum
2+getGasLeftfunctionexporttestcode)'�M 
3+�M 
4+
tests/interface/call.wasmView
@@ -1,0 +1,2 @@
1+asm type@@@importethereumcallfunctionmemoryexport
2+main1code7)34����3�48 M 
tests/interface/callDataCopy.wasmView
@@ -1,0 +1,3 @@
1+asm type
2+@@importethereum callDataCopyfunctionmemoryexportmaincode#!+��Ճ��ܲ h 
3+
tests/interface/callDataSize.wasmView
@@ -1,0 +1,2 @@
1+asm type@@importethereumgetCallDataSizefunctionmemoryexportmaincode�h 
2+
tests/interface/callValue.wasmView
@@ -1,0 +1,2 @@
1+asm type@@importethereum getCallValuefunctionmemoryexportmaincode+��������h 
2+
tests/interface/caller.wasmView
@@ -1,0 +1,2 @@
1+asm type@@importethereum getCallerfunctionmemoryexportmaincode +ݐ�������h 
2+
tests/interface/coinbase.wasmView
@@ -1,0 +1,2 @@
1+asm type@@importethereumgetBlockCoinbasefunctionmemoryexportmaincode +ݐ�������h 
2+
tests/interface/origin.wasmView
@@ -1,0 +1,2 @@
1+asm type@@importethereum getTxOriginfunctionmemoryexportmaincode +ݐ�������h 
2+
tests/interface/sstore.wasmView
@@ -1,0 +1,3 @@
1+asm type
2+@@import.ethereum storageStoreethereum storageLoadfunctionmemoryexport main12codeG���݄ƥ�4����+���݄ƥ�i
3+
tests/interfaceRunner.jsView
@@ -1,52 +1,74 @@
1-'use strict'
21 const tape = require('tape')
32 const fs = require('fs')
4-const cp = require('child_process')
53 const path = require('path')
4+const Vertex = require('merkle-trie')
5+const Address = require('../deps/address')
6+const U256 = require('../deps/u256')
67
78 const Kernel = require('../index.js')
8-const TestEnvironment = require('../testEnvironment.js')
9-const Interface = require('../interface.js')
10-const DebugInterface = require('../debugInterface.js')
9+const Environment = require('../testEnvironment.js')
1110
1211 const dir = path.join(__dirname, '/interface')
1312 // get the test names
1413 let tests = fs.readdirSync(dir).filter((file) => file.endsWith('.wast'))
15-// tests = ['balance.wast']
16-// run the tests
17-for (let testName of tests) {
18- testName = testName.split('.')[0]
19- tape(testName, (t) => {
20- // Compile Command
21- cp.execSync(`${__dirname}/../tools/sexpr-wasm-prototype/out/sexpr-wasm ${dir}/${testName}.wast -o ${dir}/${testName}.wasm`)
22- const buffer = fs.readFileSync(`${dir}/${testName}.wasm`)
23- const envData = fs.readFileSync(`${dir}/${testName}.json`).toString()
24- const ethereum = new Kernel(new TestEnvironment(envData))
14+// tests = ['callDataCopy.wast']
2515
26- // manually `callHander`
27- const environment = new TestEnvironment(envData)
28- environment.parent = ethereum
29- const testContract = new Kernel(environment)
30- const ethInterface = new Interface(environment, testContract)
31- const debugInterface = new DebugInterface()
32- environment.callHandler = testContract.callHandler.bind(testContract)
16+runTests(tests)
3317
34- try {
35- const mod = Wasm.instantiateModule(buffer, {
36- 'ethereum': ethInterface.exportTable,
37- 'debug': debugInterface.exportTable
38- })
39- ethInterface.setModule(mod)
40- debugInterface.setModule(mod)
41- mod.exports.test()
42- } catch (e) {
43- t.fail('Exception: ' + e)
44- console.error('FAIL')
45- console.error(e)
46- } finally {
47- t.pass(testName)
48- console.log('done')
49- }
50- t.end()
51- })
18+function runTests (tests) {
19+ for (let testName of tests) {
20+ testName = testName.split('.')[0]
21+ tape(testName, async (t) => {
22+ // Compile Command
23+
24+ const rootVertex = new Vertex()
25+ const code = fs.readFileSync(`${dir}/${testName}.wasm`)
26+ const envData = JSON.parse(fs.readFileSync(`${dir}/${testName}.json`).toString())
27+
28+ envData.caller = new Address(envData.caller)
29+ envData.address = new Address(envData.address)
30+ envData.coinbase = new Address(envData.coinbase)
31+ envData.origin = new Address(envData.origin)
32+ envData.callData = new Buffer(envData.callData.slice(2), 'hex')
33+ envData.callValue = new U256(envData.callValue)
34+
35+ for (let address in envData.state) {
36+ const account = envData.state[address]
37+ const accountVertex = new Vertex()
38+
39+ accountVertex.set('code', new Vertex({
40+ value: new Buffer(account.code.slice(2), 'hex')
41+ }))
42+
43+ accountVertex.set('balance', new Vertex({
44+ value: new Buffer(account.balance.slice(2), 'hex')
45+ }))
46+
47+ for (let key in account.storage) {
48+ accountVertex.set(['storage', ...new Buffer(key.slice(2), 'hex')], new Vertex({
49+ value: new Buffer(account.storage[key].slice(2), 'hex')
50+ }))
51+ }
52+
53+ const path = [...new Buffer(address.slice(2), 'hex')]
54+ rootVertex.set(path, accountVertex)
55+ }
56+
57+ envData.state = await rootVertex.get([...envData.address.toBuffer()])
58+ const kernel = new Kernel({code: code})
59+ const env = new Environment(envData)
60+
61+ try {
62+ await kernel.run(env)
63+ } catch (e) {
64+ t.fail('Exception: ' + e)
65+ console.error('FAIL')
66+ console.error(e)
67+ } finally {
68+ t.pass(testName)
69+ console.log('done')
70+ }
71+ t.end()
72+ })
73+ }
5274 }
tests/buildTests.jsView
@@ -1,0 +1,14 @@
1+const fs = require('fs')
2+const cp = require('child_process')
3+const path = require('path')
4+
5+const dir = path.join(__dirname, '/interface')
6+// get the test names
7+let tests = fs.readdirSync(dir).filter((file) => file.endsWith('.wast'))
8+// tests = ['balance.wast']
9+// run the tests
10+for (let testName of tests) {
11+ testName = testName.split('.')[0]
12+ // Compile Command
13+ cp.execSync(`${__dirname}/../tools/sexpr-wasm-prototype/out/sexpr-wasm ${dir}/${testName}.wast -o ${dir}/${testName}.wasm`)
14+}
EVMimports.jsView
@@ -1,0 +1,656 @@
1+/**
2+ * This is the Ethereum interface that is exposed to the WASM instance which
3+ * enables to interact with the Ethereum Environment
4+ */
5+const fs = require('fs')
6+const path = require('path')
7+const ethUtil = require('ethereumjs-util')
8+const Vertex = require('merkle-trie')
9+const U256 = require('./deps/u256.js')
10+
11+const U128_SIZE_BYTES = 16
12+const ADDRESS_SIZE_BYTES = 20
13+const U256_SIZE_BYTES = 32
14+
15+// The interface exposed to the WebAessembly Core
16+module.exports = class Interface {
17+ constructor (kernel) {
18+ this.kernel = kernel
19+ const shimBin = fs.readFileSync(path.join(__dirname, '/wasm/interface.wasm'))
20+ const shimMod = WebAssembly.Module(shimBin)
21+ this.shims = WebAssembly.Instance(shimMod, {
22+ 'interface': {
23+ 'useGas': this._useGas.bind(this),
24+ 'getGasLeftHigh': this._getGasLeftHigh.bind(this),
25+ 'getGasLeftLow': this._getGasLeftLow.bind(this),
26+ 'call': this._call.bind(this)
27+ }
28+ })
29+ }
30+
31+ static get name () {
32+ return 'ethereum'
33+ }
34+
35+ get exports () {
36+ let exportMethods = [
37+ // include all the public methods according to the Ethereum Environment Interface (EEI) r1
38+ 'getAddress',
39+ 'getBalance',
40+ 'getTxOrigin',
41+ 'getCaller',
42+ 'getCallValue',
43+ 'getCallDataSize',
44+ 'callDataCopy',
45+ 'callDataCopy256',
46+ 'getCodeSize',
47+ 'codeCopy',
48+ 'getExternalCodeSize',
49+ 'externalCodeCopy',
50+ 'getTxGasPrice',
51+ 'getBlockHash',
52+ 'getBlockCoinbase',
53+ 'getBlockTimestamp',
54+ 'getBlockNumber',
55+ 'getBlockDifficulty',
56+ 'getBlockGasLimit',
57+ 'log',
58+ 'create',
59+ 'callCode',
60+ 'callDelegate',
61+ 'storageStore',
62+ 'storageLoad',
63+ 'return',
64+ 'selfDestruct'
65+ ]
66+ let ret = {}
67+ exportMethods.forEach((method) => {
68+ ret[method] = this[method].bind(this)
69+ })
70+
71+ // add shims
72+ ret.useGas = this.shims.exports.useGas
73+ ret.getGasLeft = this.shims.exports.getGasLeft
74+ ret.call = this.shims.exports.call
75+ return ret
76+ }
77+
78+ setModule (mod) {
79+ this.module = mod
80+ }
81+
82+ /**
83+ * Subtracts an amount to the gas counter
84+ * @param {integer} amount the amount to subtract to the gas counter
85+ */
86+ _useGas (high, low) {
87+ this.takeGas(from64bit(high, low))
88+ }
89+
90+ /**
91+ * Returns the current amount of gas
92+ * @return {integer}
93+ */
94+ _getGasLeftHigh () {
95+ return Math.floor(this.kernel.environment.gasLeft / 4294967296)
96+ }
97+
98+ /**
99+ * Returns the current amount of gas
100+ * @return {integer}
101+ */
102+ _getGasLeftLow () {
103+ return this.kernel.environment.gasLeft
104+ }
105+
106+ /**
107+ * Gets address of currently executing account and loads it into memory at
108+ * the given offset.
109+ * @param {integer} offset
110+ */
111+ getAddress (offset) {
112+ this.takeGas(2)
113+
114+ this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.address.toMemory())
115+ }
116+
117+ /**
118+ * Gets balance of the given account and loads it into memory at the given
119+ * offset.
120+ * @param {integer} addressOffset the memory offset to laod the address
121+ * @param {integer} resultOffset
122+ */
123+ getBalance (addressOffset, offset, cbIndex) {
124+ this.takeGas(20)
125+
126+ const path = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'balance']
127+ const opPromise = this.kernel.environment.state.root.get(path)
128+ .then(vertex => new U256(vertex.value))
129+ .catch(() => new U256(0))
130+
131+ this.kernel.pushOpsQueue(opPromise, cbIndex, balance => {
132+ this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES))
133+ })
134+ }
135+
136+ /**
137+ * Gets the execution's origination address and loads it into memory at the
138+ * given offset. This is the sender of original transaction; it is never an
139+ * account with non-empty associated code.
140+ * @param {integer} offset
141+ */
142+ getTxOrigin (offset) {
143+ this.takeGas(2)
144+
145+ this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.origin.toMemory())
146+ }
147+
148+ /**
149+ * Gets caller address and loads it into memory at the given offset. This is
150+ * the address of the account that is directly responsible for this execution.
151+ * @param {integer} offset
152+ */
153+ getCaller (offset) {
154+ this.takeGas(2)
155+
156+ this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.caller.toMemory())
157+ }
158+
159+ /**
160+ * Gets the deposited value by the instruction/transaction responsible for
161+ * this execution and loads it into memory at the given location.
162+ * @param {integer} offset
163+ */
164+ getCallValue (offset) {
165+ this.takeGas(2)
166+
167+ this.setMemory(offset, U128_SIZE_BYTES, this.kernel.environment.callValue.toMemory(U128_SIZE_BYTES))
168+ }
169+
170+ /**
171+ * Get size of input data in current environment. This pertains to the input
172+ * data passed with the message call instruction or transaction.
173+ * @return {integer}
174+ */
175+ getCallDataSize () {
176+ this.takeGas(2)
177+
178+ return this.kernel.environment.callData.length
179+ }
180+
181+ /**
182+ * Copys the input data in current environment to memory. This pertains to
183+ * the input data passed with the message call instruction or transaction.
184+ * @param {integer} offset the offset in memory to load into
185+ * @param {integer} dataOffset the offset in the input data
186+ * @param {integer} length the length of data to copy
187+ */
188+ callDataCopy (offset, dataOffset, length) {
189+ this.takeGas(3 + Math.ceil(length / 32) * 3)
190+
191+ if (length) {
192+ const callData = this.kernel.environment.callData.slice(dataOffset, dataOffset + length)
193+ this.setMemory(offset, length, callData)
194+ }
195+ }
196+
197+ /**
198+ * Copys the input data in current environment to memory. This pertains to
199+ * the input data passed with the message call instruction or transaction.
200+ * @param {integer} offset the offset in memory to load into
201+ * @param {integer} dataOffset the offset in the input data
202+ */
203+ callDataCopy256 (offset, dataOffset) {
204+ this.takeGas(3)
205+ const callData = this.kernel.environment.callData.slice(dataOffset, dataOffset + 32)
206+ this.setMemory(offset, U256_SIZE_BYTES, callData)
207+ }
208+
209+ /**
210+ * Gets the size of code running in current environment.
211+ * @return {interger}
212+ */
213+ getCodeSize (cbIndex) {
214+ this.takeGas(2)
215+
216+ const opPromise = this.kernel.environment.state
217+ .get('code')
218+ .then(vertex => vertex.value.length)
219+
220+ // wait for all the prevouse async ops to finish before running the callback
221+ this.kernel.pushOpsQueue(opPromise, cbIndex, length => length)
222+ }
223+
224+ /**
225+ * Copys the code running in current environment to memory.
226+ * @param {integer} offset the memory offset
227+ * @param {integer} codeOffset the code offset
228+ * @param {integer} length the length of code to copy
229+ */
230+ codeCopy (resultOffset, codeOffset, length, cbIndex) {
231+ this.takeGas(3 + Math.ceil(length / 32) * 3)
232+
233+ let opPromise
234+
235+ if (length) {
236+ opPromise = this.kernel.environment.state
237+ .get('code')
238+ .then(vertex => vertex.value)
239+ } else {
240+ opPromise = Promise.resolve([])
241+ }
242+
243+ // wait for all the prevouse async ops to finish before running the callback
244+ this.kernel.pushOpsQueue(opPromise, cbIndex, code => {
245+ if (code.length) {
246+ code = code.slice(codeOffset, codeOffset + length)
247+ this.setMemory(resultOffset, length, code)
248+ }
249+ })
250+ }
251+
252+ /**
253+ * Get size of an account’s code.
254+ * @param {integer} addressOffset the offset in memory to load the address from
255+ * @return {integer}
256+ */
257+ getExternalCodeSize (addressOffset, cbOffset) {
258+ this.takeGas(20)
259+ const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
260+ const opPromise = this.kernel.environment.state.root
261+ .get(address)
262+ .then(vertex => vertex.value.length)
263+ .catch(() => 0)
264+
265+ // wait for all the prevouse async ops to finish before running the callback
266+ this.kernel.pushOpsQueue(opPromise, cbOffset, length => length)
267+ }
268+
269+ /**
270+ * Copys the code of an account to memory.
271+ * @param {integer} addressOffset the memory offset of the address
272+ * @param {integer} resultOffset the memory offset
273+ * @param {integer} codeOffset the code offset
274+ * @param {integer} length the length of code to copy
275+ */
276+ externalCodeCopy (addressOffset, resultOffset, codeOffset, length, cbIndex) {
277+ this.takeGas(20 + Math.ceil(length / 32) * 3)
278+
279+ const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
280+ let opPromise
281+
282+ if (length) {
283+ opPromise = this.kernel.environment.state.root
284+ .get(address)
285+ .then(vertex => vertex.value)
286+ .catch(() => [])
287+ } else {
288+ opPromise = Promise.resolve([])
289+ }
290+
291+ // wait for all the prevouse async ops to finish before running the callback
292+ this.kernel.pushOpsQueue(opPromise, cbIndex, code => {
293+ if (code.length) {
294+ code = code.slice(codeOffset, codeOffset + length)
295+ this.setMemory(resultOffset, length, code)
296+ }
297+ })
298+ }
299+
300+ /**
301+ * Gets price of gas in current environment.
302+ * @return {integer}
303+ */
304+ getTxGasPrice () {
305+ this.takeGas(2)
306+
307+ return this.kernel.environment.gasPrice
308+ }
309+
310+ /**
311+ * Gets the hash of one of the 256 most recent complete blocks.
312+ * @param {integer} number which block to load
313+ * @param {integer} offset the offset to load the hash into
314+ */
315+ getBlockHash (number, offset, cbOffset) {
316+ this.takeGas(20)
317+
318+ const diff = this.kernel.environment.block.number - number
319+ let opPromise
320+
321+ if (diff > 256 || diff <= 0) {
322+ opPromise = Promise.resolve(new U256(0))
323+ } else {
324+ opPromise = this.kernel.environment.getBlockHash(number)
325+ }
326+
327+ // wait for all the prevouse async ops to finish before running the callback
328+ this.kernel.pushOpsQueue(opPromise, cbOffset, hash => {
329+ this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
330+ })
331+ }
332+
333+ /**
334+ * Gets the block’s beneficiary address and loads into memory.
335+ * @param offset
336+ */
337+ getBlockCoinbase (offset) {
338+ this.takeGas(2)
339+
340+ this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.coinbase.toMemory())
341+ }
342+
343+ /**
344+ * Get the block’s timestamp.
345+ * @return {integer}
346+ */
347+ getBlockTimestamp () {
348+ this.takeGas(2)
349+
350+ return this.kernel.environment.block.timestamp
351+ }
352+
353+ /**
354+ * Get the block’s number.
355+ * @return {integer}
356+ */
357+ getBlockNumber () {
358+ this.takeGas(2)
359+
360+ return this.kernel.environment.block.number
361+ }
362+
363+ /**
364+ * Get the block’s difficulty.
365+ * @return {integer}
366+ */
367+ getBlockDifficulty (offset) {
368+ this.takeGas(2)
369+
370+ this.setMemory(offset, U256_SIZE_BYTES, this.kernel.environment.block.difficulty.toMemory())
371+ }
372+
373+ /**
374+ * Get the block’s gas limit.
375+ * @return {integer}
376+ */
377+ getBlockGasLimit () {
378+ this.takeGas(2)
379+
380+ return this.kernel.environment.block.gasLimit
381+ }
382+
383+ /**
384+ * Creates a new log in the current environment
385+ * @param {integer} dataOffset the offset in memory to load the memory
386+ * @param {integer} length the data length
387+ * @param {integer} number of topics
388+ */
389+ log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
390+ if (numberOfTopics < 0 || numberOfTopics > 4) {
391+ throw new Error('Invalid numberOfTopics')
392+ }
393+
394+ this.takeGas(375 + length * 8 + numberOfTopics * 375)
395+
396+ const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
397+ const topics = []
398+
399+ if (numberOfTopics > 0) {
400+ topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
401+ }
402+
403+ if (numberOfTopics > 1) {
404+ topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
405+ }
406+
407+ if (numberOfTopics > 2) {
408+ topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
409+ }
410+
411+ if (numberOfTopics > 3) {
412+ topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
413+ }
414+
415+ this.kernel.environment.logs.push({
416+ data: data,
417+ topics: topics
418+ })
419+ }
420+
421+ /**
422+ * Creates a new contract with a given value.
423+ * @param {integer} valueOffset the offset in memory to the value from
424+ * @param {integer} dataOffset the offset to load the code for the new contract from
425+ * @param {integer} length the data length
426+ * @param (integer} resultOffset the offset to write the new contract address to
427+ * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
428+ */
429+ create (valueOffset, dataOffset, length, resultOffset, cbIndex) {
430+ this.takeGas(32000)
431+
432+ const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
433+ // if (length) {
434+ // const code = this.getMemory(dataOffset, length).slice(0)
435+ // }
436+
437+ let opPromise
438+
439+ if (value.gt(this.kernel.environment.value)) {
440+ opPromise = Promise.resolve(new Buffer(20).fill(0))
441+ } else {
442+ // todo actully run the code
443+ opPromise = Promise.resolve(ethUtil.generateAddress(this.kernel.environment.address, this.kernel.environment.nonce))
444+ }
445+
446+ // wait for all the prevouse async ops to finish before running the callback
447+ this.kernel.pushOpsQueue(opPromise, cbIndex, address => {
448+ this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address)
449+ })
450+ }
451+
452+ /**
453+ * Sends a message with arbiatary data to a given address path
454+ * @param {integer} addressOffset the offset to load the address path from
455+ * @param {integer} valueOffset the offset to load the value from
456+ * @param {integer} dataOffset the offset to load data from
457+ * @param {integer} dataLength the length of data
458+ * @param {integer} resultOffset the offset to store the result data at
459+ * @param {integer} resultLength
460+ * @param {integer} gas
461+ * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
462+ */
463+ _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
464+ this.takeGas(40)
465+
466+ const gas = from64bit(gasHigh, gasLow)
467+ // Load the params from mem
468+ const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
469+ const value = new U256(this.getMemory(valueOffset, U128_SIZE_BYTES))
470+
471+ // Special case for non-zero value; why does this exist?
472+ if (!value.isZero()) {
473+ this.takeGas(9000 - 2300 + gas)
474+ this.takeGas(-gas)
475+ }
476+
477+ let opPromise = this.kernel.environment.state.root.get(address)
478+ .catch(() => {
479+ // why does this exist?
480+ this.takeGas(25000)
481+ })
482+
483+ // wait for all the prevouse async ops to finish before running the callback
484+ this.kernel.pushOpsQueue(opPromise, cbIndex, () => {
485+ return 1
486+ })
487+ }
488+
489+ /**
490+ * Message-call into this account with an alternative account’s code.
491+ * @param {integer} addressOffset the offset to load the address path from
492+ * @param {integer} valueOffset the offset to load the value from
493+ * @param {integer} dataOffset the offset to load data from
494+ * @param {integer} dataLength the length of data
495+ * @param {integer} resultOffset the offset to store the result data at
496+ * @param {integer} resultLength
497+ * @param {integer} gas
498+ * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
499+ */
500+ callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
501+ this.takeGas(40)
502+ // Load the params from mem
503+ const path = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
504+ const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
505+
506+ // Special case for non-zero value; why does this exist?
507+ if (!value.isZero()) {
508+ this.takeGas(6700)
509+ }
510+
511+ // TODO: should be message?
512+ const opPromise = this.kernel.environment.state.root.get(path)
513+ .catch(() => {
514+ // TODO: handle errors
515+ // the value was not found
516+ return null
517+ })
518+
519+ this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
520+ return 1
521+ })
522+ }
523+
524+ /**
525+ * Message-call into this account with an alternative account’s code, but
526+ * persisting the current values for sender and value.
527+ * @param {integer} gas
528+ * @param {integer} addressOffset the offset to load the address path from
529+ * @param {integer} valueOffset the offset to load the value from
530+ * @param {integer} dataOffset the offset to load data from
531+ * @param {integer} dataLength the length of data
532+ * @param {integer} resultOffset the offset to store the result data at
533+ * @param {integer} resultLength
534+ * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
535+ */
536+ callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
537+ // FIXME: count properly
538+ this.takeGas(40)
539+
540+ const data = this.getMemory(dataOffset, dataLength).slice(0)
541+ const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
542+ const [errorCode, result] = this.environment.callDelegate(gas, address, data)
543+ this.setMemory(resultOffset, resultLength, result)
544+ return errorCode
545+ }
546+
547+ /**
548+ * store a value at a given path in long term storage which are both loaded
549+ * from Memory
550+ * @param {interger} pathOffest the memory offset to load the the path from
551+ * @param {interger} valueOffset the memory offset to load the value from
552+ */
553+ storageStore (pathOffset, valueOffset, cbIndex) {
554+ this.takeGas(5000)
555+ const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
556+ // copy the value
557+ const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
558+ const valIsZero = value.every((i) => i === 0)
559+ const opPromise = this.kernel.environment.state.get(path)
560+ .then(vertex => vertex.value)
561+ .catch(() => null)
562+
563+ this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
564+ if (valIsZero && oldValue) {
565+ // delete a value
566+ this.kernel.environment.gasRefund += 15000
567+ this.kernel.environment.state.del(path)
568+ } else {
569+ if (!valIsZero && !oldValue) {
570+ // creating a new value
571+ this.takeGas(15000)
572+ }
573+ // update
574+ this.kernel.environment.state.set(path, new Vertex({
575+ value: value
576+ }))
577+ }
578+ })
579+ }
580+
581+ /**
582+ * reterives a value at a given path in long term storage
583+ * @param {interger} pathOffest the memory offset to load the the path from
584+ * @param {interger} resultOffset the memory offset to load the value from
585+ */
586+ storageLoad (pathOffset, resultOffset, cbIndex) {
587+ this.takeGas(50)
588+
589+ // convert the path to an array
590+ const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
591+ // get the value from the state
592+ const opPromise = this.kernel.environment.state.get(path)
593+ .then(vertex => vertex.value)
594+ .catch(() => new Uint8Array(32))
595+
596+ this.kernel.pushOpsQueue(opPromise, cbIndex, value => {
597+ this.setMemory(resultOffset, U256_SIZE_BYTES, value)
598+ })
599+ }
600+
601+ /**
602+ * Halt execution returning output data.
603+ * @param {integer} offset the offset of the output data.
604+ * @param {integer} length the length of the output data.
605+ */
606+ return (offset, length) {
607+ if (length) {
608+ this.kernel.environment.returnValue = this.getMemory(offset, length).slice(0)
609+ }
610+ }
611+
612+ /**
613+ * Halt execution and register account for later deletion giving the remaining
614+ * balance to an address path
615+ * @param {integer} offset the offset to load the address from
616+ */
617+ selfDestruct (addressOffset) {
618+ this.kernel.environment.selfDestruct = true
619+ this.kernel.environment.selfDestructAddress = this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)
620+ this.kernel.environment.gasRefund += 24000
621+ }
622+
623+ getMemory (offset, length) {
624+ return new Uint8Array(this.kernel.memory, offset, length)
625+ }
626+
627+ setMemory (offset, length, value) {
628+ const memory = new Uint8Array(this.kernel.memory, offset, length)
629+ memory.set(value)
630+ }
631+
632+ /*
633+ * Takes gas from the tank. Only needs to check if there's gas left to be taken,
634+ * because every caller of this method is trusted.
635+ */
636+ takeGas (amount) {
637+ if (this.kernel.environment.gasLeft < amount) {
638+ throw new Error('Ran out of gas')
639+ }
640+ this.kernel.environment.gasLeft -= amount
641+ }
642+}
643+
644+// converts a 64 bit number to a JS number
645+function from64bit (high, low) {
646+ if (high < 0) {
647+ // convert from a 32-bit two's compliment
648+ high = 0x100000000 - high
649+ }
650+ if (low < 0) {
651+ // convert from a 32-bit two's compliment
652+ low = 0x100000000 - low
653+ }
654+ // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
655+ return (high * 4294967296) + low
656+}
address.jsView
@@ -1,42 +1,0 @@
1-const BN = require('bn.js')
2-const U256 = require('./u256.js')
3-
4-module.exports = class Address extends U256 {
5- constructor (value) {
6- super(value)
7- if (this._value.byteLength() > 20) {
8- throw new Error('Invalid address length: ' + this._value.byteLength() + ' for ' + value)
9- }
10- }
11-
12- // This assumes Uint8Array in LSB (WASM code)
13- static fromMemory (value) {
14- return new Address(new BN(value, 16, 'le'))
15- }
16-
17- // This assumes Uint8Array in LSB (WASM code)
18- toMemory () {
19- return this._value.toBuffer('le', 20)
20- }
21-
22- toBuffer () {
23- return super.toBuffer(20)
24- }
25-
26- // Needs to be displayed as a hex always
27- toString () {
28- return '0x' + this._value.toString('hex', 40)
29- }
30-
31- static zero () {
32- return new Address('0x0000000000000000000000000000000000000000')
33- }
34-
35- isZero () {
36- return this._value.isZero()
37- }
38-
39- equals (address) {
40- return this.toString() === address.toString()
41- }
42-}
block.jsView
@@ -1,31 +1,0 @@
1-//
2-// This class parses a serialised Ethereum Block
3-//
4-// The input is a Buffer.
5-//
6-const Address = require('./address.js')
7-const ethUtil = require('ethereumjs-util')
8-const OldBlock = require('ethereumjs-block')
9-const U256 = require('./u256.js')
10-
11-module.exports = class Block extends OldBlock {
12- get number () {
13- return ethUtil.bufferToInt(this.header.number)
14- }
15-
16- get gasLimit () {
17- return ethUtil.bufferToInt(this.header.gasLimit)
18- }
19-
20- get difficulty () {
21- return new U256(this.header.difficulty)
22- }
23-
24- get timestamp () {
25- return ethUtil.bufferToInt(this.header.timestamp)
26- }
27-
28- get coinbase () {
29- return new Address(this.header.coinbase)
30- }
31-}
design.mdView
@@ -1,53 +1,0 @@
1-# Architecture
2-
3-This prototype attempts to model Ethereum as three seperate but interlocking
4-layers. Environment, Kernel, and VM.
5-```
6- +------------------+
7- | |
8- | Environment |
9- | |
10- +------------------+
11- |
12- +------------------+
13- | |
14- | Kernal |
15- | |
16- +------------------+
17- |
18- interfaces
19- |
20- +------------------+
21- | |
22- | VM |
23- | |
24- +------------------+
25-```
26-## VM
27-
28-The VM implements [webassembly](https://github.com/WebAssembly/design). Two
29-sets of intefaces are exposed to it by the kernal. The Kernal Interface and
30-The Environment Interface.
31-
32-## Kernel Interface
33-
34-The kernel handles the following
35- * Interprocess communication
36- * Intializing the VM and exposes ROM containing code to the VM (codeHandler)
37- * Exposing the namespace and Intializes the Environment which VM instance exists
38- (callHandler)
39- * Provides some built in contracts that facilitates different run levels
40- (runTx, runBlock)
41- * Provides resource sharing and limiting via gas
42-
43-The kernel Interface expose kernal primitives to VM which contain
44- * IPC (calls)
45- * Namespace Interface
46- * GET/PUT/DELETE/ROOT/NEXT - currently implemented as a `Map`
47-
48-## Environment Interface
49-
50-The Environment Interface expose the following
51-* blockchain infromation
52-* current block infromation
53-* transaction infromation
deps/address.jsView
@@ -1,0 +1,46 @@
1+const BN = require('bn.js')
2+const U256 = require('./u256.js')
3+
4+module.exports = class Address extends U256 {
5+ constructor (value) {
6+ super(value)
7+ if (this._value.byteLength() > 20) {
8+ throw new Error('Invalid address length: ' + this._value.byteLength() + ' for ' + value)
9+ }
10+ }
11+
12+ // This assumes Uint8Array in LSB (WASM code)
13+ static fromMemory (value) {
14+ return new Address(new BN(value, 16, 'be'))
15+ }
16+
17+ // This assumes Uint8Array in LSB (WASM code)
18+ toMemory () {
19+ return this._value.toBuffer('be', 20)
20+ }
21+
22+ toBuffer () {
23+ return super.toBuffer(20)
24+ }
25+
26+ toArray () {
27+ return [...this.toBuffer()]
28+ }
29+
30+ // Needs to be displayed as a hex always
31+ toString () {
32+ return '0x' + this._value.toString('hex', 40)
33+ }
34+
35+ static zero () {
36+ return new Address('0x0000000000000000000000000000000000000000')
37+ }
38+
39+ isZero () {
40+ return this._value.isZero()
41+ }
42+
43+ equals (address) {
44+ return this.toString() === address.toString()
45+ }
46+}
deps/block.jsView
@@ -1,0 +1,31 @@
1+//
2+// This class parses a serialised Ethereum Block
3+//
4+// The input is a Buffer.
5+//
6+const Address = require('./address.js')
7+const ethUtil = require('ethereumjs-util')
8+const OldBlock = require('ethereumjs-block')
9+const U256 = require('./u256.js')
10+
11+module.exports = class Block extends OldBlock {
12+ get number () {
13+ return ethUtil.bufferToInt(this.header.number)
14+ }
15+
16+ get gasLimit () {
17+ return ethUtil.bufferToInt(this.header.gasLimit)
18+ }
19+
20+ get difficulty () {
21+ return new U256(this.header.difficulty)
22+ }
23+
24+ get timestamp () {
25+ return ethUtil.bufferToInt(this.header.timestamp)
26+ }
27+
28+ get coinbase () {
29+ return new Address(this.header.coinbase)
30+ }
31+}
deps/rootVertex.jsView
@@ -1,0 +1,50 @@
1+const KernelVertex = require('./kernelVertex')
2+const Kernel = require('../')
3+const Precompiles = require('../precomiles/precompile.js')
4+const Address = require('./address')
5+
6+const identityAddress = new Address('0x0000000000000000000000000000000000000004')
7+const meteringAddress = new Address('0x000000000000000000000000000000000000000A')
8+const transcompilerAddress = new Address('0x000000000000000000000000000000000000000B')
9+
10+module.exports = class RootKernelVertex extends KernelVertex {
11+ constructor (opts) {
12+ super(opts)
13+ if (opts.root) {
14+ this.set(identityAddress.toArray(), new PrecomileVertex(Precompiles.identity))
15+ this.set(meteringAddress.toArray(), new PrecomileVertex(Precompiles.meteringInjector))
16+ this.set(transcompilerAddress.toArray(), new PrecomileVertex(Precompiles.transcompiler))
17+ this.kernel = new Kernel({state: this})
18+ }
19+ }
20+}
21+
22+class PrecomileVertex extends KernelVertex {
23+ /**
24+ * Creates a Vertex for precomiles. This will alwasy return false when hashed
25+ * so that its contents will never be stored in the merkle trie run serialized
26+ */
27+ constructor (precomiled) {
28+ super()
29+ this.kernel = precomiled
30+ }
31+
32+ hash () {
33+ return false
34+ }
35+}
36+
37+// detects the correct kernel to load given some code
38+KernelVertex.codeHandler = (code) => {
39+ return KernelVertex['default']
40+}
41+
42+KernelVertex.codeHandles = {
43+ 'default': Kernel
44+}
45+
46+// KernelVertex.linkHander = (link) => {
47+// }
48+
49+// KernelVertex.linkHanders = {
50+// }
deps/transaction.jsView
@@ -1,0 +1,49 @@
1+//
2+// This class parses a serialised Ethereum transaction
3+//
4+// The input is a Buffer.
5+//
6+const Address = require('./address.js')
7+const U256 = require('./u256.js')
8+const OldTx = require('ethereumjs-tx')
9+
10+module.exports = class Transaction {
11+ constructor (tx) {
12+ this._tx = new OldTx(tx)
13+ }
14+
15+ get valid () {
16+ return this._tx.verifySignature()
17+ }
18+
19+ get nonce () {
20+ return new U256(this._tx.nonce)
21+ }
22+
23+ get gasPrice () {
24+ return new U256(this._tx.gasPrice)
25+ }
26+
27+ get gasLimit () {
28+ return new U256(this._tx.gasLimit)
29+ }
30+
31+ get value () {
32+ return new U256(this._tx.value)
33+ }
34+
35+ get data () {
36+ return Uint8Array.from(this._tx.data)
37+ }
38+
39+ get from () {
40+ return new Address(this._tx.getSenderAddress())
41+ }
42+
43+ get to () {
44+ if (this._tx.to.length === 0) {
45+ return new Address('0x0000000000000000000000000000000000000000')
46+ }
47+ return new Address(this._tx.to)
48+ }
49+}
deps/u256.jsView
@@ -1,0 +1,69 @@
1+const BN = require('bn.js')
2+const ethUtil = require('ethereumjs-util')
3+
4+module.exports = class U256 {
5+ constructor (value) {
6+ // bn.js still doesn't support hex prefixes...
7+ if ((typeof value === 'string') && ethUtil.isHexPrefixed(value)) {
8+ this._value = new BN(ethUtil.stripHexPrefix(value), 16)
9+ } else {
10+ this._value = new BN(value, 10)
11+ }
12+ }
13+
14+ // This assumes Uint8Array in LSB (WASM code)
15+ static fromMemory (value) {
16+ return new U256(new BN(value, 16, 'be'))
17+ }
18+
19+ // This assumes Uint8Array in LSB (WASM code)
20+ toMemory (width) {
21+ return this._value.toBuffer('be', width || 32)
22+ }
23+
24+ toString (radix = 10) {
25+ if (radix === 16) {
26+ return '0x' + this._value.toString(16)
27+ }
28+ return this._value.toString(radix)
29+ }
30+
31+ toBuffer (width = 32) {
32+ if (width <= 0 || width > 32) {
33+ throw new Error('Invalid U256 width')
34+ }
35+ return this._value.toBuffer('be', width)
36+ }
37+
38+ toArray () {
39+ return [...this.toBuffer()]
40+ }
41+
42+ isZero () {
43+ return this._value.isZero()
44+ }
45+
46+ sub (u256) {
47+ return new U256(this._value.sub(u256._value))
48+ }
49+
50+ add (u256) {
51+ return new U256(this._value.add(u256._value))
52+ }
53+
54+ mul (u256) {
55+ return new U256(this._value.mul(u256._value))
56+ }
57+
58+ div (u256) {
59+ return new U256(this._value.div(u256._value))
60+ }
61+
62+ lt (u256) {
63+ return this._value.lt(u256._value)
64+ }
65+
66+ gt (u256) {
67+ return this._value.gt(u256._value)
68+ }
69+}
deps/utils.jsView
@@ -1,0 +1,14 @@
1+const ethUtil = require('ethereumjs-util')
2+const Address = require('./address.js')
3+
4+var Utils = {}
5+
6+Utils.isWASMCode = function (code) {
7+ return code.slice(0, 4).toString() === new Uint8Array([0, 0x61, 0x73, 0x6d]).toString()
8+}
9+
10+Utils.newAccountAddress = function (sender, nonce) {
11+ return new Address('0x' + ethUtil.generateAddress(sender.toString(), nonce.toString()).toString('hex'))
12+}
13+
14+module.exports = Utils
docs/design.mdView
@@ -1,0 +1,25 @@
1+# Architecture
2+
3+```
4+ +--------------+
5+ | |
6+ | Environment |
7+ | |
8+ +--------------+ +--------------------+
9+ | | +--+
10+ | +----+ Imports | |
11+ | | +--------------------+ |
12+ +------------+ +------------+ | | +--------------------------+
13+ | | | | | +--------------------+ | | |
14+ | Kernel +-------+VM Container+---------+ +------+ Sandboxed VM instance |
15+ | | | | | | Imports | | | |
16+ +------------+ +------------+ | +--------------------+ | +--------------------------+
17+ | |
18+ | +--------------------+ |
19+ | | | |
20+ +----+ Imports +--+
21+ +--------------------+
22+
23+```
24+# Overview
25+The `Kernel` is modeled to be somewhat like [actors](https://en.wikipedia.org/wiki/Actor_model). Each Kernel/Actor is bound to a segment of code and a state tree on startup. The Kernel provides the top level API. When the kernel recieves a message from another kernel or an external source (signal) it may run that code in a VM container. The container just provides a uniform way to interact with VMs. The container is given an instance of `Evironment`. The `Evironment` contains all the ephemeral state that need for the VM container and instance. Lastly the VM container start and manages the VM instance which is assumed to be sandboxed. The Sandbox communicates to VM container via `Imports` that are exposed to it on the time of creation.
examples/test.jsView
@@ -1,0 +1,41 @@
1+const Kernel = require('./index.js')
2+const Environment = require('./environment.js')
3+
4+const environment = new Environment()
5+const kernel = new Kernel(environment)
6+
7+const Address = require('./address.js')
8+const U256 = require('./u256.js')
9+
10+const fs = require('fs')
11+
12+environment.addAccount(new Address('0x1234567890134561111123412341234123412341'), { balance: new U256('100000000000000000') })
13+// environment.addAccount(new Address('0x4123412341234123411234567890134561111123'), { code: Uint8Array.from(fs.readFileSync('identity.wasm')) })
14+
15+environment.addAccount(new Address('0xbe862ad9abfe6f22bcb087716c7d89a26051f74c'), { balance: new U256('100000000000000000') })
16+var tx = new Buffer('f8e380648203e88080b8977f4e616d65526567000000000000000000000000000000000000000000000000003057307f4e616d6552656700000000000000000000000000000000000000000000000000573360455760415160566000396000f20036602259604556330e0f600f5933ff33560f601e5960003356576000335700604158600035560f602b590033560f603659600033565733600035576000353357001ca0b8b9fedc076110cd002224a942e9d7099e4a626ebf66cd9301fc18e2c1181806a04e270be511d42189baf14599eb8d6eb5037ab105032dd3e0fa05b43dad4cb4c2', 'hex')
17+console.log(kernel.runTx(tx, environment))
18+
19+// deploy contract
20+let ret = kernel.runTx({
21+ nonce: new U256(3),
22+ from: new Address('0x1234567890134561111123412341234123412341'),
23+ to: new Address('0x0000000000000000000000000000000000000000'),
24+ value: new U256('100'),
25+ gasLimit: new U256('1000000000'),
26+ gasPrice: new U256(1),
27+ data: Uint8Array.from(fs.readFileSync('identity.wasm'))
28+}, environment)
29+console.log('Account created: ' + ret.accountCreated)
30+
31+ret = kernel.runTx({
32+ nonce: new U256(4),
33+ from: new Address('0x1234567890134561111123412341234123412341'),
34+ to: ret.accountCreated, // new Address('0x4123412341234123411234567890134561111123'),
35+ value: new U256('100'),
36+ gasLimit: new U256('1000000000'),
37+ gasPrice: new U256(1),
38+ data: Uint8Array.from(new Buffer('spartaaaa'))
39+}, environment)
40+
41+console.log('Return value: ' + ret.returnValue)
interface.jsView
@@ -1,573 +1,0 @@
1-/**
2- * This is the Ethereum interface that is exposed to the WASM instance which
3- * enables to interact with the Ethereum Environment
4- */
5-const Address = require('./address.js')
6-const U256 = require('./u256.js')
7-const fs = require('fs')
8-const path = require('path')
9-
10-const U128_SIZE_BYTES = 16
11-const ADDRESS_SIZE_BYTES = 20
12-const U256_SIZE_BYTES = 32
13-
14-// The interface exposed to the WebAessembly Core
15-module.exports = class Interface {
16- constructor (environment) {
17- this.environment = environment
18- const shimBin = fs.readFileSync(path.join(__dirname, '/wasm/interface.wasm'))
19- const shimMod = WebAssembly.Module(shimBin)
20- this.shims = WebAssembly.Instance(shimMod, {
21- 'interface': {
22- 'useGas': this._useGas.bind(this),
23- 'getGasLeftHigh': this._getGasLeftHigh.bind(this),
24- 'getGasLeftLow': this._getGasLeftLow.bind(this),
25- 'call': this._call.bind(this)
26- }
27- })
28- }
29-
30- get exportTable () {
31- let exportMethods = [
32- // include all the public methods according to the Ethereum Environment Interface (EEI) r1
33- 'getAddress',
34- 'getBalance',
35- 'getTxOrigin',
36- 'getCaller',
37- 'getCallValue',
38- 'getCallDataSize',
39- 'callDataCopy',
40- 'callDataCopy256',
41- 'getCodeSize',
42- 'codeCopy',
43- 'getExternalCodeSize',
44- 'externalCodeCopy',
45- 'getTxGasPrice',
46- 'getBlockHash',
47- 'getBlockCoinbase',
48- 'getBlockTimestamp',
49- 'getBlockNumber',
50- 'getBlockDifficulty',
51- 'getBlockGasLimit',
52- 'log',
53- 'create',
54- 'callCode',
55- 'callDelegate',
56- 'storageStore',
57- 'storageLoad',
58- 'return',
59- 'selfDestruct'
60- ]
61- let ret = {}
62- exportMethods.forEach((method) => {
63- ret[method] = this[method].bind(this)
64- })
65- return ret
66- }
67-
68- setModule (mod) {
69- this.module = mod
70- }
71-
72- /**
73- * Subtracts an amount to the gas counter
74- * @param {integer} amount the amount to subtract to the gas counter
75- */
76- _useGas (high, low) {
77- this.takeGas(from64bit(high, low))
78- }
79-
80- /**
81- * Returns the current amount of gas
82- * @return {integer}
83- */
84- _getGasLeftHigh () {
85- return Math.floor(this.environment.gasLeft / 4294967296)
86- }
87-
88- /**
89- * Returns the current amount of gas
90- * @return {integer}
91- */
92- _getGasLeftLow () {
93- return this.environment.gasLeft
94- }
95-
96- /**
97- * Gets address of currently executing account and loads it into memory at
98- * the given offset.
99- * @param {integer} offset
100- */
101- getAddress (offset) {
102- this.takeGas(2)
103-
104- this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.address.toMemory())
105- }
106-
107- /**
108- * Gets balance of the given account and loads it into memory at the given
109- * offset.
110- * @param {integer} addressOffset the memory offset to laod the address
111- * @param {integer} resultOffset
112- */
113- getBalance (addressOffset, offset) {
114- this.takeGas(20)
115-
116- const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
117- // call the parent contract and ask for the balance of one of its child contracts
118- const balance = this.environment.getBalance(address)
119- this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES))
120- }
121-
122- /**
123- * Gets the execution's origination address and loads it into memory at the
124- * given offset. This is the sender of original transaction; it is never an
125- * account with non-empty associated code.
126- * @param {integer} offset
127- */
128- getTxOrigin (offset) {
129- this.takeGas(2)
130-
131- this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.origin.toMemory())
132- }
133-
134- /**
135- * Gets caller address and loads it into memory at the given offset. This is
136- * the address of the account that is directly responsible for this execution.
137- * @param {integer} offset
138- */
139- getCaller (offset) {
140- this.takeGas(2)
141-
142- this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.caller.toMemory())
143- }
144-
145- /**
146- * Gets the deposited value by the instruction/transaction responsible for
147- * this execution and loads it into memory at the given location.
148- * @param {integer} offset
149- */
150- getCallValue (offset) {
151- this.takeGas(2)
152-
153- this.setMemory(offset, U128_SIZE_BYTES, this.environment.callValue.toMemory(U128_SIZE_BYTES))
154- }
155-
156- /**
157- * Get size of input data in current environment. This pertains to the input
158- * data passed with the message call instruction or transaction.
159- * @return {integer}
160- */
161- getCallDataSize () {
162- this.takeGas(2)
163-
164- return this.environment.callData.length
165- }
166-
167- /**
168- * Copys the input data in current environment to memory. This pertains to
169- * the input data passed with the message call instruction or transaction.
170- * @param {integer} offset the offset in memory to load into
171- * @param {integer} dataOffset the offset in the input data
172- * @param {integer} length the length of data to copy
173- */
174- callDataCopy (offset, dataOffset, length) {
175- this.takeGas(3 + Math.ceil(length / 32) * 3)
176-
177- if (length) {
178- const callData = this.environment.callData.slice(dataOffset, dataOffset + length)
179- this.setMemory(offset, length, callData)
180- }
181- }
182-
183- /**
184- * Copys the input data in current environment to memory. This pertains to
185- * the input data passed with the message call instruction or transaction.
186- * @param {integer} offset the offset in memory to load into
187- * @param {integer} dataOffset the offset in the input data
188- */
189- callDataCopy256 (offset, dataOffset) {
190- this.takeGas(3)
191- const callData = this.environment.callData.slice(dataOffset, dataOffset + 32)
192- this.setMemory(offset, U256_SIZE_BYTES, callData)
193- }
194-
195- /**
196- * Gets the size of code running in current environment.
197- * @return {interger}
198- */
199- getCodeSize () {
200- this.takeGas(2)
201-
202- return this.environment.code.length
203- }
204-
205- /**
206- * Copys the code running in current environment to memory.
207- * @param {integer} offset the memory offset
208- * @param {integer} codeOffset the code offset
209- * @param {integer} length the length of code to copy
210- */
211- codeCopy (resultOffset, codeOffset, length) {
212- this.takeGas(3 + Math.ceil(length / 32) * 3)
213-
214- if (length) {
215- const code = this.environment.code.slice(codeOffset, codeOffset + length)
216- this.setMemory(resultOffset, length, code)
217- }
218- }
219-
220- /**
221- * Get size of an account’s code.
222- * @param {integer} addressOffset the offset in memory to load the address from
223- * @return {integer}
224- */
225- getExternalCodeSize (addressOffset) {
226- this.takeGas(20)
227-
228- const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
229- const code = this.environment.getCode(address)
230- return code.length
231- }
232-
233- /**
234- * Copys the code of an account to memory.
235- * @param {integer} addressOffset the memory offset of the address
236- * @param {integer} resultOffset the memory offset
237- * @param {integer} codeOffset the code offset
238- * @param {integer} length the length of code to copy
239- */
240- externalCodeCopy (addressOffset, resultOffset, codeOffset, length) {
241- this.takeGas(20 + Math.ceil(length / 32) * 3)
242-
243- if (length) {
244- const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
245- let code = this.environment.getCode(address)
246- code = code.slice(codeOffset, codeOffset + length)
247- this.setMemory(resultOffset, length, code)
248- }
249- }
250-
251- /**
252- * Gets price of gas in current environment.
253- * @return {integer}
254- */
255- getTxGasPrice () {
256- this.takeGas(2)
257-
258- return this.environment.gasPrice
259- }
260-
261- /**
262- * Gets the hash of one of the 256 most recent complete blocks.
263- * @param {integer} number which block to load
264- * @param {integer} offset the offset to load the hash into
265- */
266- getBlockHash (number, offset) {
267- this.takeGas(20)
268-
269- const diff = this.environment.block.number - number
270- let hash
271-
272- if (diff > 256 || diff <= 0) {
273- hash = new U256(0)
274- } else {
275- hash = new U256(this.environment.getBlockHash(number))
276- }
277- this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
278- }
279-
280- /**
281- * Gets the block’s beneficiary address and loads into memory.
282- * @param offset
283- */
284- getBlockCoinbase (offset) {
285- this.takeGas(2)
286-
287- this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.block.coinbase.toMemory())
288- }
289-
290- /**
291- * Get the block’s timestamp.
292- * @return {integer}
293- */
294- getBlockTimestamp () {
295- this.takeGas(2)
296-
297- return this.environment.block.timestamp
298- }
299-
300- /**
301- * Get the block’s number.
302- * @return {integer}
303- */
304- getBlockNumber () {
305- this.takeGas(2)
306-
307- return this.environment.block.number
308- }
309-
310- /**
311- * Get the block’s difficulty.
312- * @return {integer}
313- */
314- getBlockDifficulty (offset) {
315- this.takeGas(2)
316-
317- this.setMemory(offset, U256_SIZE_BYTES, this.environment.block.difficulty.toMemory())
318- }
319-
320- /**
321- * Get the block’s gas limit.
322- * @return {integer}
323- */
324- getBlockGasLimit () {
325- this.takeGas(2)
326-
327- return this.environment.block.gasLimit
328- }
329-
330- /**
331- * Creates a new log in the current environment
332- * @param {integer} dataOffset the offset in memory to load the memory
333- * @param {integer} length the data length
334- * @param {integer} number of topics
335- */
336- log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
337- if (numberOfTopics < 0 || numberOfTopics > 4) {
338- throw new Error('Invalid numberOfTopics')
339- }
340-
341- this.takeGas(375 + length * 8 + numberOfTopics * 375)
342-
343- const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
344- const topics = []
345-
346- if (numberOfTopics > 0) {
347- topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
348- }
349-
350- if (numberOfTopics > 1) {
351- topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
352- }
353-
354- if (numberOfTopics > 2) {
355- topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
356- }
357-
358- if (numberOfTopics > 3) {
359- topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
360- }
361-
362- this.environment.logs.push({
363- data: data,
364- topics: topics
365- })
366- }
367-
368- /**
369- * Creates a new contract with a given value.
370- * @param {integer} valueOffset the offset in memory to the value from
371- * @param {integer} dataOffset the offset to load the code for the new contract from
372- * @param {integer} length the data length
373- * @param (integer} resultOffset the offset to write the new contract address to
374- * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
375- */
376- create (valueOffset, dataOffset, length, resultOffset) {
377- this.takeGas(32000)
378-
379- const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
380- if (length) {
381- const data = this.getMemory(dataOffset, length).slice(0)
382- // const [errorCode, address] = this.environment.create(value, data)
383- }
384- let address
385- if (value.gt(this.environment.value)) {
386- address = new Address()
387- } else {
388- address = new Address('0x945304eb96065b2a98b57a48a06ae28d285a71b5')
389- }
390- this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address.toMemory())
391- // return errorCode
392- }
393-
394- /**
395- * Sends a message with arbiatary data to a given address path
396- * @param {integer} addressOffset the offset to load the address path from
397- * @param {integer} valueOffset the offset to load the value from
398- * @param {integer} dataOffset the offset to load data from
399- * @param {integer} dataLength the length of data
400- * @param {integer} resultOffset the offset to store the result data at
401- * @param {integer} resultLength
402- * @param {integer} gas
403- * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
404- */
405- _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
406- const gas = from64bit(gasHigh, gasLow)
407- this.takeGas(40 + gas)
408-
409- // Load the params from mem
410- const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
411- const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
412- const data = this.getMemory(dataOffset, dataLength).slice(0)
413-
414- // Special case for calling into empty account
415- if (!this.environment.isAccountPresent(address)) {
416- this.takeGas(25000)
417- }
418-
419- // Special case for non-zero value
420- if (!value.isZero()) {
421- this.takeGas(9000)
422- }
423-
424- const [errorCode, result] = this.environment.call(gas, address, value, data)
425- this.setMemory(resultOffset, resultLength, result)
426- return errorCode
427- }
428-
429- /**
430- * Message-call into this account with an alternative account’s code.
431- * @param {integer} addressOffset the offset to load the address path from
432- * @param {integer} valueOffset the offset to load the value from
433- * @param {integer} dataOffset the offset to load data from
434- * @param {integer} dataLength the length of data
435- * @param {integer} resultOffset the offset to store the result data at
436- * @param {integer} resultLength
437- * @param {integer} gas
438- * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
439- */
440- callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
441- // FIXME: count properly
442- this.takeGas(40)
443-
444- // Load the params from mem
445- const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
446- const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
447- const data = this.getMemory(dataOffset, dataLength).slice(0)
448- const [errorCode, result] = this.environment.callCode(gas, address, value, data)
449- this.setMemory(resultOffset, resultLength, result)
450- return errorCode
451- }
452-
453- /**
454- * Message-call into this account with an alternative account’s code, but
455- * persisting the current values for sender and value.
456- * @param {integer} gas
457- * @param {integer} addressOffset the offset to load the address path from
458- * @param {integer} valueOffset the offset to load the value from
459- * @param {integer} dataOffset the offset to load data from
460- * @param {integer} dataLength the length of data
461- * @param {integer} resultOffset the offset to store the result data at
462- * @param {integer} resultLength
463- * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
464- */
465- callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
466- // FIXME: count properly
467- this.takeGas(40)
468-
469- const data = this.getMemory(dataOffset, dataLength).slice(0)
470- const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
471- const [errorCode, result] = this.environment.callDelegate(gas, address, data)
472- this.setMemory(resultOffset, resultLength, result)
473- return errorCode
474- }
475-
476- /**
477- * store a value at a given path in long term storage which are both loaded
478- * from Memory
479- * @param {interger} pathOffest the memory offset to load the the path from
480- * @param {interger} valueOffset the memory offset to load the value from
481- */
482- storageStore (pathOffset, valueOffset) {
483- const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
484- // copy the value
485- const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
486- const oldValue = this.environment.state.get(path)
487- const valIsZero = value.every((i) => i === 0)
488-
489- this.takeGas(5000)
490-
491- // write
492- if (!valIsZero && !oldValue) {
493- this.takeGas(15000)
494- }
495-
496- // delete
497- if (valIsZero && oldValue) {
498- this.environment.gasRefund += 15000
499- this.environment.state.delete(path)
500- } else {
501- this.environment.state.set(path, value)
502- }
503- }
504-
505- /**
506- * reterives a value at a given path in long term storage
507- * @param {interger} pathOffest the memory offset to load the the path from
508- * @param {interger} resultOffset the memory offset to load the value from
509- */
510- storageLoad (pathOffset, resultOffset) {
511- this.takeGas(50)
512-
513- const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
514- const result = this.environment.state.get(path) || new Uint8Array(32)
515- this.setMemory(resultOffset, U256_SIZE_BYTES, result)
516- }
517-
518- /**
519- * Halt execution returning output data.
520- * @param {integer} offset the offset of the output data.
521- * @param {integer} length the length of the output data.
522- */
523- return (offset, length) {
524- if (length) {
525- this.environment.returnValue = this.getMemory(offset, length).slice(0)
526- }
527- }
528-
529- /**
530- * Halt execution and register account for later deletion giving the remaining
531- * balance to an address path
532- * @param {integer} offset the offset to load the address from
533- */
534- selfDestruct (addressOffset) {
535- this.environment.selfDestruct = true
536- this.environment.selfDestructAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
537- this.environment.gasRefund += 24000
538- }
539-
540- getMemory (offset, length) {
541- return new Uint8Array(this.module.exports.memory, offset, length)
542- }
543-
544- setMemory (offset, length, value) {
545- const memory = new Uint8Array(this.module.exports.memory, offset, length)
546- memory.set(value)
547- }
548-
549- /*
550- * Takes gas from the tank. Only needs to check if there's gas left to be taken,
551- * because every caller of this method is trusted.
552- */
553- takeGas (amount) {
554- if (this.environment.gasLeft < amount) {
555- throw new Error('Ran out of gas')
556- }
557- this.environment.gasLeft -= amount
558- }
559-}
560-
561-// converts a 64 bit number to a JS number
562-function from64bit (high, low) {
563- if (high < 0) {
564- // convert from a 32-bit two's compliment
565- high = 0x100000000 - high
566- }
567- if (low < 0) {
568- // convert from a 32-bit two's compliment
569- low = 0x100000000 - low
570- }
571- // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
572- return (high * 4294967296) + low
573-}
precompile.jsView
@@ -1,29 +1,0 @@
1-// const evm2wasm = require('evm2wasm')
2-// const metering = require('wasm-metering')
3-
4-function craftResponse (status, msg) {
5- // NOTE: why does this has to be so hard?
6- return Uint8Array.from(Buffer.concat([ new Buffer([ status ]), new Buffer(msg) ]))
7-}
8-
9-module.exports.meteringInjector = function (call) {
10- console.log('Executing metering injector')
11- return {
12- // returnValue: metering.injectWAST(call.data, 2).slice(0)
13- returnValue: craftResponse(0, call.data)
14- }
15-}
16-
17-module.exports.transcompiler = function (call) {
18- console.log('Executing transcompiler')
19- return {
20- // returnValue: evm2wasm.compileEVM(call.data).slice(0)
21- returnValue: craftResponse(1, 'Code not supported: ' + Buffer.from(call.data.slice(0, 8)).toString('hex') + '...')
22- }
23-}
24-
25-module.exports.identity = function (call) {
26- return {
27- returnValue: call.data.slice(0)
28- }
29-}
test.jsView
@@ -1,41 +1,0 @@
1-const Kernel = require('./index.js')
2-const Environment = require('./environment.js')
3-
4-const environment = new Environment()
5-const kernel = new Kernel(environment)
6-
7-const Address = require('./address.js')
8-const U256 = require('./u256.js')
9-
10-const fs = require('fs')
11-
12-environment.addAccount(new Address('0x1234567890134561111123412341234123412341'), { balance: new U256('100000000000000000') })
13-// environment.addAccount(new Address('0x4123412341234123411234567890134561111123'), { code: Uint8Array.from(fs.readFileSync('identity.wasm')) })
14-
15-environment.addAccount(new Address('0xbe862ad9abfe6f22bcb087716c7d89a26051f74c'), { balance: new U256('100000000000000000') })
16-var tx = new Buffer('f8e380648203e88080b8977f4e616d65526567000000000000000000000000000000000000000000000000003057307f4e616d6552656700000000000000000000000000000000000000000000000000573360455760415160566000396000f20036602259604556330e0f600f5933ff33560f601e5960003356576000335700604158600035560f602b590033560f603659600033565733600035576000353357001ca0b8b9fedc076110cd002224a942e9d7099e4a626ebf66cd9301fc18e2c1181806a04e270be511d42189baf14599eb8d6eb5037ab105032dd3e0fa05b43dad4cb4c2', 'hex')
17-console.log(kernel.runTx(tx, environment))
18-
19-// deploy contract
20-let ret = kernel.runTx({
21- nonce: new U256(3),
22- from: new Address('0x1234567890134561111123412341234123412341'),
23- to: new Address('0x0000000000000000000000000000000000000000'),
24- value: new U256('100'),
25- gasLimit: new U256('1000000000'),
26- gasPrice: new U256(1),
27- data: Uint8Array.from(fs.readFileSync('identity.wasm'))
28-}, environment)
29-console.log('Account created: ' + ret.accountCreated)
30-
31-ret = kernel.runTx({
32- nonce: new U256(4),
33- from: new Address('0x1234567890134561111123412341234123412341'),
34- to: ret.accountCreated, // new Address('0x4123412341234123411234567890134561111123'),
35- value: new U256('100'),
36- gasLimit: new U256('1000000000'),
37- gasPrice: new U256(1),
38- data: Uint8Array.from(new Buffer('spartaaaa'))
39-}, environment)
40-
41-console.log('Return value: ' + ret.returnValue)
precompiles/create.jsView
@@ -1,0 +1,55 @@
1+ // createHandler (create) {
2+ // let code = create.data
3+
4+ // // Inject metering
5+ // if (Utils.isWASMCode(code)) {
6+ // // FIXME: decide if these are the right values here: from: 0, gasLimit: 0, value: 0
7+ // code = this.callHandler({
8+ // from: Address.zero(),
9+ // to: meteringContract,
10+ // gasLimit: 0,
11+ // value: new U256(0),
12+ // data: code
13+ // }).returnValue
14+
15+ // if (code[0] === 0) {
16+ // code = code.slice(1)
17+ // } else {
18+ // throw new Error('Metering injection failed: ' + Buffer.from(code).slice(1).toString())
19+ // }
20+ // }
21+
22+ // let account = this.environment.state.get(create.from.toString())
23+ // if (!account) {
24+ // throw new Error('Account not found: ' + create.from.toString())
25+ // }
26+
27+ // let address = Utils.newAccountAddress(create.from, account.get('nonce'))
28+
29+ // this.environment.addAccount(address.toString(), {
30+ // balance: create.value,
31+ // code: code
32+ // })
33+
34+ // // Run code and take return value as contract code
35+ // // FIXME: decide if these are the right values here: value: 0, data: ''
36+ // code = this.messageHandler({
37+ // from: create.from,
38+ // to: address,
39+ // gasLimit: create.gasLimit,
40+ // value: new U256(0),
41+ // data: new Uint8Array()
42+ // }).returnValue
43+
44+ // // FIXME: special handling for selfdestruct
45+
46+ // this.environment.state.get(address.toString()).set('code', code)
47+
48+ // return {
49+ // executionOutcome: 1, // success
50+ // gasLeft: new U256(this.environment.gasLeft),
51+ // gasRefund: new U256(this.environment.gasRefund),
52+ // accountCreated: address,
53+ // logs: this.environment.logs
54+ // }
55+ // }
precompiles/precompile.jsView
@@ -1,0 +1,29 @@
1+// const evm2wasm = require('evm2wasm')
2+// const metering = require('wasm-metering')
3+
4+function craftResponse (status, msg) {
5+ // NOTE: why does this has to be so hard?
6+ return Uint8Array.from(Buffer.concat([ new Buffer([ status ]), new Buffer(msg) ]))
7+}
8+
9+module.exports.meteringInjector = function (call) {
10+ console.log('Executing metering injector')
11+ return {
12+ // returnValue: metering.injectWAST(call.data, 2).slice(0)
13+ returnValue: craftResponse(0, call.data)
14+ }
15+}
16+
17+module.exports.transcompiler = function (call) {
18+ console.log('Executing transcompiler')
19+ return {
20+ // returnValue: evm2wasm.compileEVM(call.data).slice(0)
21+ returnValue: craftResponse(1, 'Code not supported: ' + Buffer.from(call.data.slice(0, 8)).toString('hex') + '...')
22+ }
23+}
24+
25+module.exports.identity = function (call) {
26+ return {
27+ returnValue: call.data.slice(0)
28+ }
29+}
runBlock.jsView
@@ -1,0 +1,11 @@
1+// run block; the block message handler:w
2+const Environment = require('./environment')
3+
4+module.exports = class runBlock {
5+ constuctor (block, environment = new Environment()) {
6+ // verify block then run each tx
7+ block.tx.forEach((tx) => {
8+ this.runTx(tx, environment)
9+ })
10+ }
11+}
runTx.jsView
@@ -1,0 +1,77 @@
1+
2+ // run tx; the tx message handler
3+ // runTx (tx, environment = new Environment()) {
4+ // this.environment = environment
5+
6+ // if (Buffer.isBuffer(tx) || typeof tx === 'string') {
7+ // tx = new Transaction(tx)
8+ // if (!tx.valid) {
9+ // throw new Error('Invalid transaction signature')
10+ // }
11+ // }
12+
13+ // // look up sender
14+ // let fromAccount = this.environment.state.get(tx.from.toString())
15+ // if (!fromAccount) {
16+ // throw new Error('Sender account not found: ' + tx.from.toString())
17+ // }
18+
19+ // if (fromAccount.get('nonce').gt(tx.nonce)) {
20+ // throw new Error(`Invalid nonce: ${fromAccount.get('nonce')} > ${tx.nonce}`)
21+ // }
22+
23+ // fromAccount.set('nonce', fromAccount.get('nonce').add(new U256(1)))
24+
25+ // let isCreation = false
26+
27+ // // Special case: contract deployment
28+ // if (tx.to.isZero() && (tx.data.length !== 0)) {
29+ // console.log('This is a contract deployment transaction')
30+ // isCreation = true
31+ // }
32+
33+ // // This cost will not be refunded
34+ // let txCost = 21000 + (isCreation ? 32000 : 0)
35+ // tx.data.forEach((item) => {
36+ // if (item === 0) {
37+ // txCost += 4
38+ // } else {
39+ // txCost += 68
40+ // }
41+ // })
42+
43+ // if (tx.gasLimit.lt(new U256(txCost))) {
44+ // throw new Error(`Minimum transaction gas limit not met: ${txCost}`)
45+ // }
46+
47+ // if (fromAccount.get('balance').lt(tx.gasLimit.mul(tx.gasPrice))) {
48+ // throw new Error(`Insufficient account balance: ${fromAccount.get('balance').toString()} < ${tx.gasLimit.mul(tx.gasPrice).toString()}`)
49+ // }
50+
51+ // // deduct gasLimit * gasPrice from sender
52+ // fromAccount.set('balance', fromAccount.get('balance').sub(tx.gasLimit.mul(tx.gasPrice)))
53+
54+ // const handler = isCreation ? this.createHandler.bind(this) : this.callHandler.bind(this)
55+ // let ret = handler({
56+ // to: tx.to,
57+ // from: tx.from,
58+ // gasLimit: tx.gasLimit - txCost,
59+ // value: tx.value,
60+ // data: tx.data
61+ // })
62+
63+ // // refund unused gas
64+ // if (ret.executionOutcome === 1) {
65+ // fromAccount.set('balance', fromAccount.get('balance').add(tx.gasPrice.mul(ret.gasLeft.add(ret.gasRefund))))
66+ // }
67+
68+ // // save new state?
69+
70+ // return {
71+ // executionOutcome: ret.executionOutcome,
72+ // accountCreated: isCreation ? ret.accountCreated : undefined,
73+ // returnValue: isCreation ? undefined : ret.returnValue,
74+ // gasLeft: ret.gasLeft,
75+ // logs: ret.logs
76+ // }
77+ // }
transaction.jsView
@@ -1,49 +1,0 @@
1-//
2-// This class parses a serialised Ethereum transaction
3-//
4-// The input is a Buffer.
5-//
6-const Address = require('./address.js')
7-const U256 = require('./u256.js')
8-const OldTx = require('ethereumjs-tx')
9-
10-module.exports = class Transaction {
11- constructor (tx) {
12- this._tx = new OldTx(tx)
13- }
14-
15- get valid () {
16- return this._tx.verifySignature()
17- }
18-
19- get nonce () {
20- return new U256(this._tx.nonce)
21- }
22-
23- get gasPrice () {
24- return new U256(this._tx.gasPrice)
25- }
26-
27- get gasLimit () {
28- return new U256(this._tx.gasLimit)
29- }
30-
31- get value () {
32- return new U256(this._tx.value)
33- }
34-
35- get data () {
36- return Uint8Array.from(this._tx.data)
37- }
38-
39- get from () {
40- return new Address(this._tx.getSenderAddress())
41- }
42-
43- get to () {
44- if (this._tx.to.length === 0) {
45- return new Address('0x0000000000000000000000000000000000000000')
46- }
47- return new Address(this._tx.to)
48- }
49-}
u256.jsView
@@ -1,65 +1,0 @@
1-const BN = require('bn.js')
2-const ethUtil = require('ethereumjs-util')
3-
4-module.exports = class U256 {
5- constructor (value) {
6- // bn.js still doesn't support hex prefixes...
7- if ((typeof value === 'string') && ethUtil.isHexPrefixed(value)) {
8- this._value = new BN(ethUtil.stripHexPrefix(value), 16)
9- } else {
10- this._value = new BN(value, 10)
11- }
12- }
13-
14- // This assumes Uint8Array in LSB (WASM code)
15- static fromMemory (value) {
16- return new U256(new BN(value, 16, 'le'))
17- }
18-
19- // This assumes Uint8Array in LSB (WASM code)
20- toMemory (width) {
21- return this._value.toBuffer('le', width || 32)
22- }
23-
24- toString (radix = 10) {
25- if (radix === 16) {
26- return '0x' + this._value.toString(16)
27- }
28- return this._value.toString(radix)
29- }
30-
31- toBuffer (width) {
32- if (width <= 0 || width > 32) {
33- throw new Error('Invalid U256 width')
34- }
35- return this._value.toBuffer('be', width || 32)
36- }
37-
38- isZero () {
39- return this._value.isZero()
40- }
41-
42- sub (u256) {
43- return new U256(this._value.sub(u256._value))
44- }
45-
46- add (u256) {
47- return new U256(this._value.add(u256._value))
48- }
49-
50- mul (u256) {
51- return new U256(this._value.mul(u256._value))
52- }
53-
54- div (u256) {
55- return new U256(this._value.div(u256._value))
56- }
57-
58- lt (u256) {
59- return this._value.lt(u256._value)
60- }
61-
62- gt (u256) {
63- return this._value.gt(u256._value)
64- }
65-}
utils.jsView
@@ -1,14 +1,0 @@
1-const ethUtil = require('ethereumjs-util')
2-const Address = require('./address.js')
3-
4-var Utils = {}
5-
6-Utils.isWASMCode = function (code) {
7- return code.slice(0, 4).toString() === new Uint8Array([0, 0x61, 0x73, 0x6d]).toString()
8-}
9-
10-Utils.newAccountAddress = function (sender, nonce) {
11- return new Address('0x' + ethUtil.generateAddress(sender.toString(), nonce.toString()).toString('hex'))
12-}
13-
14-module.exports = Utils
vm.jsView
@@ -1,0 +1,55 @@
1+module.exports = class VM {
2+ /**
3+ * The interface API is the api the exposed to interfaces. All queries about
4+ * the enviroment and call to the kernel go through this API
5+ */
6+ constructor (code) {
7+ this._module = WebAssembly.Module(code)
8+ }
9+
10+ /**
11+ * Runs the core VM with a given environment and imports
12+ */
13+ run (environment, imports) {
14+ this._environment = environment
15+ // TODO, delete the instance once done.
16+ const instance = this._instance = WebAssembly.Instance(this._module, imports)
17+ if (instance.exports.main) {
18+ instance.exports.main()
19+ }
20+ return this.onDone()
21+ }
22+
23+ /**
24+ * returns a promise that resolves when the wasm instance is done running
25+ */
26+ async onDone () {
27+ let prevOps
28+ while (prevOps !== this._opsQueue) {
29+ prevOps = this._opsQueue
30+ await this._opsQueue
31+ }
32+ }
33+
34+ /**
35+ * addes an aync operation to the operations queue
36+ */
37+ pushOpsQueue (promise, callbackIndex, intefaceCallback) {
38+ this._opsQueue = Promise.all([this._opsQueue, promise]).then(values => {
39+ const result = intefaceCallback(values.pop())
40+ this._instance.exports[callbackIndex.toString()](result)
41+ })
42+ }
43+
44+ sendMessage (message) {
45+
46+ }
47+
48+ get environment () {
49+ return this._environment
50+ }
51+
52+ get memory () {
53+ return this._instance.exports.memory
54+ }
55+}

Built with git-ssb-web