git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: af928b88a1d05571409d3c6fd288d451004654fa

Files: af928b88a1d05571409d3c6fd288d451004654fa / interface.js

18047 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 // FIXME: count properly
449 this.takeGas(40)
450
451 // Load the params from mem
452 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
453 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
454 const data = this.getMemory(dataOffset, dataLength).slice(0)
455 const [errorCode, result] = this.environment.callCode(gas, address, value, data)
456 this.setMemory(resultOffset, resultLength, result)
457 return errorCode
458 }
459
460 /**
461 * Message-call into this account with an alternative account’s code, but
462 * persisting the current values for sender and value.
463 * @param {integer} gas
464 * @param {integer} addressOffset the offset to load the address path from
465 * @param {integer} valueOffset the offset to load the value from
466 * @param {integer} dataOffset the offset to load data from
467 * @param {integer} dataLength the length of data
468 * @param {integer} resultOffset the offset to store the result data at
469 * @param {integer} resultLength
470 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
471 */
472 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
473 // FIXME: count properly
474 this.takeGas(40)
475
476 const data = this.getMemory(dataOffset, dataLength).slice(0)
477 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
478 const [errorCode, result] = this.environment.callDelegate(gas, address, data)
479 this.setMemory(resultOffset, resultLength, result)
480 return errorCode
481 }
482
483 /**
484 * store a value at a given path in long term storage which are both loaded
485 * from Memory
486 * @param {interger} pathOffest the memory offset to load the the path from
487 * @param {interger} valueOffset the memory offset to load the value from
488 */
489 storageStore (pathOffset, valueOffset) {
490 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
491 // copy the value
492 const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
493 const oldValue = this.environment.state.get(path)
494 const valIsZero = value.every((i) => i === 0)
495
496 this.takeGas(5000)
497
498 // write
499 if (!valIsZero && !oldValue) {
500 this.takeGas(15000)
501 }
502
503 // delete
504 if (valIsZero && oldValue) {
505 this.environment.gasRefund += 15000
506 this.environment.state.delete(path)
507 } else {
508 this.environment.state.set(path, value)
509 }
510 }
511
512 /**
513 * reterives a value at a given path in long term storage
514 * @param {interger} pathOffest the memory offset to load the the path from
515 * @param {interger} resultOffset the memory offset to load the value from
516 */
517 storageLoad (pathOffset, resultOffset) {
518 this.takeGas(50)
519
520 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
521 const result = this.environment.state.get(path) || new Uint8Array(32)
522 this.setMemory(resultOffset, U256_SIZE_BYTES, result)
523 }
524
525 /**
526 * Halt execution returning output data.
527 * @param {integer} offset the offset of the output data.
528 * @param {integer} length the length of the output data.
529 */
530 return (offset, length) {
531 if (length) {
532 this.environment.returnValue = this.getMemory(offset, length).slice(0)
533 }
534 }
535
536 /**
537 * Halt execution and register account for later deletion giving the remaining
538 * balance to an address path
539 * @param {integer} offset the offset to load the address from
540 */
541 selfDestruct (addressOffset) {
542 this.environment.selfDestruct = true
543 this.environment.selfDestructAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
544 this.environment.gasRefund += 24000
545 }
546
547 getMemory (offset, length) {
548 return new Uint8Array(this.module.exports.memory, offset, length)
549 }
550
551 setMemory (offset, length, value) {
552 const memory = new Uint8Array(this.module.exports.memory, offset, length)
553 memory.set(value)
554 }
555
556 /*
557 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
558 * because every caller of this method is trusted.
559 */
560 takeGas (amount) {
561 if (this.environment.gasLeft < amount) {
562 throw new Error('Ran out of gas')
563 }
564 this.environment.gasLeft -= amount
565 }
566}
567
568// converts a 64 bit number to a JS number
569function from64bit (high, low) {
570 if (high < 0) {
571 // convert from a 32-bit two's compliment
572 high = 0x100000000 - high
573 }
574 if (low < 0) {
575 // convert from a 32-bit two's compliment
576 low = 0x100000000 - low
577 }
578 // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
579 return (high * 4294967296) + low
580}
581

Built with git-ssb-web