git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 5626d2b938fbe2739a9576eb4bf4a2bdced83d13

Files: 5626d2b938fbe2739a9576eb4bf4a2bdced83d13 / interface.js

18337 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('./address.js')
6const U256 = require('./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) {
17 this.environment = environment
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 get exportTable () {
31 let exportMethods = [
32 // include all the public methods according to the Ethereum Environment Interface (EEI) r1
33 'getAddress',
34 'getBalance',
35 'getTxOrigin',
36 'getCaller',
37 'getCallValue',
38 'getCallDataSize',
39 'callDataCopy',
40 'callDataCopy256',
41 'getCodeSize',
42 'codeCopy',
43 'getExternalCodeSize',
44 'externalCodeCopy',
45 'getTxGasPrice',
46 'getBlockHash',
47 'getBlockCoinbase',
48 'getBlockTimestamp',
49 'getBlockNumber',
50 'getBlockDifficulty',
51 'getBlockGasLimit',
52 'log',
53 'create',
54 'callCode',
55 'callDelegate',
56 'storageStore',
57 'storageLoad',
58 'return',
59 'selfDestruct'
60 ]
61 let ret = {}
62 exportMethods.forEach((method) => {
63 ret[method] = this[method].bind(this)
64 })
65 return ret
66 }
67
68 setModule (mod) {
69 this.module = mod
70 }
71
72 /**
73 * Subtracts an amount to the gas counter
74 * @param {integer} amount the amount to subtract to the gas counter
75 */
76 _useGas (high, low) {
77 this.takeGas(from64bit(high, low))
78 }
79
80 /**
81 * Returns the current amount of gas
82 * @return {integer}
83 */
84 _getGasLeftHigh () {
85 return Math.floor(this.environment.gasLeft / 4294967296)
86 }
87
88 /**
89 * Returns the current amount of gas
90 * @return {integer}
91 */
92 _getGasLeftLow () {
93 return this.environment.gasLeft
94 }
95
96 /**
97 * Gets address of currently executing account and loads it into memory at
98 * the given offset.
99 * @param {integer} offset
100 */
101 getAddress (offset) {
102 this.takeGas(2)
103
104 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.address.toMemory())
105 }
106
107 /**
108 * Gets balance of the given account and loads it into memory at the given
109 * offset.
110 * @param {integer} addressOffset the memory offset to laod the address
111 * @param {integer} resultOffset
112 */
113 getBalance (addressOffset, offset) {
114 this.takeGas(20)
115
116 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
117 // call the parent contract and ask for the balance of one of its child contracts
118 const balance = this.environment.getBalance(address)
119 this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES))
120 }
121
122 /**
123 * Gets the execution's origination address and loads it into memory at the
124 * given offset. This is the sender of original transaction; it is never an
125 * account with non-empty associated code.
126 * @param {integer} offset
127 */
128 getTxOrigin (offset) {
129 this.takeGas(2)
130
131 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.origin.toMemory())
132 }
133
134 /**
135 * Gets caller address and loads it into memory at the given offset. This is
136 * the address of the account that is directly responsible for this execution.
137 * @param {integer} offset
138 */
139 getCaller (offset) {
140 this.takeGas(2)
141
142 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.caller.toMemory())
143 }
144
145 /**
146 * Gets the deposited value by the instruction/transaction responsible for
147 * this execution and loads it into memory at the given location.
148 * @param {integer} offset
149 */
150 getCallValue (offset) {
151 this.takeGas(2)
152
153 this.setMemory(offset, U128_SIZE_BYTES, this.environment.callValue.toMemory(U128_SIZE_BYTES))
154 }
155
156 /**
157 * Get size of input data in current environment. This pertains to the input
158 * data passed with the message call instruction or transaction.
159 * @return {integer}
160 */
161 getCallDataSize () {
162 this.takeGas(2)
163
164 return this.environment.callData.length
165 }
166
167 /**
168 * Copys the input data in current environment to memory. This pertains to
169 * the input data passed with the message call instruction or transaction.
170 * @param {integer} offset the offset in memory to load into
171 * @param {integer} dataOffset the offset in the input data
172 * @param {integer} length the length of data to copy
173 */
174 callDataCopy (offset, dataOffset, length) {
175 this.takeGas(3 + Math.ceil(length / 32) * 3)
176
177 if (length) {
178 const callData = this.environment.callData.slice(dataOffset, dataOffset + length)
179 this.setMemory(offset, length, callData)
180 }
181 }
182
183 /**
184 * Copys the input data in current environment to memory. This pertains to
185 * the input data passed with the message call instruction or transaction.
186 * @param {integer} offset the offset in memory to load into
187 * @param {integer} dataOffset the offset in the input data
188 */
189 callDataCopy256 (offset, dataOffset) {
190 this.takeGas(3)
191 const callData = this.environment.callData.slice(dataOffset, dataOffset + 32)
192 this.setMemory(offset, U256_SIZE_BYTES, callData)
193 }
194
195 /**
196 * Gets the size of code running in current environment.
197 * @return {interger}
198 */
199 getCodeSize () {
200 this.takeGas(2)
201
202 return this.environment.code.length
203 }
204
205 /**
206 * Copys the code running in current environment to memory.
207 * @param {integer} offset the memory offset
208 * @param {integer} codeOffset the code offset
209 * @param {integer} length the length of code to copy
210 */
211 codeCopy (resultOffset, codeOffset, length) {
212 this.takeGas(3 + Math.ceil(length / 32) * 3)
213
214 if (length) {
215 const code = this.environment.code.slice(codeOffset, codeOffset + length)
216 this.setMemory(resultOffset, length, code)
217 }
218 }
219
220 /**
221 * Get size of an account’s code.
222 * @param {integer} addressOffset the offset in memory to load the address from
223 * @return {integer}
224 */
225 getExternalCodeSize (addressOffset) {
226 this.takeGas(20)
227
228 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
229 const code = this.environment.getCode(address)
230 return code.length
231 }
232
233 /**
234 * Copys the code of an account to memory.
235 * @param {integer} addressOffset the memory offset of the address
236 * @param {integer} resultOffset the memory offset
237 * @param {integer} codeOffset the code offset
238 * @param {integer} length the length of code to copy
239 */
240 externalCodeCopy (addressOffset, resultOffset, codeOffset, length) {
241 this.takeGas(20 + Math.ceil(length / 32) * 3)
242
243 if (length) {
244 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
245 let code = this.environment.getCode(address)
246 code = code.slice(codeOffset, codeOffset + length)
247 this.setMemory(resultOffset, length, code)
248 }
249 }
250
251 /**
252 * Gets price of gas in current environment.
253 * @return {integer}
254 */
255 getTxGasPrice () {
256 this.takeGas(2)
257
258 return this.environment.gasPrice
259 }
260
261 /**
262 * Gets the hash of one of the 256 most recent complete blocks.
263 * @param {integer} number which block to load
264 * @param {integer} offset the offset to load the hash into
265 */
266 getBlockHash (number, offset) {
267 this.takeGas(20)
268
269 const diff = this.environment.block.number - number
270 let hash
271
272 if (diff > 256 || diff <= 0) {
273 hash = new U256(0)
274 } else {
275 hash = new U256(this.environment.getBlockHash(number))
276 }
277 this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
278 }
279
280 /**
281 * Gets the block’s beneficiary address and loads into memory.
282 * @param offset
283 */
284 getBlockCoinbase (offset) {
285 this.takeGas(2)
286
287 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.block.coinbase.toMemory())
288 }
289
290 /**
291 * Get the block’s timestamp.
292 * @return {integer}
293 */
294 getBlockTimestamp () {
295 this.takeGas(2)
296
297 return this.environment.block.timestamp
298 }
299
300 /**
301 * Get the block’s number.
302 * @return {integer}
303 */
304 getBlockNumber () {
305 this.takeGas(2)
306
307 return this.environment.block.number
308 }
309
310 /**
311 * Get the block’s difficulty.
312 * @return {integer}
313 */
314 getBlockDifficulty (offset) {
315 this.takeGas(2)
316
317 this.setMemory(offset, U256_SIZE_BYTES, this.environment.block.difficulty.toMemory())
318 }
319
320 /**
321 * Get the block’s gas limit.
322 * @return {integer}
323 */
324 getBlockGasLimit () {
325 this.takeGas(2)
326
327 return this.environment.block.gasLimit
328 }
329
330 /**
331 * Creates a new log in the current environment
332 * @param {integer} dataOffset the offset in memory to load the memory
333 * @param {integer} length the data length
334 * @param {integer} number of topics
335 */
336 log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
337 if (numberOfTopics < 0 || numberOfTopics > 4) {
338 throw new Error('Invalid numberOfTopics')
339 }
340
341 this.takeGas(375 + length * 8 + numberOfTopics * 375)
342
343 const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
344 const topics = []
345
346 if (numberOfTopics > 0) {
347 topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
348 }
349
350 if (numberOfTopics > 1) {
351 topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
352 }
353
354 if (numberOfTopics > 2) {
355 topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
356 }
357
358 if (numberOfTopics > 3) {
359 topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
360 }
361
362 this.environment.logs.push({
363 data: data,
364 topics: topics
365 })
366 }
367
368 /**
369 * Creates a new contract with a given value.
370 * @param {integer} valueOffset the offset in memory to the value from
371 * @param {integer} dataOffset the offset to load the code for the new contract from
372 * @param {integer} length the data length
373 * @param (integer} resultOffset the offset to write the new contract address to
374 * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
375 */
376 create (valueOffset, dataOffset, length, resultOffset) {
377 this.takeGas(32000)
378
379 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
380 if (length) {
381 const data = this.getMemory(dataOffset, length).slice(0)
382 // const [errorCode, address] = this.environment.create(value, data)
383 }
384 let address
385 if (value.gt(this.environment.value)) {
386 address = new Address()
387 } else {
388 address = new Address('0x945304eb96065b2a98b57a48a06ae28d285a71b5')
389 }
390 this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address.toMemory())
391 // return errorCode
392 }
393
394 /**
395 * Sends a message with arbiatary data to a given address path
396 * @param {integer} addressOffset the offset to load the address path from
397 * @param {integer} valueOffset the offset to load the value from
398 * @param {integer} dataOffset the offset to load data from
399 * @param {integer} dataLength the length of data
400 * @param {integer} resultOffset the offset to store the result data at
401 * @param {integer} resultLength
402 * @param {integer} gas
403 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
404 */
405 _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
406 const gas = from64bit(gasHigh, gasLow)
407 console.log(gas);
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) {
503 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
504 // copy the value
505 const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
506 const oldValue = this.environment.state.get(path)
507 const valIsZero = value.every((i) => i === 0)
508
509 this.takeGas(5000)
510
511 // write
512 if (!valIsZero && !oldValue) {
513 this.takeGas(15000)
514 }
515
516 // delete
517 if (valIsZero && oldValue) {
518 this.environment.gasRefund += 15000
519 this.environment.state.delete(path)
520 } else {
521 this.environment.state.set(path, value)
522 }
523 }
524
525 /**
526 * reterives a value at a given path in long term storage
527 * @param {interger} pathOffest the memory offset to load the the path from
528 * @param {interger} resultOffset the memory offset to load the value from
529 */
530 storageLoad (pathOffset, resultOffset) {
531 this.takeGas(50)
532
533 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
534 const result = this.environment.state.get(path) || new Uint8Array(32)
535 this.setMemory(resultOffset, U256_SIZE_BYTES, result)
536 }
537
538 /**
539 * Halt execution returning output data.
540 * @param {integer} offset the offset of the output data.
541 * @param {integer} length the length of the output data.
542 */
543 return (offset, length) {
544 if (length) {
545 this.environment.returnValue = this.getMemory(offset, length).slice(0)
546 }
547 }
548
549 /**
550 * Halt execution and register account for later deletion giving the remaining
551 * balance to an address path
552 * @param {integer} offset the offset to load the address from
553 */
554 selfDestruct (addressOffset) {
555 this.environment.selfDestruct = true
556 this.environment.selfDestructAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
557 this.environment.gasRefund += 24000
558 }
559
560 getMemory (offset, length) {
561 return new Uint8Array(this.module.exports.memory, offset, length)
562 }
563
564 setMemory (offset, length, value) {
565 const memory = new Uint8Array(this.module.exports.memory, offset, length)
566 memory.set(value)
567 }
568
569 /*
570 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
571 * because every caller of this method is trusted.
572 */
573 takeGas (amount) {
574 if (this.environment.gasLeft < amount) {
575 throw new Error('Ran out of gas')
576 }
577 this.environment.gasLeft -= amount
578 }
579}
580
581// converts a 64 bit number to a JS number
582function from64bit (high, low) {
583 if (high < 0) {
584 // convert from a 32-bit two's compliment
585 high = 0x100000000 - high
586 }
587 if (low < 0) {
588 // convert from a 32-bit two's compliment
589 low = 0x100000000 - low
590 }
591 // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
592 return (high * 4294967296) + low
593}
594

Built with git-ssb-web