git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: de87b9b68f0638e5deda64385e6c49c506325b8e

Files: de87b9b68f0638e5deda64385e6c49c506325b8e / EVMimports.js

20627 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 U256 = require('./deps/u256.js')
10
11const U128_SIZE_BYTES = 16
12const ADDRESS_SIZE_BYTES = 20
13const U256_SIZE_BYTES = 32
14
15// The interface exposed to the WebAessembly Core
16module.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 = ['accounts', ...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 > 0 && offset >= 0 && dataOffset >= 0) {
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
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
218 .get('code')
219 .then(vertex => vertex.value.length)
220
221 // wait for all the prevouse async ops to finish before running the callback
222 this.kernel.pushOpsQueue(opPromise, cbIndex, length => length)
223 }
224
225 /**
226 * Copys the code running in current environment to memory.
227 * @param {integer} offset the memory offset
228 * @param {integer} codeOffset the code offset
229 * @param {integer} length the length of code to copy
230 */
231 codeCopy (resultOffset, codeOffset, length, cbIndex) {
232 this.takeGas(3 + Math.ceil(length / 32) * 3)
233
234 let opPromise
235
236 if (length) {
237 opPromise = this.kernel.environment.state
238 .get('code')
239 .then(vertex => vertex.value)
240 } else {
241 opPromise = Promise.resolve([])
242 }
243
244 // wait for all the prevouse async ops to finish before running the callback
245 this.kernel.pushOpsQueue(opPromise, cbIndex, code => {
246 if (code.length) {
247 code = code.slice(codeOffset, codeOffset + length)
248 this.setMemory(resultOffset, length, code)
249 }
250 })
251 }
252
253 /**
254 * Get size of an account’s code.
255 * @param {integer} addressOffset the offset in memory to load the address from
256 * @return {integer}
257 */
258 getExternalCodeSize (addressOffset, cbOffset) {
259 this.takeGas(20)
260 const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
261 const opPromise = this.kernel.environment.state.root
262 .get(address)
263 .then(vertex => vertex.value.length)
264 .catch(() => 0)
265
266 // wait for all the prevouse async ops to finish before running the callback
267 this.kernel.pushOpsQueue(opPromise, cbOffset, length => length)
268 }
269
270 /**
271 * Copys the code of an account to memory.
272 * @param {integer} addressOffset the memory offset of the address
273 * @param {integer} resultOffset the memory offset
274 * @param {integer} codeOffset the code offset
275 * @param {integer} length the length of code to copy
276 */
277 externalCodeCopy (addressOffset, resultOffset, codeOffset, length, cbIndex) {
278 this.takeGas(20 + Math.ceil(length / 32) * 3)
279
280 const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
281 let opPromise
282
283 if (length) {
284 opPromise = this.kernel.environment.state.root
285 .get(address)
286 .then(vertex => vertex.value)
287 .catch(() => [])
288 } else {
289 opPromise = Promise.resolve([])
290 }
291
292 // wait for all the prevouse async ops to finish before running the callback
293 this.kernel.pushOpsQueue(opPromise, cbIndex, code => {
294 if (code.length) {
295 code = code.slice(codeOffset, codeOffset + length)
296 this.setMemory(resultOffset, length, code)
297 }
298 })
299 }
300
301 /**
302 * Gets price of gas in current environment.
303 * @return {integer}
304 */
305 getTxGasPrice () {
306 this.takeGas(2)
307
308 return this.kernel.environment.gasPrice
309 }
310
311 /**
312 * Gets the hash of one of the 256 most recent complete blocks.
313 * @param {integer} number which block to load
314 * @param {integer} offset the offset to load the hash into
315 */
316 getBlockHash (number, offset, cbOffset) {
317 this.takeGas(20)
318
319 const diff = this.kernel.environment.block.number - number
320 let opPromise
321
322 if (diff > 256 || diff <= 0) {
323 opPromise = Promise.resolve(new U256(0))
324 } else {
325 opPromise = this.kernel.environment.getBlockHash(number)
326 }
327
328 // wait for all the prevouse async ops to finish before running the callback
329 this.kernel.pushOpsQueue(opPromise, cbOffset, hash => {
330 this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
331 })
332 }
333
334 /**
335 * Gets the block’s beneficiary address and loads into memory.
336 * @param offset
337 */
338 getBlockCoinbase (offset) {
339 this.takeGas(2)
340
341 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.block.header.coinbase)
342 }
343
344 /**
345 * Get the block’s timestamp.
346 * @return {integer}
347 */
348 getBlockTimestamp () {
349 this.takeGas(2)
350
351 return this.kernel.environment.block.timestamp
352 }
353
354 /**
355 * Get the block’s number.
356 * @return {integer}
357 */
358 getBlockNumber () {
359 this.takeGas(2)
360
361 return this.kernel.environment.block.number
362 }
363
364 /**
365 * Get the block’s difficulty.
366 * @return {integer}
367 */
368 getBlockDifficulty (offset) {
369 this.takeGas(2)
370
371 this.setMemory(offset, U256_SIZE_BYTES, this.kernel.environment.block.difficulty.toMemory())
372 }
373
374 /**
375 * Get the block’s gas limit.
376 * @return {integer}
377 */
378 getBlockGasLimit () {
379 this.takeGas(2)
380
381 return this.kernel.environment.block.gasLimit
382 }
383
384 /**
385 * Creates a new log in the current environment
386 * @param {integer} dataOffset the offset in memory to load the memory
387 * @param {integer} length the data length
388 * @param {integer} number of topics
389 */
390 log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
391 if (numberOfTopics < 0 || numberOfTopics > 4) {
392 throw new Error('Invalid numberOfTopics')
393 }
394
395 this.takeGas(375 + length * 8 + numberOfTopics * 375)
396
397 const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
398 const topics = []
399
400 if (numberOfTopics > 0) {
401 topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
402 }
403
404 if (numberOfTopics > 1) {
405 topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
406 }
407
408 if (numberOfTopics > 2) {
409 topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
410 }
411
412 if (numberOfTopics > 3) {
413 topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
414 }
415
416 this.kernel.environment.logs.push({
417 data: data,
418 topics: topics
419 })
420 }
421
422 /**
423 * Creates a new contract with a given value.
424 * @param {integer} valueOffset the offset in memory to the value from
425 * @param {integer} dataOffset the offset to load the code for the new contract from
426 * @param {integer} length the data length
427 * @param (integer} resultOffset the offset to write the new contract address to
428 * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
429 */
430 create (valueOffset, dataOffset, length, resultOffset, cbIndex) {
431 this.takeGas(32000)
432
433 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
434 // if (length) {
435 // const code = this.getMemory(dataOffset, length).slice(0)
436 // }
437
438 let opPromise
439
440 if (value.gt(this.kernel.environment.value)) {
441 opPromise = Promise.resolve(new Buffer(20).fill(0))
442 } else {
443 // todo actully run the code
444 opPromise = Promise.resolve(ethUtil.generateAddress(this.kernel.environment.address, this.kernel.environment.nonce))
445 }
446
447 // wait for all the prevouse async ops to finish before running the callback
448 this.kernel.pushOpsQueue(opPromise, cbIndex, address => {
449 this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address)
450 })
451 }
452
453 /**
454 * Sends a message with arbiatary data to a given address path
455 * @param {integer} addressOffset the offset to load the address path from
456 * @param {integer} valueOffset the offset to load the value from
457 * @param {integer} dataOffset the offset to load data from
458 * @param {integer} dataLength the length of data
459 * @param {integer} resultOffset the offset to store the result data at
460 * @param {integer} resultLength
461 * @param {integer} gas
462 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
463 */
464 _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
465 this.takeGas(40)
466
467 const gas = from64bit(gasHigh, gasLow)
468 // Load the params from mem
469 const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
470 const value = new U256(this.getMemory(valueOffset, U128_SIZE_BYTES))
471
472 // Special case for non-zero value; why does this exist?
473 if (!value.isZero()) {
474 this.takeGas(9000 - 2300 + gas)
475 this.takeGas(-gas)
476 }
477
478 let opPromise = this.kernel.environment.state.root.get(address)
479 .catch(() => {
480 // why does this exist?
481 this.takeGas(25000)
482 })
483
484 // wait for all the prevouse async ops to finish before running the callback
485 this.kernel.pushOpsQueue(opPromise, cbIndex, () => {
486 return 1
487 })
488 }
489
490 /**
491 * Message-call into this account with an alternative account’s code.
492 * @param {integer} addressOffset the offset to load the address path from
493 * @param {integer} valueOffset the offset to load the value from
494 * @param {integer} dataOffset the offset to load data from
495 * @param {integer} dataLength the length of data
496 * @param {integer} resultOffset the offset to store the result data at
497 * @param {integer} resultLength
498 * @param {integer} gas
499 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
500 */
501 callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
502 this.takeGas(40)
503 // Load the params from mem
504 const path = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
505 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
506
507 // Special case for non-zero value; why does this exist?
508 if (!value.isZero()) {
509 this.takeGas(6700)
510 }
511
512 // TODO: should be message?
513 const opPromise = this.kernel.environment.state.root.get(path)
514 .catch(() => {
515 // TODO: handle errors
516 // the value was not found
517 return null
518 })
519
520 this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
521 return 1
522 })
523 }
524
525 /**
526 * Message-call into this account with an alternative account’s code, but
527 * persisting the current values for sender and value.
528 * @param {integer} gas
529 * @param {integer} addressOffset the offset to load the address path from
530 * @param {integer} valueOffset the offset to load the value from
531 * @param {integer} dataOffset the offset to load data from
532 * @param {integer} dataLength the length of data
533 * @param {integer} resultOffset the offset to store the result data at
534 * @param {integer} resultLength
535 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
536 */
537 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
538 // FIXME: count properly
539 this.takeGas(40)
540
541 const data = this.getMemory(dataOffset, dataLength).slice(0)
542 const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
543 const [errorCode, result] = this.environment.callDelegate(gas, address, data)
544 this.setMemory(resultOffset, resultLength, result)
545 return errorCode
546 }
547
548 /**
549 * store a value at a given path in long term storage which are both loaded
550 * from Memory
551 * @param {interger} pathOffest the memory offset to load the the path from
552 * @param {interger} valueOffset the memory offset to load the value from
553 */
554 storageStore (pathOffset, valueOffset, cbIndex) {
555 this.takeGas(5000)
556 const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
557 // copy the value
558 const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
559 const valIsZero = value.every((i) => i === 0)
560 const opPromise = this.kernel.environment.state.get(path)
561 .then(vertex => vertex.value)
562 .catch(() => null)
563
564 this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
565 if (valIsZero && oldValue) {
566 // delete a value
567 this.kernel.environment.gasRefund += 15000
568 this.kernel.environment.state.del(path)
569 } else {
570 if (!valIsZero && !oldValue) {
571 // creating a new value
572 this.takeGas(15000)
573 }
574 // update
575 this.kernel.environment.state.set(path, new Vertex({
576 value: value
577 }))
578 }
579 })
580 }
581
582 /**
583 * reterives a value at a given path in long term storage
584 * @param {interger} pathOffest the memory offset to load the the path from
585 * @param {interger} resultOffset the memory offset to load the value from
586 */
587 storageLoad (pathOffset, resultOffset, cbIndex) {
588 this.takeGas(50)
589
590 // convert the path to an array
591 const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
592 // get the value from the state
593 const opPromise = this.kernel.environment.state.get(path)
594 .then(vertex => vertex.value)
595 .catch(() => new Uint8Array(32))
596
597 this.kernel.pushOpsQueue(opPromise, cbIndex, value => {
598 this.setMemory(resultOffset, U256_SIZE_BYTES, value)
599 })
600 }
601
602 /**
603 * Halt execution returning output data.
604 * @param {integer} offset the offset of the output data.
605 * @param {integer} length the length of the output data.
606 */
607 return (offset, length) {
608 this.kernel.environment.returnValue = this.getMemory(offset, length).slice(0)
609 }
610
611 /**
612 * Halt execution and register account for later deletion giving the remaining
613 * balance to an address path
614 * @param {integer} offset the offset to load the address from
615 */
616 selfDestruct (addressOffset) {
617 this.kernel.environment.selfDestruct = true
618 this.kernel.environment.selfDestructAddress = this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)
619 this.kernel.environment.gasRefund += 24000
620 }
621
622 getMemory (offset, length) {
623 if (offset >= 0 && length > 0) {
624 return new Uint8Array(this.kernel.memory, offset, length)
625 } else {
626 return new Uint8Array([])
627 }
628 }
629
630 setMemory (offset, length, value) {
631 if (offset >= 0 && length > 0) {
632 const memory = new Uint8Array(this.kernel.memory, offset, length)
633 memory.set(value)
634 }
635 }
636
637 /*
638 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
639 * because every caller of this method is trusted.
640 */
641 takeGas (amount) {
642 if (this.kernel.environment.gasLeft < amount) {
643 throw new Error('Ran out of gas')
644 }
645 this.kernel.environment.gasLeft -= amount
646 }
647}
648
649// converts a 64 bit number to a JS number
650function from64bit (high, low) {
651 if (high < 0) {
652 // convert from a 32-bit two's compliment
653 high = 0x100000000 - high
654 }
655 if (low < 0) {
656 // convert from a 32-bit two's compliment
657 low = 0x100000000 - low
658 }
659 // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
660 return (high * 4294967296) + low
661}
662

Built with git-ssb-web