git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 4b9fedd40734548f93c4ee00a7a7bd604f0fb029

Files: 4b9fedd40734548f93c4ee00a7a7bd604f0fb029 / interface.js

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

Built with git-ssb-web