git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: a01401f258acd7e0b1f4ecbe82cdd1bc26048b4c

Files: a01401f258acd7e0b1f4ecbe82cdd1bc26048b4c / EVMinterface.js

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

Built with git-ssb-web