git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 1c2e46867f5b53672c2ec9beb976209e90ebc8db

Files: 1c2e46867f5b53672c2ec9beb976209e90ebc8db / interface.js

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

Built with git-ssb-web