git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 43bf2f2e8a30e0f3f0ae3c32631f5507c7449d67

Files: 43bf2f2e8a30e0f3f0ae3c32631f5507c7449d67 / interface.js

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

Built with git-ssb-web