git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: e214b19ee2bdfeef044a0c6277beab1b4d7472bb

Files: e214b19ee2bdfeef044a0c6277beab1b4d7472bb / EVMinterface.js

20454 bytesRaw
1/**
2 * This is the Ethereum interface that is exposed to the WASM instance which
3 * enables to interact with the Ethereum Environment
4 */
5const fs = require('fs')
6const path = require('path')
7const ethUtil = require('ethereumjs-util')
8const Vertex = require('merkle-trie')
9const Address = require('./deps/address.js')
10const U256 = require('./deps/u256.js')
11
12const U128_SIZE_BYTES = 16
13const ADDRESS_SIZE_BYTES = 20
14const U256_SIZE_BYTES = 32
15
16// The interface exposed to the WebAessembly Core
17module.exports = class Interface {
18 constructor (kernel) {
19 this.kernel = kernel
20 const shimBin = fs.readFileSync(path.join(__dirname, '/wasm/interface.wasm'))
21 const shimMod = WebAssembly.Module(shimBin)
22 this.shims = WebAssembly.Instance(shimMod, {
23 'interface': {
24 'useGas': this._useGas.bind(this),
25 'getGasLeftHigh': this._getGasLeftHigh.bind(this),
26 'getGasLeftLow': this._getGasLeftLow.bind(this),
27 'call': this._call.bind(this)
28 }
29 })
30 }
31
32 static get name () {
33 return 'ethereum'
34 }
35
36 get exports () {
37 let exportMethods = [
38 // include all the public methods according to the Ethereum Environment Interface (EEI) r1
39 'getAddress',
40 'getBalance',
41 'getTxOrigin',
42 'getCaller',
43 'getCallValue',
44 'getCallDataSize',
45 'callDataCopy',
46 'callDataCopy256',
47 'getCodeSize',
48 'codeCopy',
49 'getExternalCodeSize',
50 'externalCodeCopy',
51 'getTxGasPrice',
52 'getBlockHash',
53 'getBlockCoinbase',
54 'getBlockTimestamp',
55 'getBlockNumber',
56 'getBlockDifficulty',
57 'getBlockGasLimit',
58 'log',
59 'create',
60 'callCode',
61 'callDelegate',
62 'storageStore',
63 'storageLoad',
64 'return',
65 'selfDestruct'
66 ]
67 let ret = {}
68 exportMethods.forEach((method) => {
69 ret[method] = this[method].bind(this)
70 })
71
72 // add shims
73 ret.useGas = this.shims.exports.useGas
74 ret.getGasLeft = this.shims.exports.getGasLeft
75 ret.call = this.shims.exports.call
76 return ret
77 }
78
79 setModule (mod) {
80 this.module = mod
81 }
82
83 /**
84 * Subtracts an amount to the gas counter
85 * @param {integer} amount the amount to subtract to the gas counter
86 */
87 _useGas (high, low) {
88 this.takeGas(from64bit(high, low))
89 }
90
91 /**
92 * Returns the current amount of gas
93 * @return {integer}
94 */
95 _getGasLeftHigh () {
96 return Math.floor(this.kernel.environment.gasLeft / 4294967296)
97 }
98
99 /**
100 * Returns the current amount of gas
101 * @return {integer}
102 */
103 _getGasLeftLow () {
104 return this.kernel.environment.gasLeft
105 }
106
107 /**
108 * Gets address of currently executing account and loads it into memory at
109 * the given offset.
110 * @param {integer} offset
111 */
112 getAddress (offset) {
113 this.takeGas(2)
114
115 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.address.toMemory())
116 }
117
118 /**
119 * Gets balance of the given account and loads it into memory at the given
120 * offset.
121 * @param {integer} addressOffset the memory offset to laod the address
122 * @param {integer} resultOffset
123 */
124 getBalance (addressOffset, offset, cbIndex) {
125 this.takeGas(20)
126
127 const path = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'balance']
128 const opPromise = this.kernel.environment.state.root.get(path)
129 .then(vertex => new U256(vertex.value))
130 .catch(() => new U256(0))
131
132 this.kernel.pushOpsQueue(opPromise, cbIndex, balance => {
133 this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES))
134 })
135 }
136
137 /**
138 * Gets the execution's origination address and loads it into memory at the
139 * given offset. This is the sender of original transaction; it is never an
140 * account with non-empty associated code.
141 * @param {integer} offset
142 */
143 getTxOrigin (offset) {
144 this.takeGas(2)
145
146 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.origin.toMemory())
147 }
148
149 /**
150 * Gets caller address and loads it into memory at the given offset. This is
151 * the address of the account that is directly responsible for this execution.
152 * @param {integer} offset
153 */
154 getCaller (offset) {
155 this.takeGas(2)
156
157 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.caller.toMemory())
158 }
159
160 /**
161 * Gets the deposited value by the instruction/transaction responsible for
162 * this execution and loads it into memory at the given location.
163 * @param {integer} offset
164 */
165 getCallValue (offset) {
166 this.takeGas(2)
167
168 this.setMemory(offset, U128_SIZE_BYTES, this.kernel.environment.callValue.toMemory(U128_SIZE_BYTES))
169 }
170
171 /**
172 * Get size of input data in current environment. This pertains to the input
173 * data passed with the message call instruction or transaction.
174 * @return {integer}
175 */
176 getCallDataSize () {
177 this.takeGas(2)
178
179 return this.kernel.environment.callData.length
180 }
181
182 /**
183 * Copys the input data in current environment to memory. This pertains to
184 * the input data passed with the message call instruction or transaction.
185 * @param {integer} offset the offset in memory to load into
186 * @param {integer} dataOffset the offset in the input data
187 * @param {integer} length the length of data to copy
188 */
189 callDataCopy (offset, dataOffset, length) {
190 this.takeGas(3 + Math.ceil(length / 32) * 3)
191
192 if (length) {
193 const callData = this.kernel.environment.callData.slice(dataOffset, dataOffset + length)
194 this.setMemory(offset, length, callData)
195 }
196 }
197
198 /**
199 * Copys the input data in current environment to memory. This pertains to
200 * the input data passed with the message call instruction or transaction.
201 * @param {integer} offset the offset in memory to load into
202 * @param {integer} dataOffset the offset in the input data
203 */
204 callDataCopy256 (offset, dataOffset) {
205 this.takeGas(3)
206 const callData = this.kernel.environment.callData.slice(dataOffset, dataOffset + 32)
207 this.setMemory(offset, U256_SIZE_BYTES, callData)
208 }
209
210 /**
211 * Gets the size of code running in current environment.
212 * @return {interger}
213 */
214 getCodeSize (cbIndex) {
215 this.takeGas(2)
216
217 const opPromise = this.kernel.environment.state.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.get('code')
237 .then(vertex => vertex.value)
238 } else {
239 opPromise = Promise.resolve([])
240 }
241
242 // wait for all the prevouse async ops to finish before running the callback
243 this.kernel.pushOpsQueue(opPromise, cbIndex, code => {
244 if (code.length) {
245 code = code.slice(codeOffset, codeOffset + length)
246 this.setMemory(resultOffset, length, code)
247 }
248 })
249 }
250
251 /**
252 * Get size of an account’s code.
253 * @param {integer} addressOffset the offset in memory to load the address from
254 * @return {integer}
255 */
256 getExternalCodeSize (addressOffset, cbOffset) {
257 this.takeGas(20)
258 const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
259 const opPromise = this.kernel.environment.state.root.get(address)
260 .then(vertex => vertex.value.length)
261 .catch(() => 0)
262
263 // wait for all the prevouse async ops to finish before running the callback
264 this.kernel.pushOpsQueue(opPromise, cbOffset, length => length)
265 }
266
267 /**
268 * Copys the code of an account to memory.
269 * @param {integer} addressOffset the memory offset of the address
270 * @param {integer} resultOffset the memory offset
271 * @param {integer} codeOffset the code offset
272 * @param {integer} length the length of code to copy
273 */
274 externalCodeCopy (addressOffset, resultOffset, codeOffset, length, cbIndex) {
275 this.takeGas(20 + Math.ceil(length / 32) * 3)
276
277 const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
278 let opPromise
279
280 if (length) {
281 opPromise = this.kernel.environment.state.root
282 .get(address)
283 .then(vertex => vertex.value)
284 .catch(() => [])
285 } else {
286 opPromise = Promise.resolve([])
287 }
288
289 // wait for all the prevouse async ops to finish before running the callback
290 this.kernel.pushOpsQueue(opPromise, cbIndex, code => {
291 if (code.length) {
292 code = code.slice(codeOffset, codeOffset + length)
293 this.setMemory(resultOffset, length, code)
294 }
295 })
296 }
297
298 /**
299 * Gets price of gas in current environment.
300 * @return {integer}
301 */
302 getTxGasPrice () {
303 this.takeGas(2)
304
305 return this.kernel.environment.gasPrice
306 }
307
308 /**
309 * Gets the hash of one of the 256 most recent complete blocks.
310 * @param {integer} number which block to load
311 * @param {integer} offset the offset to load the hash into
312 */
313 getBlockHash (number, offset, cbOffset) {
314 this.takeGas(20)
315
316 const diff = this.kernel.environment.block.number - number
317 let opPromise
318
319 if (diff > 256 || diff <= 0) {
320 opPromise = Promise.resolve(new U256(0))
321 } else {
322 opPromise = this.kernel.environment.getBlockHash(number)
323 }
324
325 // wait for all the prevouse async ops to finish before running the callback
326 this.kernel.pushOpsQueue(opPromise, cbOffset, hash => {
327 this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
328 })
329 }
330
331 /**
332 * Gets the block’s beneficiary address and loads into memory.
333 * @param offset
334 */
335 getBlockCoinbase (offset) {
336 this.takeGas(2)
337
338 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.block.coinbase.toMemory())
339 }
340
341 /**
342 * Get the block’s timestamp.
343 * @return {integer}
344 */
345 getBlockTimestamp () {
346 this.takeGas(2)
347
348 return this.kernel.environment.block.timestamp
349 }
350
351 /**
352 * Get the block’s number.
353 * @return {integer}
354 */
355 getBlockNumber () {
356 this.takeGas(2)
357
358 return this.kernel.environment.block.number
359 }
360
361 /**
362 * Get the block’s difficulty.
363 * @return {integer}
364 */
365 getBlockDifficulty (offset) {
366 this.takeGas(2)
367
368 this.setMemory(offset, U256_SIZE_BYTES, this.kernel.environment.block.difficulty.toMemory())
369 }
370
371 /**
372 * Get the block’s gas limit.
373 * @return {integer}
374 */
375 getBlockGasLimit () {
376 this.takeGas(2)
377
378 return this.kernel.environment.block.gasLimit
379 }
380
381 /**
382 * Creates a new log in the current environment
383 * @param {integer} dataOffset the offset in memory to load the memory
384 * @param {integer} length the data length
385 * @param {integer} number of topics
386 */
387 log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
388 if (numberOfTopics < 0 || numberOfTopics > 4) {
389 throw new Error('Invalid numberOfTopics')
390 }
391
392 this.takeGas(375 + length * 8 + numberOfTopics * 375)
393
394 const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
395 const topics = []
396
397 if (numberOfTopics > 0) {
398 topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
399 }
400
401 if (numberOfTopics > 1) {
402 topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
403 }
404
405 if (numberOfTopics > 2) {
406 topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
407 }
408
409 if (numberOfTopics > 3) {
410 topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
411 }
412
413 this.kernel.environment.logs.push({
414 data: data,
415 topics: topics
416 })
417 }
418
419 /**
420 * Creates a new contract with a given value.
421 * @param {integer} valueOffset the offset in memory to the value from
422 * @param {integer} dataOffset the offset to load the code for the new contract from
423 * @param {integer} length the data length
424 * @param (integer} resultOffset the offset to write the new contract address to
425 * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
426 */
427 create (valueOffset, dataOffset, length, resultOffset, cbIndex) {
428 this.takeGas(32000)
429
430 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
431 if (length) {
432 const code = this.getMemory(dataOffset, length).slice(0)
433 }
434
435 let opPromise
436
437 if (value.gt(this.kernel.environment.value)) {
438 opPromise = Promise.resolve(new Buffer(20).fill(0))
439 } else {
440 // todo actully run the code
441 opPromise = Promise.resolve(ethUtil.generateAddress(this.kernel.environment.address, this.kernel.environment.nonce))
442 }
443
444 // wait for all the prevouse async ops to finish before running the callback
445 this.kernel.pushOpsQueue(opPromise, cbIndex, address => {
446 this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address)
447 })
448 }
449
450 /**
451 * Sends a message with arbiatary data to a given address path
452 * @param {integer} addressOffset the offset to load the address path from
453 * @param {integer} valueOffset the offset to load the value from
454 * @param {integer} dataOffset the offset to load data from
455 * @param {integer} dataLength the length of data
456 * @param {integer} resultOffset the offset to store the result data at
457 * @param {integer} resultLength
458 * @param {integer} gas
459 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
460 */
461 _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
462 const gas = from64bit(gasHigh, gasLow)
463 // Load the params from mem
464 const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
465 const value = new U256(this.getMemory(valueOffset, U128_SIZE_BYTES))
466
467 this.takeGas(40)
468
469 // Special case for non-zero value; why does this exist?
470 if (!value.isZero()) {
471 this.takeGas(9000 - 2300 + gas)
472 this.takeGas(-gas)
473 }
474
475 let opPromise = this.kernel.environment.state.root.get(address)
476 .catch(() => {
477 // why does this exist?
478 this.takeGas(25000)
479 })
480
481 // wait for all the prevouse async ops to finish before running the callback
482 this.kernel.pushOpsQueue(opPromise, cbIndex, () => {
483 return 1
484 })
485 }
486
487 /**
488 * Message-call into this account with an alternative account’s code.
489 * @param {integer} addressOffset the offset to load the address path from
490 * @param {integer} valueOffset the offset to load the value from
491 * @param {integer} dataOffset the offset to load data from
492 * @param {integer} dataLength the length of data
493 * @param {integer} resultOffset the offset to store the result data at
494 * @param {integer} resultLength
495 * @param {integer} gas
496 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
497 */
498 callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
499 this.takeGas(40)
500 // Load the params from mem
501 const path = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
502 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
503
504 // Special case for non-zero value; why does this exist?
505 if (!value.isZero()) {
506 this.takeGas(6700)
507 }
508
509 // TODO: should be message?
510 const opPromise = this.kernel.environment.state.root.get(path)
511 .catch(() => {
512 // TODO: handle errors
513 // the value was not found
514 return null
515 })
516
517 this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
518 return 1
519 })
520 }
521
522 /**
523 * Message-call into this account with an alternative account’s code, but
524 * persisting the current values for sender and value.
525 * @param {integer} gas
526 * @param {integer} addressOffset the offset to load the address path from
527 * @param {integer} valueOffset the offset to load the value from
528 * @param {integer} dataOffset the offset to load data from
529 * @param {integer} dataLength the length of data
530 * @param {integer} resultOffset the offset to store the result data at
531 * @param {integer} resultLength
532 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
533 */
534 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
535 // FIXME: count properly
536 this.takeGas(40)
537
538 const data = this.getMemory(dataOffset, dataLength).slice(0)
539 const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
540 const [errorCode, result] = this.environment.callDelegate(gas, address, data)
541 this.setMemory(resultOffset, resultLength, result)
542 return errorCode
543 }
544
545 /**
546 * store a value at a given path in long term storage which are both loaded
547 * from Memory
548 * @param {interger} pathOffest the memory offset to load the the path from
549 * @param {interger} valueOffset the memory offset to load the value from
550 */
551 storageStore (pathOffset, valueOffset, cbIndex) {
552 this.takeGas(5000)
553 const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
554 // copy the value
555 const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
556 const valIsZero = value.every((i) => i === 0)
557 const opPromise = this.kernel.environment.state.get(path)
558 .then(vertex => vertex.value)
559 .catch(() => null)
560
561 this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
562 if (valIsZero && oldValue) {
563 // delete a value
564 this.kernel.environment.gasRefund += 15000
565 this.kernel.environment.state.del(path)
566 } else {
567 if (!valIsZero && !oldValue) {
568 // creating a new value
569 this.takeGas(15000)
570 }
571 // update
572 this.kernel.environment.state.set(path, new Vertex({
573 value: value
574 }))
575 }
576 })
577 }
578
579 /**
580 * reterives a value at a given path in long term storage
581 * @param {interger} pathOffest the memory offset to load the the path from
582 * @param {interger} resultOffset the memory offset to load the value from
583 */
584 storageLoad (pathOffset, resultOffset, cbIndex) {
585 this.takeGas(50)
586
587 // convert the path to an array
588 const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
589 // get the value from the state
590 const opPromise = this.kernel.environment.state.get(path)
591 .then(vertex => vertex.value)
592 .catch(() => new Uint8Array(32))
593
594 this.kernel.pushOpsQueue(opPromise, cbIndex, value => {
595 this.setMemory(resultOffset, U256_SIZE_BYTES, value)
596 })
597 }
598
599 /**
600 * Halt execution returning output data.
601 * @param {integer} offset the offset of the output data.
602 * @param {integer} length the length of the output data.
603 */
604 return (offset, length) {
605 if (length) {
606 this.kernel.environment.returnValue = this.getMemory(offset, length).slice(0)
607 }
608 }
609
610 /**
611 * Halt execution and register account for later deletion giving the remaining
612 * balance to an address path
613 * @param {integer} offset the offset to load the address from
614 */
615 selfDestruct (addressOffset) {
616 this.kernel.environment.selfDestruct = true
617 this.kernel.environment.selfDestructAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
618 this.kernel.environment.gasRefund += 24000
619 }
620
621 getMemory (offset, length) {
622 return new Uint8Array(this.kernel.memory, offset, length)
623 }
624
625 setMemory (offset, length, value) {
626 const memory = new Uint8Array(this.kernel.memory, offset, length)
627 memory.set(value)
628 }
629
630 /*
631 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
632 * because every caller of this method is trusted.
633 */
634 takeGas (amount) {
635 if (this.kernel.environment.gasLeft < amount) {
636 throw new Error('Ran out of gas')
637 }
638 this.kernel.environment.gasLeft -= amount
639 }
640}
641
642// converts a 64 bit number to a JS number
643function from64bit (high, low) {
644 if (high < 0) {
645 // convert from a 32-bit two's compliment
646 high = 0x100000000 - high
647 }
648 if (low < 0) {
649 // convert from a 32-bit two's compliment
650 low = 0x100000000 - low
651 }
652 // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
653 return (high * 4294967296) + low
654}
655

Built with git-ssb-web