git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 2d5edf066ff154d5fc07115a7b1b66bcc2899f57

Files: 2d5edf066ff154d5fc07115a7b1b66bcc2899f57 / EVMinterface.js

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

Built with git-ssb-web