git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 92b25513d4a47b5f802137f167db1b71bb72a64b

Files: 92b25513d4a47b5f802137f167db1b71bb72a64b / EVMinterface.js

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

Built with git-ssb-web