git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 84e730af9e78896d112fb5818ef323f5d767f569

Files: 84e730af9e78896d112fb5818ef323f5d767f569 / interface.js

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

Built with git-ssb-web