git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 5940f1f1ef3193889f5dd9c71bee73ed64311513

Files: 5940f1f1ef3193889f5dd9c71bee73ed64311513 / interface.js

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

Built with git-ssb-web