git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 3c2192ab4c4610302da22858ac40084fe5db349d

Files: 3c2192ab4c4610302da22858ac40084fe5db349d / EVMinterface.js

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

Built with git-ssb-web