git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 89c0461616621d14c849ba592bac2c9ca85978d9

Files: 89c0461616621d14c849ba592bac2c9ca85978d9 / interface.js

17037 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 throw new Error('Negative gas deduction requested')
70 }
71
72 this.takeGas(amount)
73 }
74
75 /**
76 * Returns the current amount of gas
77 * @return {integer}
78 */
79 getGasLeft () {
80 this.takeGas(2)
81
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 const callData = this.environment.callData.slice(dataOffset, dataOffset + length)
167 this.setMemory(offset, length, callData)
168 }
169
170 /**
171 * Copys the input data in current environment to memory. This pertains to
172 * the input data passed with the message call instruction or transaction.
173 * @param {integer} offset the offset in memory to load into
174 * @param {integer} dataOffset the offset in the input data
175 */
176 callDataCopy256 (offset, dataOffset) {
177 this.takeGas(3)
178 const callData = this.environment.callData.slice(dataOffset, dataOffset + 32)
179 this.setMemory(offset, U256_SIZE_BYTES, callData)
180 }
181
182 /**
183 * Gets the size of code running in current environment.
184 * @return {interger}
185 */
186 getCodeSize () {
187 this.takeGas(2)
188
189 return this.environment.code.length
190 }
191
192 /**
193 * Copys the code running in current environment to memory.
194 * @param {integer} offset the memory offset
195 * @param {integer} codeOffset the code offset
196 * @param {integer} length the length of code to copy
197 */
198 codeCopy (resultOffset, codeOffset, length) {
199 this.takeGas(3 + Math.ceil(length / 32) * 3)
200
201 const code = new Uint8Array(this.environment.code, codeOffset, length)
202 this.setMemory(resultOffset, length, code)
203 }
204
205 /**
206 * Get size of an account’s code.
207 * @param {integer} addressOffset the offset in memory to load the address from
208 * @return {integer}
209 */
210 getExternalCodeSize (addressOffset) {
211 this.takeGas(20)
212
213 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
214 const code = this.environment.getCode(address)
215 return code.length
216 }
217
218 /**
219 * Copys the code of an account to memory.
220 * @param {integer} addressOffset the memory offset of the address
221 * @param {integer} resultOffset the memory offset
222 * @param {integer} codeOffset the code offset
223 * @param {integer} length the length of code to copy
224 */
225 externalCodeCopy (addressOffset, resultOffset, codeOffset, length) {
226 this.takeGas(20 + Math.ceil(length / 32) * 3)
227
228 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
229 let code = this.environment.getCode(address)
230 code = new Uint8Array(code, codeOffset, length)
231 this.setMemory(resultOffset, length, code)
232 }
233
234 /**
235 * Gets price of gas in current environment.
236 * @return {integer}
237 */
238 getTxGasPrice () {
239 this.takeGas(2)
240
241 return this.environment.gasPrice
242 }
243
244 /**
245 * Gets the hash of one of the 256 most recent complete blocks.
246 * @param {integer} number which block to load
247 * @param {integer} offset the offset to load the hash into
248 */
249 getBlockHash (number, offset) {
250 this.takeGas(20)
251
252 const diff = this.environment.block.number - number
253 let hash
254
255 if (diff > 256 || diff <= 0) {
256 hash = new U256(0)
257 } else {
258 hash = new U256(this.environment.getBlockHash(number))
259 }
260 this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
261 }
262
263 /**
264 * Gets the block’s beneficiary address and loads into memory.
265 * @param offset
266 */
267 getBlockCoinbase (offset) {
268 this.takeGas(2)
269
270 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.block.coinbase.toMemory())
271 }
272
273 /**
274 * Get the block’s timestamp.
275 * @return {integer}
276 */
277 getBlockTimestamp () {
278 this.takeGas(2)
279
280 return this.environment.block.timestamp
281 }
282
283 /**
284 * Get the block’s number.
285 * @return {integer}
286 */
287 getBlockNumber () {
288 this.takeGas(2)
289
290 return this.environment.block.number
291 }
292
293 /**
294 * Get the block’s difficulty.
295 * @return {integer}
296 */
297 getBlockDifficulty (offset) {
298 this.takeGas(2)
299
300 this.setMemory(offset, U256_SIZE_BYTES, this.environment.block.difficulty.toMemory())
301 }
302
303 /**
304 * Get the block’s gas limit.
305 * @return {integer}
306 */
307 getBlockGasLimit () {
308 this.takeGas(2)
309
310 return this.environment.block.gasLimit
311 }
312
313 /**
314 * Creates a new log in the current environment
315 * @param {integer} dataOffset the offset in memory to load the memory
316 * @param {integer} length the data length
317 * @param {integer} number of topics
318 */
319 log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
320 if (numberOfTopics < 0 || numberOfTopics > 4) {
321 throw new Error('Invalid numberOfTopics')
322 }
323
324 this.takeGas(375 + length * 8 + numberOfTopics * 375)
325
326 const data = this.getMemory(dataOffset, length).slice(0)
327 let topics = []
328
329 if (numberOfTopics > 0) {
330 topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
331 }
332
333 if (numberOfTopics > 1) {
334 topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
335 }
336
337 if (numberOfTopics > 2) {
338 topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
339 }
340
341 if (numberOfTopics > 3) {
342 topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
343 }
344
345 this.environment.logs.push({
346 data: data,
347 topics: topics
348 })
349 }
350
351 /**
352 * Creates a new contract with a given value.
353 * @param {integer} valueOffset the offset in memory to the value from
354 * @param {integer} dataOffset the offset to load the code for the new contract from
355 * @param {integer} length the data length
356 */
357 create (valueOffset, dataOffset, length) {
358 this.takeGas(32000)
359
360 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
361 const data = this.getMemory(dataOffset, length).slice(0)
362 const result = this.environment.create(value, data)
363 return result
364 }
365
366 /**
367 * Sends a message with arbiatary data to a given address path
368 * @param {integer} addressOffset the offset to load the address path from
369 * @param {integer} valueOffset the offset to load the value from
370 * @param {integer} dataOffset the offset to load data from
371 * @param {integer} dataLength the length of data
372 * @param {integer} resultOffset the offset to store the result data at
373 * @param {integer} resultLength
374 * @param {integer} gas
375 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
376 */
377 call (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
378 // FIXME: count properly
379 this.takeGas(40)
380
381 // Load the params from mem
382 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
383 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
384 const data = this.getMemory(dataOffset, dataLength).slice(0)
385 // Run the call
386 const [result, errorCode] = this.environment.call(gas, address, value, data)
387 this.setMemory(resultOffset, resultLength, result)
388 return errorCode
389 }
390
391 /**
392 * Message-call into this account with an alternative account’s code.
393 * @param {integer} addressOffset the offset to load the address path from
394 * @param {integer} valueOffset the offset to load the value from
395 * @param {integer} dataOffset the offset to load data from
396 * @param {integer} dataLength the length of data
397 * @param {integer} resultOffset the offset to store the result data at
398 * @param {integer} resultLength
399 * @param {integer} gas
400 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
401 */
402 callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
403 // FIXME: count properly
404 this.takeGas(40)
405
406 // Load the params from mem
407 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
408 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
409 const data = this.getMemory(dataOffset, dataLength).slice(0)
410 // Run the call
411 const [result, errorCode] = this.environment.callCode(gas, address, value, data)
412 this.setMemory(resultOffset, resultLength, result)
413 return errorCode
414 }
415
416 /**
417 * Message-call into this account with an alternative account’s code, but
418 * persisting the current values for sender and value.
419 * @param {integer} gas
420 * @param {integer} addressOffset the offset to load the address path from
421 * @param {integer} valueOffset the offset to load the value from
422 * @param {integer} dataOffset the offset to load data from
423 * @param {integer} dataLength the length of data
424 * @param {integer} resultOffset the offset to store the result data at
425 * @param {integer} resultLength
426 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
427 */
428 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
429 // FIXME: count properly
430 this.takeGas(40)
431
432 const data = this.getMemory(dataOffset, dataLength).slice(0)
433 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
434 const [result, errorCode] = this.environment.callDelegate(gas, address, data)
435 this.setMemory(resultOffset, resultLength, result)
436 return errorCode
437 }
438
439 /**
440 * store a value at a given path in long term storage which are both loaded
441 * from Memory
442 * @param {interger} pathOffest the memory offset to load the the path from
443 * @param {interger} valueOffset the memory offset to load the value from
444 */
445 storageStore (pathOffset, valueOffset) {
446 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
447 // copy the value
448 const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
449 const oldValue = this.environment.state.get(path)
450 const valIsZero = value.every((i) => i === 0)
451
452 this.takeGas(5000)
453
454 // write
455 if (!valIsZero && !oldValue) {
456 this.takeGas(15000)
457 }
458
459 // delete
460 if (valIsZero && oldValue) {
461 this.environment.gasRefund += 15000
462 this.environment.state.delete(path)
463 } else {
464 this.environment.state.set(path, value)
465 }
466 }
467
468 /**
469 * reterives a value at a given path in long term storage
470 * @param {interger} pathOffest the memory offset to load the the path from
471 * @param {interger} resultOffset the memory offset to load the value from
472 */
473 storageLoad (pathOffset, resultOffset) {
474 this.takeGas(50)
475
476 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
477 const result = this.environment.state.get(path)
478 this.setMemory(resultOffset, U256_SIZE_BYTES, result)
479 }
480
481 /**
482 * Halt execution returning output data.
483 * @param {integer} offset the offset of the output data.
484 * @param {integer} length the length of the output data.
485 */
486 return (offset, length) {
487 this.environment.returnValue = this.getMemory(offset, length).slice(0)
488 }
489
490 /**
491 * Halt execution and register account for later deletion giving the remaining
492 * balance to an address path
493 * @param {integer} offset the offset to load the address from
494 */
495 selfDestruct (addressOffset) {
496 this.environment.suicideAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
497 this.environment.gasRefund += 24000
498 }
499
500 getMemory (offset, length) {
501 return new Uint8Array(this.module.exports.memory, offset, length)
502 }
503
504 setMemory (offset, length, value) {
505 const memory = new Uint8Array(this.module.exports.memory, offset, length)
506 memory.set(value)
507 }
508
509 /*
510 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
511 * because every caller of this method is trusted.
512 */
513 takeGas (amount) {
514 if (this.environment.gasLeft < amount) {
515 throw new Error('Ran out of gas')
516 }
517 this.environment.gasLeft -= amount
518 }
519}
520
521//
522// Polyfill required unless this is sorted: https://bugs.chromium.org/p/chromium/issues/detail?id=633895
523//
524// Polyfill from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
525//
526Function.prototype.bind = function (oThis) { // eslint-disable-line
527 if (typeof this !== 'function') {
528 // closest thing possible to the ECMAScript 5
529 // internal IsCallable function
530 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
531 }
532
533 var aArgs = Array.prototype.slice.call(arguments, 1)
534 var fToBind = this
535 var fNOP = function () {}
536 var fBound = function () {
537 return fToBind.apply(this instanceof fNOP ? this : oThis,
538 aArgs.concat(Array.prototype.slice.call(arguments)))
539 }
540
541 if (this.prototype) {
542 // Function.prototype doesn't have a prototype property
543 fNOP.prototype = this.prototype
544 }
545
546 fBound.prototype = new fNOP() // eslint-disable-line new-cap
547
548 return fBound
549}
550

Built with git-ssb-web