Commit 3c4a2d1f6298f7d8b4e012acbb8edd52f1bf5203
Merge pull request #64 from ewasm/refractor_async_testing
Implements the Async Interfacewanderer 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.js | ||
---|---|---|
@@ -5,17 +5,17 @@ | ||
5 | 5 | * This expose some functions that can help with debugging wast |
6 | 6 | */ |
7 | 7 | |
8 | 8 | module.exports = class DebugInterface { |
9 | - constructor (environment) { | |
10 | - this.environment = environment | |
9 | + constructor (kernel) { | |
10 | + this.kernel = kernel | |
11 | 11 | } |
12 | 12 | |
13 | - setModule (mod) { | |
14 | - this.module = mod | |
13 | + static get name () { | |
14 | + return 'debug' | |
15 | 15 | } |
16 | 16 | |
17 | - get exportTable () { | |
17 | + get exports () { | |
18 | 18 | return { |
19 | 19 | 'print': function (a) { |
20 | 20 | console.log(a) |
21 | 21 | }, |
@@ -31,17 +31,18 @@ | ||
31 | 31 | const opcode = opcodes(op) |
32 | 32 | if (opcode.number) { |
33 | 33 | opcode.name += opcode.number |
34 | 34 | } |
35 | - console.error(`op: ${opcode.name} gas: ${this.environment.gasLeft}`) | |
35 | + console.error(`op: ${opcode.name} gas: ${this.kernel.environment.gasLeft} sp: ${sp}`) | |
36 | 36 | console.log('-------------stack--------------') |
37 | 37 | 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')}`) | |
39 | 39 | } |
40 | 40 | }.bind(this) |
41 | 41 | } |
42 | 42 | } |
43 | 43 | |
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() | |
46 | 47 | } |
47 | 48 | } |
environment.js | ||
---|---|---|
@@ -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 | |
4 | 7 | const fakeBlockChain = require('./fakeBlockChain.js') |
5 | 8 | |
6 | 9 | module.exports = class Environment { |
7 | - constructor (data) { | |
10 | + constructor (data = {}) { | |
8 | 11 | const defaults = { |
9 | 12 | block: new Block(), |
10 | 13 | blockchain: fakeBlockChain, |
11 | 14 | // gas tank |
@@ -24,54 +27,44 @@ | ||
24 | 27 | logs: [], |
25 | 28 | selfDestruct: false, |
26 | 29 | selfDestructAddress: new Address('0x0000000000000000000000000000000000000000'), |
27 | 30 | // more output calls |
28 | - returnValue: new Uint8Array() | |
31 | + returnValue: new Uint8Array(), | |
32 | + state: new Vertex({store: new Store()}) | |
29 | 33 | } |
30 | - | |
31 | - this.state = new Map() | |
32 | - | |
33 | - Object.assign(this, defaults, data || {}) | |
34 | + Object.assign(this, defaults, data) | |
34 | 35 | } |
35 | 36 | |
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 | - | |
45 | 37 | 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 | + // } | |
52 | 44 | } |
53 | 45 | |
54 | 46 | 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 | + // } | |
61 | 53 | } |
62 | 54 | |
63 | 55 | 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 | + // } | |
70 | 62 | } |
71 | 63 | |
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() | |
74 | 67 | } |
75 | 68 | |
76 | 69 | set createHandler (value) { |
77 | 70 | this.createhandler = value |
@@ -88,9 +81,9 @@ | ||
88 | 81 | } |
89 | 82 | |
90 | 83 | call (gas, address, value, data) { |
91 | 84 | // FIXME: create a child environment here |
92 | - const ret = this.callhandler({ | |
85 | + const ret = this.root.messagehandler({ | |
93 | 86 | from: this.address, |
94 | 87 | to: address, |
95 | 88 | gasLimit: gas, |
96 | 89 | value: value, |
fakeBlockChain.js | ||
---|---|---|
@@ -1,12 +1,13 @@ | ||
1 | 1 | const utils = require('ethereumjs-util') |
2 | +const U256 = require('./deps/u256.js') | |
2 | 3 | |
3 | 4 | module.exports = { |
4 | 5 | getBlock: (n) => { |
5 | 6 | const hash = utils.sha3(new Buffer(utils.bufferToInt(n).toString())) |
6 | 7 | const block = { |
7 | 8 | hash: () => { |
8 | - return hash | |
9 | + return new U256(hash) | |
9 | 10 | } |
10 | 11 | } |
11 | 12 | return block |
12 | 13 | } |
index.js | ||
---|---|---|
@@ -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') | |
15 | 2 | // 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') | |
20 | 5 | 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') | |
27 | 6 | |
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) | |
31 | 11 | |
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) | |
59 | 16 | } |
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 | |
64 | 17 | |
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 | + }, {}) | |
72 | 26 | } |
73 | - return instance | |
74 | 27 | } |
75 | 28 | |
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) | |
155 | 36 | } |
156 | 37 | |
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 | |
169 | 45 | } |
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) | |
170 | 52 | } |
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 | - } | |
199 | 53 | } |
200 | 54 | |
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 | |
240 | 61 | }) |
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 | - } | |
276 | 62 | } |
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 () {} | |
288 | 63 | } |
opcodes.js | ||
---|---|---|
@@ -57,10 +57,10 @@ | ||
57 | 57 | 0x50: ['POP', 2, 1, 0, false], |
58 | 58 | 0x51: ['MLOAD', 3, 1, 1, false], |
59 | 59 | 0x52: ['MSTORE', 3, 2, 0, false], |
60 | 60 | 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], | |
63 | 63 | 0x56: ['JUMP', 8, 1, 0, false], |
64 | 64 | 0x57: ['JUMPI', 10, 2, 0, false], |
65 | 65 | 0x58: ['PC', 2, 0, 1, false], |
66 | 66 | 0x59: ['MSIZE', 2, 0, 1, false], |
package.json | ||
---|---|---|
@@ -3,9 +3,10 @@ | ||
3 | 3 | "version": "0.0.0", |
4 | 4 | "description": "This is a JS prototype of the eWASM kernal.", |
5 | 5 | "scripts": { |
6 | 6 | "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" | |
8 | 9 | }, |
9 | 10 | "repository": { |
10 | 11 | "type": "git", |
11 | 12 | "url": "git+https://github.com/ewasm/ewasm-kernel.git" |
@@ -23,9 +24,9 @@ | ||
23 | 24 | "author": "mjbecze <mjbecze@gmail.com>", |
24 | 25 | "contributors": "Alex Beregszaszi <alex@rtfs.hu>", |
25 | 26 | "license": "MPL-2.0", |
26 | 27 | "devDependencies": { |
27 | - "standard": "^8.5.0", | |
28 | + "standard": "*", | |
28 | 29 | "tape": "^4.5.1" |
29 | 30 | }, |
30 | 31 | "standard": { |
31 | 32 | "ignore": [ |
@@ -38,7 +39,8 @@ | ||
38 | 39 | "dependencies": { |
39 | 40 | "bn.js": "^4.11.6", |
40 | 41 | "ethereumjs-block": "^1.2.2", |
41 | 42 | "ethereumjs-tx": "^1.1.2", |
43 | + "merkle-trie": "0.0.0", | |
42 | 44 | "ethereumjs-util": "^5.0.0" |
43 | 45 | } |
44 | 46 | } |
testEnvironment.js | ||
---|---|---|
@@ -1,82 +1,8 @@ | ||
1 | 1 | 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') | |
6 | 3 | |
7 | 4 | 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() | |
81 | 7 | } |
82 | 8 | } |
tests/interface/address.json | ||
---|---|---|
@@ -1,3 +1,13 @@ | ||
1 | 1 | { |
2 | + "callValue": "0x00", | |
3 | + "callData": "0x00", | |
4 | + "state": { | |
5 | + "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { | |
6 | + "balance": "0x056bc75e2d63100000", | |
7 | + "code": "0x00", | |
8 | + "nonce": "0x00" | |
9 | + } | |
10 | + }, | |
11 | + "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
2 | 12 | "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" |
3 | 13 | } |
tests/interface/address.wast | ||
---|---|---|
@@ -2,15 +2,15 @@ | ||
2 | 2 | (module |
3 | 3 | (memory 1) |
4 | 4 | |
5 | 5 | (import $address "ethereum" "getAddress" (param i32)) |
6 | - (export "test" 0) | |
6 | + (export "main" 0) | |
7 | 7 | (export "a" memory) |
8 | 8 | (func |
9 | 9 | (block |
10 | 10 | ;; loads the address into memory |
11 | 11 | (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)) | |
13 | 13 | (return) |
14 | 14 | ) |
15 | 15 | (unreachable) |
16 | 16 | ) |
tests/interface/balance.json | ||
---|---|---|
@@ -1,9 +1,13 @@ | ||
1 | 1 | { |
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" | |
6 | 9 | } |
7 | - ] | |
8 | - ] | |
10 | + }, | |
11 | + "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | + "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" | |
9 | 13 | } |
tests/interface/balance.wast | ||
---|---|---|
@@ -1,17 +1,19 @@ | ||
1 | 1 | ;; address of 5d48c1018904a172886829bbbd9c6f4a2d06c47b has a balance of 0x056bc75e2d63100000 (100 ETH) |
2 | 2 | (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)) | |
5 | 5 | (export "a" memory) |
6 | - (export "test" 0) | |
6 | + (export "main" 0) | |
7 | 7 | (func |
8 | + (call_import $balance (i32.const 0) (i32.const 0) (i32.const 1)) | |
9 | + ) | |
10 | + | |
11 | + (export "1" 1) | |
12 | + (func | |
8 | 13 | (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) | |
14 | 16 | ) |
15 | 17 | (unreachable) |
16 | 18 | ) |
17 | 19 | ) |
tests/interface/basic_gas_ops.json | ||
---|---|---|
@@ -1,3 +1,14 @@ | ||
1 | 1 | { |
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", | |
2 | 13 | "gasLeft": 1000 |
3 | 14 | } |
tests/interface/call.json | ||
---|---|---|
@@ -1,3 +1,13 @@ | ||
1 | 1 | { |
2 | + "callValue": "0x00", | |
3 | + "callData": "0x00", | |
4 | + "state": { | |
5 | + "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { | |
6 | + "balance": "0x056bc75e2d63100000", | |
7 | + "code": "0x00", | |
8 | + "nonce": "0x00" | |
9 | + } | |
10 | + }, | |
11 | + "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
2 | 12 | "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" |
3 | 13 | } |
tests/interface/call.wast | ||
---|---|---|
@@ -1,11 +1,11 @@ | ||
1 | 1 | ;; starts with an address of 5d48c1018904a172886829bbbd9c6f4a2d06c47b |
2 | 2 | (module |
3 | 3 | (memory 1) |
4 | 4 | |
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)) | |
7 | 6 | (export "a" memory) |
7 | + (export "main" 0) | |
8 | 8 | (func |
9 | 9 | (block |
10 | 10 | ;; Memory layout: |
11 | 11 | ;; 0 - 20 bytes: address (4) |
@@ -13,13 +13,16 @@ | ||
13 | 13 | ;; 52 - 56 bytes: data (0x42004200) |
14 | 14 | ;; 56 - 60 bytes: result |
15 | 15 | (i32.store (i32.const 0) (i32.const 0x4)) |
16 | 16 | (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)) | |
23 | 18 | ) |
24 | 19 | ) |
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 | + ) | |
25 | 28 | ) |
tests/interface/callDataCopy.json | ||
---|---|---|
@@ -1,3 +1,13 @@ | ||
1 | 1 | { |
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" | |
3 | 13 | } |
tests/interface/callDataCopy.wast | ||
---|---|---|
@@ -3,13 +3,12 @@ | ||
3 | 3 | (memory 1) |
4 | 4 | (import $callDataCopy "ethereum" "callDataCopy" (param i32 i32 i32)) |
5 | 5 | |
6 | 6 | (export "memory" memory) |
7 | - (export "test" 0) | |
7 | + (export "main" 0) | |
8 | 8 | (func |
9 | 9 | (block |
10 | 10 | (call_import $callDataCopy (i32.const 0) (i32.const 0) (i32.const 8)) |
11 | - | |
12 | 11 | (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x2065726120756f59)) |
13 | 12 | (return) |
14 | 13 | ) |
15 | 14 | (unreachable) |
tests/interface/callDataSize.json | ||
---|---|---|
@@ -1,3 +1,13 @@ | ||
1 | 1 | { |
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" | |
3 | 13 | } |
tests/interface/callDataSize.wast | ||
---|---|---|
@@ -1,9 +1,9 @@ | ||
1 | 1 | (module |
2 | 2 | (memory 1) |
3 | 3 | (import $callDataSize "ethereum" "getCallDataSize" (result i64)) |
4 | 4 | |
5 | - (export "test" 0) | |
5 | + (export "main" 0) | |
6 | 6 | (func |
7 | 7 | (block |
8 | 8 | (if (i64.eq (call_import $callDataSize) (i64.const 277)) |
9 | 9 | (return) |
tests/interface/callValue.json | ||
---|---|---|
@@ -1,3 +1,13 @@ | ||
1 | 1 | { |
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" | |
3 | 13 | } |
tests/interface/callValue.wast | ||
---|---|---|
@@ -3,16 +3,14 @@ | ||
3 | 3 | (memory 1) |
4 | 4 | (import $callValue "ethereum" "getCallValue" (param i32)) |
5 | 5 | |
6 | 6 | (export "a" memory) |
7 | - (export "test" 0) | |
7 | + (export "main" 0) | |
8 | 8 | (func |
9 | 9 | (block |
10 | 10 | (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) | |
15 | 13 | ) |
16 | 14 | (unreachable) |
17 | 15 | ) |
18 | 16 | ) |
tests/interface/caller.json | ||
---|---|---|
@@ -1,3 +1,14 @@ | ||
1 | 1 | { |
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" | |
3 | 14 | } |
tests/interface/caller.wast | ||
---|---|---|
@@ -2,15 +2,15 @@ | ||
2 | 2 | (module |
3 | 3 | (memory 1) |
4 | 4 | (import $caller "ethereum" "getCaller" (param i32)) |
5 | 5 | |
6 | - (export "test" 0) | |
6 | + (export "main" 0) | |
7 | 7 | (export "a" memory) |
8 | 8 | (func |
9 | 9 | (block |
10 | 10 | ;; loads the caller into memory |
11 | 11 | (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)) | |
13 | 13 | (return) |
14 | 14 | ) |
15 | 15 | (unreachable) |
16 | 16 | ) |
tests/interface/coinbase.json | ||
---|---|---|
@@ -1,5 +1,13 @@ | ||
1 | 1 | { |
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" | |
5 | 13 | } |
tests/interface/coinbase.wast | ||
---|---|---|
@@ -2,15 +2,15 @@ | ||
2 | 2 | (module |
3 | 3 | (memory 1) |
4 | 4 | |
5 | 5 | (import $coinbase "ethereum" "getBlockCoinbase" (param i32)) |
6 | - (export "test" 0) | |
6 | + (export "main" 0) | |
7 | 7 | (export "a" memory) |
8 | 8 | (func |
9 | 9 | (block |
10 | 10 | ;; loads the coinbase into memory |
11 | 11 | (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)) | |
13 | 13 | (return) |
14 | 14 | ) |
15 | 15 | (unreachable) |
16 | 16 | ) |
tests/interface/origin.json | ||
---|---|---|
@@ -1,3 +1,14 @@ | ||
1 | 1 | { |
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" | |
3 | 14 | } |
tests/interface/origin.wast | ||
---|---|---|
@@ -2,15 +2,15 @@ | ||
2 | 2 | (module |
3 | 3 | (memory 1) |
4 | 4 | (import $origin "ethereum" "getTxOrigin" (param i32)) |
5 | 5 | |
6 | - (export "test" 0) | |
6 | + (export "main" 0) | |
7 | 7 | (export "a" memory) |
8 | 8 | (func |
9 | 9 | (block |
10 | 10 | ;; loads the address into memory |
11 | 11 | (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)) | |
13 | 13 | (return) |
14 | 14 | ) |
15 | 15 | (unreachable) |
16 | 16 | ) |
tests/interface/sstore.json | ||
---|---|---|
@@ -1,2 +1,14 @@ | ||
1 | 1 | { |
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" | |
2 | 14 | } |
tests/interface/sstore.wast | ||
---|---|---|
@@ -1,28 +1,33 @@ | ||
1 | 1 | ;; starts with an caller of 5d48c1018904a172886829bbbd9c6f4a2d06c47b |
2 | 2 | (module |
3 | 3 | (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)) | |
6 | 6 | |
7 | - (export "test" 0) | |
7 | + (export "main" 0) | |
8 | 8 | (export "a" memory) |
9 | 9 | (func |
10 | 10 | (local $temp i64) |
11 | 11 | (block |
12 | 12 | ;; should roundtrip store and load a value from storage |
13 | 13 | (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 | + ) | |
16 | 17 | |
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 | |
17 | 28 | (if (i64.ne (i64.load (i32.const 64)) (i64.const 173553719826446289)) |
18 | 29 | (unreachable)) |
19 | 30 | |
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)) | |
26 | 31 | ) |
27 | 32 | ) |
28 | 33 | ) |
tests/interface/address.wasm | ||
---|---|---|
@@ -1,0 +1,3 @@ | ||
1 | + asm type@ @ import ethereum | |
2 | +getAddressfunctionmemoryexport maincode + ݐ������� h | |
3 | + |
tests/interface/balance.wasm | ||
---|---|---|
@@ -1,0 +1,6 @@ | ||
1 | + asm type | |
2 | +@ @ import ethereum | |
3 | +getBalancefunctionmemoryexport | |
4 | + main1code% | |
5 | + + ��������h | |
6 | +data ]H���r�h)���oJ-�{ |
tests/interface/basic_gas_ops.wasm | ||
---|---|---|
@@ -1,0 +1,4 @@ | ||
1 | + asm type@ @ @ import' ethereumuseGasethereum | |
2 | +getGasLeftfunctionexport testcode)' �M | |
3 | + �M | |
4 | + |
tests/interface/call.wasm | ||
---|---|---|
@@ -1,0 +1,2 @@ | ||
1 | + asm type@@ @ import ethereumcallfunctionmemoryexport | |
2 | + main1code7) 3 4����3 � 48 M |
tests/interface/callDataCopy.wasm | ||
---|---|---|
@@ -1,0 +1,3 @@ | ||
1 | + asm type | |
2 | +@ @ import ethereumcallDataCopyfunctionmemoryexport maincode#! + ��Ճ��ܲ h | |
3 | + |
tests/interface/callDataSize.wasm | ||
---|---|---|
@@ -1,0 +1,2 @@ | ||
1 | + asm type@ @ import ethereumgetCallDataSizefunctionmemory export maincode �h | |
2 | + |
tests/interface/callValue.wasm | ||
---|---|---|
@@ -1,0 +1,2 @@ | ||
1 | + asm type@ @ import ethereumgetCallValuefunctionmemoryexport maincode + ��������h | |
2 | + |
tests/interface/caller.wasm | ||
---|---|---|
@@ -1,0 +1,2 @@ | ||
1 | + asm type@ @ import ethereum getCallerfunctionmemoryexport maincode + ݐ������� h | |
2 | + |
tests/interface/coinbase.wasm | ||
---|---|---|
@@ -1,0 +1,2 @@ | ||
1 | + asm type@ @ import ethereumgetBlockCoinbasefunctionmemoryexport maincode + ݐ������� h | |
2 | + |
tests/interface/origin.wasm | ||
---|---|---|
@@ -1,0 +1,2 @@ | ||
1 | + asm type@ @ import ethereumgetTxOriginfunctionmemoryexport maincode + ݐ������� h | |
2 | + |
tests/interface/sstore.wasm | ||
---|---|---|
@@ -1,0 +1,3 @@ | ||
1 | + asm type | |
2 | +@ @ import. ethereumstorageStore ethereumstorageLoadfunctionmemoryexport main12codeG ���݄ƥ�4 � � � � + ���݄ƥ�i | |
3 | + |
tests/interfaceRunner.js | ||
---|---|---|
@@ -1,52 +1,74 @@ | ||
1 | -'use strict' | |
2 | 1 | const tape = require('tape') |
3 | 2 | const fs = require('fs') |
4 | -const cp = require('child_process') | |
5 | 3 | const path = require('path') |
4 | +const Vertex = require('merkle-trie') | |
5 | +const Address = require('../deps/address') | |
6 | +const U256 = require('../deps/u256') | |
6 | 7 | |
7 | 8 | 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') | |
11 | 10 | |
12 | 11 | const dir = path.join(__dirname, '/interface') |
13 | 12 | // get the test names |
14 | 13 | 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'] | |
25 | 15 | |
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) | |
33 | 17 | |
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 | + } | |
52 | 74 | } |
tests/buildTests.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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