git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 229256951b870dc5f3a910c2e413c4df95f6cefa

Files: 229256951b870dc5f3a910c2e413c4df95f6cefa / EVMinterface.js

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

Built with git-ssb-web