git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: c326c8af2a729760e833b861163acade71770ff5

Files: c326c8af2a729760e833b861163acade71770ff5 / interface.js

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

Built with git-ssb-web