git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 9bb00a66c6590ce780568e10d649be74c66ce1e6

Files: 9bb00a66c6590ce780568e10d649be74c66ce1e6 / EVMinterface.js

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

Built with git-ssb-web