git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 08886ada2409d7516bf4a5654c170fa62fe5e439

Files: 08886ada2409d7516bf4a5654c170fa62fe5e439 / EVMinterface.js

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

Built with git-ssb-web