git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 6d77ad101453e8f18c589971f99e9d718ba6a243

Files: 6d77ad101453e8f18c589971f99e9d718ba6a243 / interface.js

17523 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 * @param (integer} resultOffset the offset to write the new contract address to
357 * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
358 */
359 create (valueOffset, dataOffset, length, resultOffset) {
360 this.takeGas(32000 + length * 200)
361
362 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
363 const data = this.getMemory(dataOffset, length).slice(0)
364 const [errorCode, address] = this.environment.create(value, data)
365 this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address.toMemory())
366 return errorCode
367 }
368
369 /**
370 * Sends a message with arbiatary data to a given address path
371 * @param {integer} addressOffset the offset to load the address path from
372 * @param {integer} valueOffset the offset to load the value from
373 * @param {integer} dataOffset the offset to load data from
374 * @param {integer} dataLength the length of data
375 * @param {integer} resultOffset the offset to store the result data at
376 * @param {integer} resultLength
377 * @param {integer} gas
378 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
379 */
380 call (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
381 this.takeGas(40 + gas)
382
383 // Load the params from mem
384 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
385 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
386 const data = this.getMemory(dataOffset, dataLength).slice(0)
387
388 // Special case for calling into empty account
389 if (!this.environment.isAccountPresent(address)) {
390 this.takeGas(25000)
391 }
392
393 // Special case for non-zero value
394 if (!value.isZero()) {
395 this.takeGas(9000)
396 gas += 2300;
397 }
398
399 const [errorCode, result] = this.environment.call(gas, address, value, data)
400 this.setMemory(resultOffset, resultLength, result)
401 return errorCode
402 }
403
404 /**
405 * Message-call into this account with an alternative account’s code.
406 * @param {integer} addressOffset the offset to load the address path from
407 * @param {integer} valueOffset the offset to load the value from
408 * @param {integer} dataOffset the offset to load data from
409 * @param {integer} dataLength the length of data
410 * @param {integer} resultOffset the offset to store the result data at
411 * @param {integer} resultLength
412 * @param {integer} gas
413 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
414 */
415 callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
416 // FIXME: count properly
417 this.takeGas(40)
418
419 // Load the params from mem
420 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
421 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
422 const data = this.getMemory(dataOffset, dataLength).slice(0)
423 const [errorCode, result] = this.environment.callCode(gas, address, value, data)
424 this.setMemory(resultOffset, resultLength, result)
425 return errorCode
426 }
427
428 /**
429 * Message-call into this account with an alternative account’s code, but
430 * persisting the current values for sender and value.
431 * @param {integer} gas
432 * @param {integer} addressOffset the offset to load the address path from
433 * @param {integer} valueOffset the offset to load the value from
434 * @param {integer} dataOffset the offset to load data from
435 * @param {integer} dataLength the length of data
436 * @param {integer} resultOffset the offset to store the result data at
437 * @param {integer} resultLength
438 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
439 */
440 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
441 // FIXME: count properly
442 this.takeGas(40)
443
444 const data = this.getMemory(dataOffset, dataLength).slice(0)
445 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
446 const [errorCode, result] = this.environment.callDelegate(gas, address, data)
447 this.setMemory(resultOffset, resultLength, result)
448 return errorCode
449 }
450
451 /**
452 * store a value at a given path in long term storage which are both loaded
453 * from Memory
454 * @param {interger} pathOffest the memory offset to load the the path from
455 * @param {interger} valueOffset the memory offset to load the value from
456 */
457 storageStore (pathOffset, valueOffset) {
458 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
459 // copy the value
460 const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
461 const oldValue = this.environment.state.get(path)
462 const valIsZero = value.every((i) => i === 0)
463
464 this.takeGas(5000)
465
466 // write
467 if (!valIsZero && !oldValue) {
468 this.takeGas(15000)
469 }
470
471 // delete
472 if (valIsZero && oldValue) {
473 this.environment.gasRefund += 15000
474 this.environment.state.delete(path)
475 } else {
476 this.environment.state.set(path, value)
477 }
478 }
479
480 /**
481 * reterives a value at a given path in long term storage
482 * @param {interger} pathOffest the memory offset to load the the path from
483 * @param {interger} resultOffset the memory offset to load the value from
484 */
485 storageLoad (pathOffset, resultOffset) {
486 this.takeGas(50)
487
488 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
489 const result = this.environment.state.get(path)
490 this.setMemory(resultOffset, U256_SIZE_BYTES, result)
491 }
492
493 /**
494 * Halt execution returning output data.
495 * @param {integer} offset the offset of the output data.
496 * @param {integer} length the length of the output data.
497 */
498 return (offset, length) {
499 this.environment.returnValue = this.getMemory(offset, length).slice(0)
500 }
501
502 /**
503 * Halt execution and register account for later deletion giving the remaining
504 * balance to an address path
505 * @param {integer} offset the offset to load the address from
506 */
507 selfDestruct (addressOffset) {
508 this.environment.suicideAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
509 this.environment.gasRefund += 24000
510 }
511
512 getMemory (offset, length) {
513 return new Uint8Array(this.module.exports.memory, offset, length)
514 }
515
516 setMemory (offset, length, value) {
517 const memory = new Uint8Array(this.module.exports.memory, offset, length)
518 memory.set(value)
519 }
520
521 /*
522 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
523 * because every caller of this method is trusted.
524 */
525 takeGas (amount) {
526 if (this.environment.gasLeft < amount) {
527 throw new Error('Ran out of gas')
528 }
529 this.environment.gasLeft -= amount
530 }
531}
532
533//
534// Polyfill required unless this is sorted: https://bugs.chromium.org/p/chromium/issues/detail?id=633895
535//
536// Polyfill from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
537//
538Function.prototype.bind = function (oThis) { // eslint-disable-line
539 if (typeof this !== 'function') {
540 // closest thing possible to the ECMAScript 5
541 // internal IsCallable function
542 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
543 }
544
545 var aArgs = Array.prototype.slice.call(arguments, 1)
546 var fToBind = this
547 var fNOP = function () {}
548 var fBound = function () {
549 return fToBind.apply(this instanceof fNOP ? this : oThis,
550 aArgs.concat(Array.prototype.slice.call(arguments)))
551 }
552
553 if (this.prototype) {
554 // Function.prototype doesn't have a prototype property
555 fNOP.prototype = this.prototype
556 }
557
558 fBound.prototype = new fNOP() // eslint-disable-line new-cap
559
560 return fBound
561}
562

Built with git-ssb-web