git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 218378ee81f2cf20c03ed1333553072db9dd4205

Files: 218378ee81f2cf20c03ed1333553072db9dd4205 / EVMinterface.js

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

Built with git-ssb-web