git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: f51b785e3c8a0cb4cdd5b659a4410b44e414b871

Files: f51b785e3c8a0cb4cdd5b659a4410b44e414b871 / interface.js

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

Built with git-ssb-web