git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: be42af81974876ce832fa149178075c8fca67321

Files: be42af81974876ce832fa149178075c8fca67321 / interface.js

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

Built with git-ssb-web