git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 7cdddef81c118db3b188949697b28350cbe2e343

Files: 7cdddef81c118db3b188949697b28350cbe2e343 / EVMinterface.js

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

Built with git-ssb-web