git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 452e5b0152ca519d0bb687f4b4771aeefeffdc24

Files: 452e5b0152ca519d0bb687f4b4771aeefeffdc24 / EVMinterface.js

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

Built with git-ssb-web