git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 8acbfdf6688be5aabb51397bfe951bb0997b1c8b

Files: 8acbfdf6688be5aabb51397bfe951bb0997b1c8b / interface.js

18388 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 return this.environment.gasLeft
83 }
84
85 /**
86 * Gets address of currently executing account and loads it into memory at
87 * the given offset.
88 * @param {integer} offset
89 */
90 getAddress (offset) {
91 this.takeGas(2)
92
93 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.address.toMemory())
94 }
95
96 /**
97 * Gets balance of the given account and loads it into memory at the given
98 * offset.
99 * @param {integer} addressOffset the memory offset to laod the address
100 * @param {integer} resultOffset
101 */
102 getBalance (addressOffset, offset) {
103 this.takeGas(20)
104
105 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
106 // call the parent contract and ask for the balance of one of its child contracts
107 const balance = this.environment.getBalance(address)
108 this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES))
109 }
110
111 /**
112 * Gets the execution's origination address and loads it into memory at the
113 * given offset. This is the sender of original transaction; it is never an
114 * account with non-empty associated code.
115 * @param {integer} offset
116 */
117 getTxOrigin (offset) {
118 this.takeGas(2)
119
120 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.origin.toMemory())
121 }
122
123 /**
124 * Gets caller address and loads it into memory at the given offset. This is
125 * the address of the account that is directly responsible for this execution.
126 * @param {integer} offset
127 */
128 getCaller (offset) {
129 this.takeGas(2)
130
131 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.caller.toMemory())
132 }
133
134 /**
135 * Gets the deposited value by the instruction/transaction responsible for
136 * this execution and loads it into memory at the given location.
137 * @param {integer} offset
138 */
139 getCallValue (offset) {
140 this.takeGas(2)
141
142 this.setMemory(offset, U128_SIZE_BYTES, this.environment.callValue.toMemory(U128_SIZE_BYTES))
143 }
144
145 /**
146 * Get size of input data in current environment. This pertains to the input
147 * data passed with the message call instruction or transaction.
148 * @return {integer}
149 */
150 getCallDataSize () {
151 this.takeGas(2)
152
153 return this.environment.callData.length
154 }
155
156 /**
157 * Copys the input data in current environment to memory. This pertains to
158 * the input data passed with the message call instruction or transaction.
159 * @param {integer} offset the offset in memory to load into
160 * @param {integer} dataOffset the offset in the input data
161 * @param {integer} length the length of data to copy
162 */
163 callDataCopy (offset, dataOffset, length) {
164 this.takeGas(3 + Math.ceil(length / 32) * 3)
165
166 if (length) {
167 const callData = this.environment.callData.slice(dataOffset, dataOffset + length)
168 this.setMemory(offset, length, callData)
169 }
170 }
171
172 /**
173 * Copys the input data in current environment to memory. This pertains to
174 * the input data passed with the message call instruction or transaction.
175 * @param {integer} offset the offset in memory to load into
176 * @param {integer} dataOffset the offset in the input data
177 */
178 callDataCopy256 (offset, dataOffset) {
179 this.takeGas(3)
180 const callData = this.environment.callData.slice(dataOffset, dataOffset + 32)
181 this.setMemory(offset, U256_SIZE_BYTES, callData)
182 }
183
184 /**
185 * Gets the size of code running in current environment.
186 * @return {interger}
187 */
188 getCodeSize () {
189 this.takeGas(2)
190
191 return this.environment.code.length
192 }
193
194 /**
195 * Copys the code running in current environment to memory.
196 * @param {integer} offset the memory offset
197 * @param {integer} codeOffset the code offset
198 * @param {integer} length the length of code to copy
199 */
200 codeCopy (resultOffset, codeOffset, length) {
201 this.takeGas(3 + Math.ceil(length / 32) * 3)
202
203 if (length) {
204 const code = this.environment.code.slice(codeOffset, codeOffset + length)
205 this.setMemory(resultOffset, length, code)
206 }
207 }
208
209 /**
210 * Get size of an account’s code.
211 * @param {integer} addressOffset the offset in memory to load the address from
212 * @return {integer}
213 */
214 getExternalCodeSize (addressOffset) {
215 this.takeGas(20)
216
217 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
218 const code = this.environment.getCode(address)
219 return code.length
220 }
221
222 /**
223 * Copys the code of an account to memory.
224 * @param {integer} addressOffset the memory offset of the address
225 * @param {integer} resultOffset the memory offset
226 * @param {integer} codeOffset the code offset
227 * @param {integer} length the length of code to copy
228 */
229 externalCodeCopy (addressOffset, resultOffset, codeOffset, length) {
230 this.takeGas(20 + Math.ceil(length / 32) * 3)
231
232 if (length) {
233 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
234 let code = this.environment.getCode(address)
235 code = code.slice(codeOffset, codeOffset + length)
236 this.setMemory(resultOffset, length, code)
237 }
238 }
239
240 /**
241 * Gets price of gas in current environment.
242 * @return {integer}
243 */
244 getTxGasPrice () {
245 this.takeGas(2)
246
247 return this.environment.gasPrice
248 }
249
250 /**
251 * Gets the hash of one of the 256 most recent complete blocks.
252 * @param {integer} number which block to load
253 * @param {integer} offset the offset to load the hash into
254 */
255 getBlockHash (number, offset) {
256 this.takeGas(20)
257
258 const diff = this.environment.block.number - number
259 let hash
260
261 if (diff > 256 || diff <= 0) {
262 hash = new U256(0)
263 } else {
264 hash = new U256(this.environment.getBlockHash(number))
265 }
266 this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
267 }
268
269 /**
270 * Gets the block’s beneficiary address and loads into memory.
271 * @param offset
272 */
273 getBlockCoinbase (offset) {
274 this.takeGas(2)
275
276 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.block.coinbase.toMemory())
277 }
278
279 /**
280 * Get the block’s timestamp.
281 * @return {integer}
282 */
283 getBlockTimestamp () {
284 this.takeGas(2)
285
286 return this.environment.block.timestamp
287 }
288
289 /**
290 * Get the block’s number.
291 * @return {integer}
292 */
293 getBlockNumber () {
294 this.takeGas(2)
295
296 return this.environment.block.number
297 }
298
299 /**
300 * Get the block’s difficulty.
301 * @return {integer}
302 */
303 getBlockDifficulty (offset) {
304 this.takeGas(2)
305
306 this.setMemory(offset, U256_SIZE_BYTES, this.environment.block.difficulty.toMemory())
307 }
308
309 /**
310 * Get the block’s gas limit.
311 * @return {integer}
312 */
313 getBlockGasLimit () {
314 this.takeGas(2)
315
316 return this.environment.block.gasLimit
317 }
318
319 /**
320 * Creates a new log in the current environment
321 * @param {integer} dataOffset the offset in memory to load the memory
322 * @param {integer} length the data length
323 * @param {integer} number of topics
324 */
325 log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
326 if (numberOfTopics < 0 || numberOfTopics > 4) {
327 throw new Error('Invalid numberOfTopics')
328 }
329
330 this.takeGas(375 + length * 8 + numberOfTopics * 375)
331
332 const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
333 const topics = []
334
335 if (numberOfTopics > 0) {
336 topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
337 }
338
339 if (numberOfTopics > 1) {
340 topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
341 }
342
343 if (numberOfTopics > 2) {
344 topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
345 }
346
347 if (numberOfTopics > 3) {
348 topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
349 }
350
351 this.environment.logs.push({
352 data: data,
353 topics: topics
354 })
355 }
356
357 /**
358 * Creates a new contract with a given value.
359 * @param {integer} valueOffset the offset in memory to the value from
360 * @param {integer} dataOffset the offset to load the code for the new contract from
361 * @param {integer} length the data length
362 * @param (integer} resultOffset the offset to write the new contract address to
363 * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
364 */
365 create (valueOffset, dataOffset, length, resultOffset) {
366 this.takeGas(32000)
367
368 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
369 if (length) {
370 const data = this.getMemory(dataOffset, length).slice(0)
371 // const [errorCode, address] = this.environment.create(value, data)
372 }
373 let address
374 if (value.gt(this.environment.value)) {
375 address = new Address()
376 } else {
377 address = new Address('0x945304eb96065b2a98b57a48a06ae28d285a71b5')
378 }
379 this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address.toMemory())
380 // return errorCode
381 }
382
383 /**
384 * Sends a message with arbiatary data to a given address path
385 * @param {integer} addressOffset the offset to load the address path from
386 * @param {integer} valueOffset the offset to load the value from
387 * @param {integer} dataOffset the offset to load data from
388 * @param {integer} dataLength the length of data
389 * @param {integer} resultOffset the offset to store the result data at
390 * @param {integer} resultLength
391 * @param {integer} gas
392 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
393 */
394 call (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
395 // Load the params from mem
396 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
397 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
398
399 this.takeGas(40)
400
401 // const data = this.getMemory(dataOffset, dataLength).slice(0)
402
403 // // Special case for calling into empty account
404 // if (!this.environment.isAccountPresent(address)) {
405 // this.takeGas(25000)
406 // }
407 if (address.lt(new U256(4))) {
408 this.takeGas(25000)
409 }
410 // // Special case for non-zero value
411 if (!value.isZero()) {
412 this.takeGas(9000 - 2300 + gas)
413 this.takeGas(-gas)
414 }
415
416 // const [errorCode, result] = this.environment.call(gas, address, value, data)
417 // this.setMemory(resultOffset, resultLength, result)
418 // return errorCode
419 //
420 return 1
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 // Load the params from mem
436 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
437 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
438
439 this.takeGas(40)
440
441 // const data = this.getMemory(dataOffset, dataLength).slice(0)
442
443 // // Special case for calling into empty account
444 // if (!this.environment.isAccountPresent(address)) {
445 // this.takeGas(25000)
446 // }
447 // // Special case for non-zero value
448 if (!value.isZero()) {
449 this.takeGas(9000 - 2300 + gas)
450 this.takeGas(-gas)
451 }
452
453 // const [errorCode, result] = this.environment.call(gas, address, value, data)
454 // this.setMemory(resultOffset, resultLength, result)
455 // return errorCode
456 //
457 return 1
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//
569// Polyfill required unless this is sorted: https://bugs.chromium.org/p/chromium/issues/detail?id=633895
570//
571// Polyfill from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
572//
573Function.prototype.bind = function (oThis) { // eslint-disable-line
574 if (typeof this !== 'function') {
575 // closest thing possible to the ECMAScript 5
576 // internal IsCallable function
577 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
578 }
579
580 var aArgs = Array.prototype.slice.call(arguments, 1)
581 var fToBind = this
582 var fNOP = function () {}
583 var fBound = function () {
584 return fToBind.apply(this instanceof fNOP ? this : oThis,
585 aArgs.concat(Array.prototype.slice.call(arguments)))
586 }
587
588 if (this.prototype) {
589 // Function.prototype doesn't have a prototype property
590 fNOP.prototype = this.prototype
591 }
592
593 fBound.prototype = new fNOP() // eslint-disable-line new-cap
594
595 return fBound
596}
597

Built with git-ssb-web