git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: de14490c984932fcb886b69855e66ae6d5ba7dac

Files: de14490c984932fcb886b69855e66ae6d5ba7dac / EVMinterface.js

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

Built with git-ssb-web