git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: e0e6608f9df71b679e52c4d0916732e4e8201fc2

Files: e0e6608f9df71b679e52c4d0916732e4e8201fc2 / EVMinterface.js

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

Built with git-ssb-web