git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 346aa8e29c19c848a5fa994e486e5bd6f5842c44

Files: 346aa8e29c19c848a5fa994e486e5bd6f5842c44 / EVMinterface.js

20942 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 const code = this.getMemory(dataOffset, length).slice(0)
432 let opPromise
433
434 if (value.gt(this.kernel.environment.value)) {
435 opPromise = Promise.resolve(new Address())
436 } else {
437 // todo actully run the code
438 opPromise = Promise.resolve(ethUtil.generateAddress(this.kernel.environment.from, this.kernel.environment.nonce))
439 }
440
441 // wait for all the prevouse async ops to finish before running the callback
442 this.kernel.pushOpsQueue(opPromise, cbIndex, address => {
443 this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address)
444 })
445 }
446
447 /**
448 * Sends a message with arbiatary data to a given address path
449 * @param {integer} addressOffset the offset to load the address path from
450 * @param {integer} valueOffset the offset to load the value from
451 * @param {integer} dataOffset the offset to load data from
452 * @param {integer} dataLength the length of data
453 * @param {integer} resultOffset the offset to store the result data at
454 * @param {integer} resultLength
455 * @param {integer} gas
456 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
457 */
458 _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
459 const gas = from64bit(gasHigh, gasLow)
460 // Load the params from mem
461 const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
462 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
463
464 this.takeGas(40)
465
466 // const data = this.getMemory(dataOffset, dataLength).slice(0)
467
468 // // Special case for calling into empty account
469 // if (!this.environment.isAccountPresent(address)) {
470 // this.takeGas(25000)
471 // }
472 if (address.lt(new U256(4))) {
473 this.takeGas(25000)
474 }
475 // // Special case for non-zero value
476 if (!value.isZero()) {
477 this.takeGas(9000 - 2300 + gas)
478 this.takeGas(-gas)
479 }
480
481 // const [errorCode, result] = this.environment.call(gas, address, value, data)
482 // this.setMemory(resultOffset, resultLength, result)
483 // return errorCode
484 //
485 return 1
486 }
487
488 /**
489 * Message-call into this account with an alternative account’s code.
490 * @param {integer} addressOffset the offset to load the address path from
491 * @param {integer} valueOffset the offset to load the value from
492 * @param {integer} dataOffset the offset to load data from
493 * @param {integer} dataLength the length of data
494 * @param {integer} resultOffset the offset to store the result data at
495 * @param {integer} resultLength
496 * @param {integer} gas
497 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
498 */
499 callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
500 this.takeGas(40)
501 // Load the params from mem
502 const path = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
503 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
504
505 // Special case for non-zero value
506 if (!value.isZero()) {
507 this.takeGas(6700)
508 }
509
510 // TODO: should be message?
511 const opPromise = this.kernel.environment.state.root.get(path)
512 .catch(() => {
513 // TODO: handle errors
514 // the value was not found
515 return null
516 })
517
518 this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
519 //TODO
520 // const data = this.getMemory(dataOffset, dataLength).slice(0)
521
522 // // Special case for calling into empty account
523 // if (!this.environment.isAccountPresent(address)) {
524 // this.takeGas(25000)
525 // }
526
527 // const [errorCode, result] = this.environment.call(gas, address, value, data)
528 // this.setMemory(resultOffset, resultLength, result)
529 // return errorCode
530 //
531 })
532 }
533
534 /**
535 * Message-call into this account with an alternative account’s code, but
536 * persisting the current values for sender and value.
537 * @param {integer} gas
538 * @param {integer} addressOffset the offset to load the address path from
539 * @param {integer} valueOffset the offset to load the value from
540 * @param {integer} dataOffset the offset to load data from
541 * @param {integer} dataLength the length of data
542 * @param {integer} resultOffset the offset to store the result data at
543 * @param {integer} resultLength
544 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
545 */
546 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
547 // FIXME: count properly
548 this.takeGas(40)
549
550 const data = this.getMemory(dataOffset, dataLength).slice(0)
551 const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
552 const [errorCode, result] = this.environment.callDelegate(gas, address, data)
553 this.setMemory(resultOffset, resultLength, result)
554 return errorCode
555 }
556
557 /**
558 * store a value at a given path in long term storage which are both loaded
559 * from Memory
560 * @param {interger} pathOffest the memory offset to load the the path from
561 * @param {interger} valueOffset the memory offset to load the value from
562 */
563 storageStore (pathOffset, valueOffset, cbIndex) {
564 this.takeGas(5000)
565 const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
566 // copy the value
567 const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
568 const valIsZero = value.every((i) => i === 0)
569 const opPromise = this.kernel.environment.state.get(path)
570 .then(vertex => vertex.value)
571 .catch(() => null)
572
573 this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
574 if (valIsZero && oldValue) {
575 // delete a value
576 this.kernel.environment.gasRefund += 15000
577 this.kernel.environment.state.del(path)
578 } else {
579 if (!valIsZero && !oldValue) {
580 // creating a new value
581 this.takeGas(15000)
582 }
583 // update
584 this.kernel.environment.state.set(path, new Vertex({
585 value: value
586 }))
587 }
588 })
589 }
590
591 /**
592 * reterives a value at a given path in long term storage
593 * @param {interger} pathOffest the memory offset to load the the path from
594 * @param {interger} resultOffset the memory offset to load the value from
595 */
596 storageLoad (pathOffset, resultOffset, cbIndex) {
597 this.takeGas(50)
598
599 // convert the path to an array
600 const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
601 // get the value from the state
602 const opPromise = this.kernel.environment.state.get(path)
603 .then(vertex => vertex.value)
604 .catch(() => new Uint8Array(32))
605
606 this.kernel.pushOpsQueue(opPromise, cbIndex, value => {
607 this.setMemory(resultOffset, U256_SIZE_BYTES, value)
608 })
609 }
610
611 /**
612 * Halt execution returning output data.
613 * @param {integer} offset the offset of the output data.
614 * @param {integer} length the length of the output data.
615 */
616 return (offset, length) {
617 if (length) {
618 this.kernel.environment.returnValue = this.getMemory(offset, length).slice(0)
619 }
620 }
621
622 /**
623 * Halt execution and register account for later deletion giving the remaining
624 * balance to an address path
625 * @param {integer} offset the offset to load the address from
626 */
627 selfDestruct (addressOffset) {
628 this.kernel.environment.selfDestruct = true
629 this.kernel.environment.selfDestructAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
630 this.kernel.environment.gasRefund += 24000
631 }
632
633 getMemory (offset, length) {
634 return new Uint8Array(this.kernel.memory, offset, length)
635 }
636
637 setMemory (offset, length, value) {
638 const memory = new Uint8Array(this.kernel.memory, offset, length)
639 memory.set(value)
640 }
641
642 /*
643 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
644 * because every caller of this method is trusted.
645 */
646 takeGas (amount) {
647 if (this.kernel.environment.gasLeft < amount) {
648 throw new Error('Ran out of gas')
649 }
650 this.kernel.environment.gasLeft -= amount
651 }
652}
653
654// converts a 64 bit number to a JS number
655function from64bit (high, low) {
656 if (high < 0) {
657 // convert from a 32-bit two's compliment
658 high = 0x100000000 - high
659 }
660 if (low < 0) {
661 // convert from a 32-bit two's compliment
662 low = 0x100000000 - low
663 }
664 // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
665 return (high * 4294967296) + low
666}
667

Built with git-ssb-web