git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: ea59a1089955c500d2cda985bf74f9fc83ba71e3

Files: ea59a1089955c500d2cda985bf74f9fc83ba71e3 / EVMinterface.js

20397 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 (opts) {
20 opts.response.gasRefund = 0
21 this.message = opts.message
22 this.kernel = opts.kernel
23 this.vm = opts.vm
24 this.results = opts.response
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 static get name () {
38 return 'ethereum'
39 }
40
41 get exports () {
42 let exportMethods = [
43 // include all the public methods according to the Ethereum Environment Interface (EEI) r1
44 'getAddress',
45 'getBalance',
46 'getTxOrigin',
47 'getCaller',
48 'getCallValue',
49 'getCallDataSize',
50 'callDataCopy',
51 'callDataCopy256',
52 'getCodeSize',
53 'codeCopy',
54 'getExternalCodeSize',
55 'externalCodeCopy',
56 'getTxGasPrice',
57 'getBlockHash',
58 'getBlockCoinbase',
59 'getBlockTimestamp',
60 'getBlockNumber',
61 'getBlockDifficulty',
62 'getBlockGasLimit',
63 'log',
64 'create',
65 'callCode',
66 'callDelegate',
67 'storageStore',
68 'storageLoad',
69 'return',
70 'selfDestruct'
71 ]
72 let ret = {}
73 exportMethods.forEach((method) => {
74 ret[method] = this[method].bind(this)
75 })
76
77 // add shims
78 ret.useGas = this.shims.exports.useGas
79 ret.getGasLeft = this.shims.exports.getGasLeft
80 ret.call = this.shims.exports.call
81 return ret
82 }
83
84 setModule (mod) {
85 this.module = mod
86 }
87
88 /**
89 * Subtracts an amount to the gas counter
90 * @param {integer} amount the amount to subtract to the gas counter
91 */
92 _useGas (high, low) {
93 this.takeGas(from64bit(high, low))
94 }
95
96 /**
97 * Returns the current amount of gas
98 * @return {integer}
99 */
100 _getGasLeftHigh () {
101 return Math.floor(this.message.gas / 4294967296)
102 }
103
104 /**
105 * Returns the current amount of gas
106 * @return {integer}
107 */
108 _getGasLeftLow () {
109 return this.message.gas
110 }
111
112 /**
113 * Gets address of currently executing account and loads it into memory at
114 * the given offset.
115 * @param {integer} offset
116 */
117 getAddress (offset) {
118 this.takeGas(2)
119 const path = this.kernel.path
120 this.setMemory(offset, ADDRESS_SIZE_BYTES, new Buffer(path[1].slice(2), 'hex'))
121 }
122
123 /**
124 * Gets balance of the given account and loads it into memory at the given
125 * offset.
126 * @param {integer} addressOffset the memory offset to laod the address
127 * @param {integer} resultOffset
128 */
129 getBalance (addressOffset, offset, cbIndex) {
130 this.takeGas(20)
131
132 const path = [common.PARENT, common.PARENT, '0x' + new Buffer(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)).toString('hex')]
133 const opPromise = this.kernel.send(new Message({
134 to: path,
135 data: {
136 getValue: 'balance'
137 },
138 sync: true
139 }))
140 .catch(() => new Buffer([]))
141
142 this.vm.pushOpsQueue(opPromise, cbIndex, balance => {
143 this.setMemory(offset, U128_SIZE_BYTES, new U256(balance).toMemory(U128_SIZE_BYTES))
144 })
145 }
146
147 /**
148 * Gets the execution's origination address and loads it into memory at the
149 * given offset. This is the sender of original transaction; it is never an
150 * account with non-empty associated code.
151 * @param {integer} offset
152 */
153 getTxOrigin (offset) {
154 this.takeGas(2)
155
156 const origin = new Buffer(this.message.from[2].slice(2), 'hex')
157 this.setMemory(offset, ADDRESS_SIZE_BYTES, origin)
158 }
159
160 /**
161 * Gets caller address and loads it into memory at the given offset. This is
162 * the address of the account that is directly responsible for this execution.
163 * @param {integer} offset
164 */
165 getCaller (offset) {
166 this.takeGas(2)
167 const caller = this.message.from[2]
168 this.setMemory(offset, ADDRESS_SIZE_BYTES, new Buffer(caller.slice(2), 'hex'))
169 }
170
171 /**
172 * Gets the deposited value by the instruction/transaction responsible for
173 * this execution and loads it into memory at the given location.
174 * @param {integer} offset
175 */
176 getCallValue (offset) {
177 this.takeGas(2)
178
179 this.setMemory(offset, U128_SIZE_BYTES, this.message.value.toMemory(U128_SIZE_BYTES))
180 }
181
182 /**
183 * Get size of input data in current environment. This pertains to the input
184 * data passed with the message call instruction or transaction.
185 * @return {integer}
186 */
187 getCallDataSize () {
188 this.takeGas(2)
189
190 return this.message.data.length
191 }
192
193 /**
194 * Copys the input data in current environment to memory. This pertains to
195 * the input data passed with the message call instruction or transaction.
196 * @param {integer} offset the offset in memory to load into
197 * @param {integer} dataOffset the offset in the input data
198 * @param {integer} length the length of data to copy
199 */
200 callDataCopy (offset, dataOffset, length) {
201 this.takeGas(3 + Math.ceil(length / 32) * 3)
202
203 if (length) {
204 const callData = this.message.data.slice(dataOffset, dataOffset + length)
205 this.setMemory(offset, length, callData)
206 }
207 }
208
209 /**
210 * Copys the input data in current environment to memory. This pertains to
211 * the input data passed with the message call instruction or transaction.
212 * @param {integer} offset the offset in memory to load into
213 * @param {integer} dataOffset the offset in the input data
214 */
215 callDataCopy256 (offset, dataOffset) {
216 this.takeGas(3)
217 const callData = this.message.data.slice(dataOffset, dataOffset + 32)
218 this.setMemory(offset, U256_SIZE_BYTES, callData)
219 }
220
221 /**
222 * Gets the size of code running in current environment.
223 * @return {interger}
224 */
225 getCodeSize (cbIndex) {
226 this.takeGas(2)
227
228 // wait for all the prevouse async ops to finish before running the callback
229 this.vm.pushOpsQueue(this.kernel.code.length, cbIndex, length => length)
230 }
231
232 /**
233 * Copys the code running in current environment to memory.
234 * @param {integer} offset the memory offset
235 * @param {integer} codeOffset the code offset
236 * @param {integer} length the length of code to copy
237 */
238 codeCopy (resultOffset, codeOffset, length, cbIndex) {
239 this.takeGas(3 + Math.ceil(length / 32) * 3)
240
241 // wait for all the prevouse async ops to finish before running the callback
242 this.vm.pushOpsQueue(this.kernel.code, cbIndex, code => {
243 if (code.length) {
244 code = code.slice(codeOffset, codeOffset + length)
245 this.setMemory(resultOffset, length, code)
246 }
247 })
248 }
249
250 /**
251 * Get size of an account’s code.
252 * @param {integer} addressOffset the offset in memory to load the address from
253 * @return {integer}
254 */
255 getExternalCodeSize (addressOffset, cbOffset) {
256 this.takeGas(20)
257 const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
258 const opPromise = this.kernel.sendMessage(common.ROOT, common.getterMessage('code', address))
259 .then(vertex => vertex.value.length)
260 .catch(() => 0)
261
262 // wait for all the prevouse async ops to finish before running the callback
263 this.vm.pushOpsQueue(opPromise, cbOffset, length => length)
264 }
265
266 /**
267 * Copys the code of an account to memory.
268 * @param {integer} addressOffset the memory offset of the address
269 * @param {integer} resultOffset the memory offset
270 * @param {integer} codeOffset the code offset
271 * @param {integer} length the length of code to copy
272 */
273 externalCodeCopy (addressOffset, resultOffset, codeOffset, length, cbIndex) {
274 this.takeGas(20 + Math.ceil(length / 32) * 3)
275
276 const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
277 let opPromise
278
279 if (length) {
280 opPromise = this.kernel.sendMessage(common.ROOT, common.getterMessage('code', address))
281 .get(address)
282 .then(vertex => vertex.value)
283 .catch(() => [])
284 } else {
285 opPromise = Promise.resolve([])
286 }
287
288 // wait for all the prevouse async ops to finish before running the callback
289 this.vm.pushOpsQueue(opPromise, cbIndex, code => {
290 if (code.length) {
291 code = code.slice(codeOffset, codeOffset + length)
292 this.setMemory(resultOffset, length, code)
293 }
294 })
295 }
296
297 /**
298 * Gets price of gas in current environment.
299 * @return {integer}
300 */
301 getTxGasPrice () {
302 this.takeGas(2)
303
304 return this.message.gasPrice
305 }
306
307 /**
308 * Gets the hash of one of the 256 most recent complete blocks.
309 * @param {integer} number which block to load
310 * @param {integer} offset the offset to load the hash into
311 */
312 getBlockHash (number, offset, cbOffset) {
313 this.takeGas(20)
314
315 const diff = this.message.block.number - number
316 let opPromise
317
318 if (diff > 256 || diff <= 0) {
319 opPromise = Promise.resolve(new U256(0))
320 } else {
321 opPromise = this.state.get(['blockchain', number]).then(vertex => vertex.hash())
322 }
323
324 // wait for all the prevouse async ops to finish before running the callback
325 this.vm.pushOpsQueue(opPromise, cbOffset, hash => {
326 this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
327 })
328 }
329
330 /**
331 * Gets the block’s beneficiary address and loads into memory.
332 * @param offset
333 */
334 getBlockCoinbase (offset) {
335 this.takeGas(2)
336
337 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.message.block.header.coinbase)
338 }
339
340 /**
341 * Get the block’s timestamp.
342 * @return {integer}
343 */
344 getBlockTimestamp () {
345 this.takeGas(2)
346
347 return this.message.block.timestamp
348 }
349
350 /**
351 * Get the block’s number.
352 * @return {integer}
353 */
354 getBlockNumber () {
355 this.takeGas(2)
356
357 return this.message.block.number
358 }
359
360 /**
361 * Get the block’s difficulty.
362 * @return {integer}
363 */
364 getBlockDifficulty (offset) {
365 this.takeGas(2)
366
367 this.setMemory(offset, U256_SIZE_BYTES, this.message.block.difficulty.toMemory())
368 }
369
370 /**
371 * Get the block’s gas limit.
372 * @return {integer}
373 */
374 getBlockGasLimit () {
375 this.takeGas(2)
376
377 return this.message.gasLimit
378 }
379
380 /**
381 * Creates a new log in the current environment
382 * @param {integer} dataOffset the offset in memory to load the memory
383 * @param {integer} length the data length
384 * @param {integer} number of topics
385 */
386 log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
387 if (numberOfTopics < 0 || numberOfTopics > 4) {
388 throw new Error('Invalid numberOfTopics')
389 }
390
391 this.takeGas(375 + length * 8 + numberOfTopics * 375)
392
393 const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
394 const topics = []
395
396 if (numberOfTopics > 0) {
397 topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
398 }
399
400 if (numberOfTopics > 1) {
401 topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
402 }
403
404 if (numberOfTopics > 2) {
405 topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
406 }
407
408 if (numberOfTopics > 3) {
409 topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
410 }
411
412 this.kernel.sendMessage([this.kernel.root, 'logs'], new Message({
413 data: data,
414 topics: topics
415 }))
416 }
417
418 /**
419 * Creates a new contract with a given value.
420 * @param {integer} valueOffset the offset in memory to the value from
421 * @param {integer} dataOffset the offset to load the code for the new contract from
422 * @param {integer} length the data length
423 * @param (integer} resultOffset the offset to write the new contract address to
424 * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
425 */
426 create (valueOffset, dataOffset, length, resultOffset, cbIndex) {
427 this.takeGas(32000)
428
429 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
430 // if (length) {
431 // const code = this.getMemory(dataOffset, length).slice(0)
432 // }
433
434 let opPromise
435
436 if (value.gt(this.kernel.environment.value)) {
437 opPromise = Promise.resolve(new Buffer(20).fill(0))
438 } else {
439 // todo actully run the code
440 opPromise = Promise.resolve(ethUtil.generateAddress(this.kernel.environment.address, this.kernel.environment.nonce))
441 }
442
443 // wait for all the prevouse async ops to finish before running the callback
444 this.vm.pushOpsQueue(opPromise, cbIndex, address => {
445 this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address)
446 })
447 }
448
449 /**
450 * Sends a message with arbiatary data to a given address path
451 * @param {integer} addressOffset the offset to load the address path from
452 * @param {integer} valueOffset the offset to load the value from
453 * @param {integer} dataOffset the offset to load data from
454 * @param {integer} dataLength the length of data
455 * @param {integer} resultOffset the offset to store the result data at
456 * @param {integer} resultLength
457 * @param {integer} gas
458 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
459 */
460 _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
461 this.takeGas(40)
462 const gas = from64bit(gasHigh, gasLow)
463 // Load the params from mem
464 const address = [common.PARENT, common.PARENT, ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
465 const value = new U256(this.getMemory(valueOffset, U128_SIZE_BYTES))
466
467 // Special case for non-zero value; why does this exist?
468 if (!value.isZero()) {
469 this.takeGas(9000 - 2300 + gas)
470 this.takeGas(-gas)
471 }
472
473 const message = new Message({
474 to: address,
475 value: value
476 })
477
478 const messagePromise = this.kernel.send(message).then(result => {
479 if (result.exception) {
480 this.takeGas(25000)
481 }
482 })
483
484 // wait for all the prevouse async ops to finish before running the callback
485 this.vm.pushOpsQueue(messagePromise, 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 = ['accounts', ...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.state.root.get(path)
514 .catch(() => {
515 // TODO: handle errors
516 // the value was not found
517 return null
518 })
519
520 this.vm.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 key = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
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.state.get(key)
561 .then(vertex => vertex.value)
562 .catch(() => null)
563
564 this.vm.pushOpsQueue(opPromise, cbIndex, oldValue => {
565 if (valIsZero && oldValue) {
566 // delete a value
567 this.results.gasRefund += 15000
568 this.kernel.state.del(key)
569 } else {
570 if (!valIsZero && !oldValue) {
571 // creating a new value
572 this.takeGas(15000)
573 }
574 // update
575 this.kernel.state.set(key, 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 key = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
592 // get the value from the state
593 const opPromise = this.kernel.state.get([key])
594 .then(vertex => vertex.value)
595 .catch(() => new Uint8Array(32))
596
597 this.vm.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.results.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.results.selfDestruct = true
620 this.results.selfDestructAddress = this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)
621 this.results.gasRefund += 24000
622 }
623
624 getMemory (offset, length) {
625 return new Uint8Array(this.vm.memory(), offset, length)
626 }
627
628 setMemory (offset, length, value) {
629 const memory = new Uint8Array(this.vm.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.message.gas < amount) {
639 throw new Error('Ran out of gas')
640 }
641 this.message.gas -= 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