git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 530ddd254210fc24ecf91434d349a5162a554763

Files: 530ddd254210fc24ecf91434d349a5162a554763 / EVMinterface.js

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

Built with git-ssb-web