git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 5bc36d61069f96790b11614e2244d309fb385b2e

Files: 5bc36d61069f96790b11614e2244d309fb385b2e / interface.js

17878 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')
7const fs = require('fs')
8const path = require('path')
9
10const U128_SIZE_BYTES = 16
11const ADDRESS_SIZE_BYTES = 20
12const U256_SIZE_BYTES = 32
13
14// The interface exposed to the WebAessembly Core
15module.exports = class Interface {
16 constructor (environment) {
17 this.environment = environment
18 const shimBin = fs.readFileSync(path.join(__dirname, '/wasm/interface.wasm'))
19 const shimMod = WebAssembly.Module(shimBin)
20 this.shims = WebAssembly.Instance(shimMod, {
21 'interface': {
22 'useGas': this._useGas.bind(this),
23 'getGasLeftHigh': this._getGasLeftHigh.bind(this),
24 'getGasLeftLow': this._getGasLeftLow.bind(this),
25 'call': this._call.bind(this)
26 }
27 })
28 }
29
30 get exportTable () {
31 let exportMethods = [
32 // include all the public methods according to the Ethereum Environment Interface (EEI) r1
33 'getAddress',
34 'getBalance',
35 'getTxOrigin',
36 'getCaller',
37 'getCallValue',
38 'getCallDataSize',
39 'callDataCopy',
40 'callDataCopy256',
41 'getCodeSize',
42 'codeCopy',
43 'getExternalCodeSize',
44 'externalCodeCopy',
45 'getTxGasPrice',
46 'getBlockHash',
47 'getBlockCoinbase',
48 'getBlockTimestamp',
49 'getBlockNumber',
50 'getBlockDifficulty',
51 'getBlockGasLimit',
52 'log',
53 'create',
54 'callCode',
55 'callDelegate',
56 'storageStore',
57 'storageLoad',
58 'return',
59 'selfDestruct'
60 ]
61 let ret = {}
62 exportMethods.forEach((method) => {
63 ret[method] = this[method].bind(this)
64 })
65 return ret
66 }
67
68 setModule (mod) {
69 this.module = mod
70 }
71
72 /**
73 * Subtracts an amount to the gas counter
74 * @param {integer} amount the amount to subtract to the gas counter
75 */
76 _useGas (high, low) {
77 this.takeGas(from64bit(high, low))
78 }
79
80 /**
81 * Returns the current amount of gas
82 * @return {integer}
83 */
84 _getGasLeftHigh () {
85 return Math.floor(this.environment.gasLeft / 4294967296)
86 }
87
88 /**
89 * Returns the current amount of gas
90 * @return {integer}
91 */
92 _getGasLeftLow () {
93 return this.environment.gasLeft
94 }
95
96 /**
97 * Gets address of currently executing account and loads it into memory at
98 * the given offset.
99 * @param {integer} offset
100 */
101 getAddress (offset) {
102 this.takeGas(2)
103
104 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.address.toMemory())
105 }
106
107 /**
108 * Gets balance of the given account and loads it into memory at the given
109 * offset.
110 * @param {integer} addressOffset the memory offset to laod the address
111 * @param {integer} resultOffset
112 */
113 getBalance (addressOffset, offset) {
114 this.takeGas(20)
115
116 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
117 // call the parent contract and ask for the balance of one of its child contracts
118 const balance = this.environment.getBalance(address)
119 this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES))
120 }
121
122 /**
123 * Gets the execution's origination address and loads it into memory at the
124 * given offset. This is the sender of original transaction; it is never an
125 * account with non-empty associated code.
126 * @param {integer} offset
127 */
128 getTxOrigin (offset) {
129 this.takeGas(2)
130
131 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.origin.toMemory())
132 }
133
134 /**
135 * Gets caller address and loads it into memory at the given offset. This is
136 * the address of the account that is directly responsible for this execution.
137 * @param {integer} offset
138 */
139 getCaller (offset) {
140 this.takeGas(2)
141
142 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.caller.toMemory())
143 }
144
145 /**
146 * Gets the deposited value by the instruction/transaction responsible for
147 * this execution and loads it into memory at the given location.
148 * @param {integer} offset
149 */
150 getCallValue (offset) {
151 this.takeGas(2)
152
153 this.setMemory(offset, U128_SIZE_BYTES, this.environment.callValue.toMemory(U128_SIZE_BYTES))
154 }
155
156 /**
157 * Get size of input data in current environment. This pertains to the input
158 * data passed with the message call instruction or transaction.
159 * @return {integer}
160 */
161 getCallDataSize () {
162 this.takeGas(2)
163
164 return this.environment.callData.length
165 }
166
167 /**
168 * Copys the input data in current environment to memory. This pertains to
169 * the input data passed with the message call instruction or transaction.
170 * @param {integer} offset the offset in memory to load into
171 * @param {integer} dataOffset the offset in the input data
172 * @param {integer} length the length of data to copy
173 */
174 callDataCopy (offset, dataOffset, length) {
175 this.takeGas(3 + Math.ceil(length / 32) * 3)
176
177 if (length) {
178 const callData = this.environment.callData.slice(dataOffset, dataOffset + length)
179 this.setMemory(offset, length, callData)
180 }
181 }
182
183 /**
184 * Copys the input data in current environment to memory. This pertains to
185 * the input data passed with the message call instruction or transaction.
186 * @param {integer} offset the offset in memory to load into
187 * @param {integer} dataOffset the offset in the input data
188 */
189 callDataCopy256 (offset, dataOffset) {
190 this.takeGas(3)
191 const callData = this.environment.callData.slice(dataOffset, dataOffset + 32)
192 this.setMemory(offset, U256_SIZE_BYTES, callData)
193 }
194
195 /**
196 * Gets the size of code running in current environment.
197 * @return {interger}
198 */
199 getCodeSize () {
200 this.takeGas(2)
201
202 return this.environment.code.length
203 }
204
205 /**
206 * Copys the code running in current environment to memory.
207 * @param {integer} offset the memory offset
208 * @param {integer} codeOffset the code offset
209 * @param {integer} length the length of code to copy
210 */
211 codeCopy (resultOffset, codeOffset, length) {
212 this.takeGas(3 + Math.ceil(length / 32) * 3)
213
214 if (length) {
215 const code = this.environment.code.slice(codeOffset, codeOffset + length)
216 this.setMemory(resultOffset, length, code)
217 }
218 }
219
220 /**
221 * Get size of an account’s code.
222 * @param {integer} addressOffset the offset in memory to load the address from
223 * @return {integer}
224 */
225 getExternalCodeSize (addressOffset) {
226 this.takeGas(20)
227
228 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
229 const code = this.environment.getCode(address)
230 return code.length
231 }
232
233 /**
234 * Copys the code of an account to memory.
235 * @param {integer} addressOffset the memory offset of the address
236 * @param {integer} resultOffset the memory offset
237 * @param {integer} codeOffset the code offset
238 * @param {integer} length the length of code to copy
239 */
240 externalCodeCopy (addressOffset, resultOffset, codeOffset, length) {
241 this.takeGas(20 + Math.ceil(length / 32) * 3)
242
243 if (length) {
244 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
245 let code = this.environment.getCode(address)
246 code = code.slice(codeOffset, codeOffset + length)
247 this.setMemory(resultOffset, length, code)
248 }
249 }
250
251 /**
252 * Gets price of gas in current environment.
253 * @return {integer}
254 */
255 getTxGasPrice () {
256 this.takeGas(2)
257
258 return this.environment.gasPrice
259 }
260
261 /**
262 * Gets the hash of one of the 256 most recent complete blocks.
263 * @param {integer} number which block to load
264 * @param {integer} offset the offset to load the hash into
265 */
266 getBlockHash (number, offset) {
267 this.takeGas(20)
268
269 const diff = this.environment.block.number - number
270 let hash
271
272 if (diff > 256 || diff <= 0) {
273 hash = new U256(0)
274 } else {
275 hash = new U256(this.environment.getBlockHash(number))
276 }
277 this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
278 }
279
280 /**
281 * Gets the block’s beneficiary address and loads into memory.
282 * @param offset
283 */
284 getBlockCoinbase (offset) {
285 this.takeGas(2)
286
287 this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.block.coinbase.toMemory())
288 }
289
290 /**
291 * Get the block’s timestamp.
292 * @return {integer}
293 */
294 getBlockTimestamp () {
295 this.takeGas(2)
296
297 return this.environment.block.timestamp
298 }
299
300 /**
301 * Get the block’s number.
302 * @return {integer}
303 */
304 getBlockNumber () {
305 this.takeGas(2)
306
307 return this.environment.block.number
308 }
309
310 /**
311 * Get the block’s difficulty.
312 * @return {integer}
313 */
314 getBlockDifficulty (offset) {
315 this.takeGas(2)
316
317 this.setMemory(offset, U256_SIZE_BYTES, this.environment.block.difficulty.toMemory())
318 }
319
320 /**
321 * Get the block’s gas limit.
322 * @return {integer}
323 */
324 getBlockGasLimit () {
325 this.takeGas(2)
326
327 return this.environment.block.gasLimit
328 }
329
330 /**
331 * Creates a new log in the current environment
332 * @param {integer} dataOffset the offset in memory to load the memory
333 * @param {integer} length the data length
334 * @param {integer} number of topics
335 */
336 log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
337 if (numberOfTopics < 0 || numberOfTopics > 4) {
338 throw new Error('Invalid numberOfTopics')
339 }
340
341 this.takeGas(375 + length * 8 + numberOfTopics * 375)
342
343 const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
344 const topics = []
345
346 if (numberOfTopics > 0) {
347 topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
348 }
349
350 if (numberOfTopics > 1) {
351 topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
352 }
353
354 if (numberOfTopics > 2) {
355 topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
356 }
357
358 if (numberOfTopics > 3) {
359 topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
360 }
361
362 this.environment.logs.push({
363 data: data,
364 topics: topics
365 })
366 }
367
368 /**
369 * Creates a new contract with a given value.
370 * @param {integer} valueOffset the offset in memory to the value from
371 * @param {integer} dataOffset the offset to load the code for the new contract from
372 * @param {integer} length the data length
373 * @param (integer} resultOffset the offset to write the new contract address to
374 * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
375 */
376 create (valueOffset, dataOffset, length, resultOffset) {
377 this.takeGas(32000)
378
379 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
380 if (length) {
381 const data = this.getMemory(dataOffset, length).slice(0)
382 // const [errorCode, address] = this.environment.create(value, data)
383 }
384 let address
385 if (value.gt(this.environment.value)) {
386 address = new Address()
387 } else {
388 address = new Address('0x945304eb96065b2a98b57a48a06ae28d285a71b5')
389 }
390 this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address.toMemory())
391 // return errorCode
392 }
393
394 /**
395 * Sends a message with arbiatary data to a given address path
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 _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
406 const gas = from64bit(gasHigh, gasLow)
407 this.takeGas(40 + gas)
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
414 // Special case for calling into empty account
415 if (!this.environment.isAccountPresent(address)) {
416 this.takeGas(25000)
417 }
418
419 // Special case for non-zero value
420 if (!value.isZero()) {
421 this.takeGas(9000)
422 }
423
424 const [errorCode, result] = this.environment.call(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.
431 * @param {integer} addressOffset the offset to load the address path from
432 * @param {integer} valueOffset the offset to load the value from
433 * @param {integer} dataOffset the offset to load data from
434 * @param {integer} dataLength the length of data
435 * @param {integer} resultOffset the offset to store the result data at
436 * @param {integer} resultLength
437 * @param {integer} gas
438 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
439 */
440 callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
441 // FIXME: count properly
442 this.takeGas(40)
443
444 // Load the params from mem
445 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
446 const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
447 const data = this.getMemory(dataOffset, dataLength).slice(0)
448 const [errorCode, result] = this.environment.callCode(gas, address, value, data)
449 this.setMemory(resultOffset, resultLength, result)
450 return errorCode
451 }
452
453 /**
454 * Message-call into this account with an alternative account’s code, but
455 * persisting the current values for sender and value.
456 * @param {integer} gas
457 * @param {integer} addressOffset the offset to load the address path from
458 * @param {integer} valueOffset the offset to load the value from
459 * @param {integer} dataOffset the offset to load data from
460 * @param {integer} dataLength the length of data
461 * @param {integer} resultOffset the offset to store the result data at
462 * @param {integer} resultLength
463 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
464 */
465 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
466 // FIXME: count properly
467 this.takeGas(40)
468
469 const data = this.getMemory(dataOffset, dataLength).slice(0)
470 const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
471 const [errorCode, result] = this.environment.callDelegate(gas, address, data)
472 this.setMemory(resultOffset, resultLength, result)
473 return errorCode
474 }
475
476 /**
477 * store a value at a given path in long term storage which are both loaded
478 * from Memory
479 * @param {interger} pathOffest the memory offset to load the the path from
480 * @param {interger} valueOffset the memory offset to load the value from
481 */
482 storageStore (pathOffset, valueOffset) {
483 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
484 // copy the value
485 const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
486 const oldValue = this.environment.state.get(path)
487 const valIsZero = value.every((i) => i === 0)
488
489 this.takeGas(5000)
490
491 // write
492 if (!valIsZero && !oldValue) {
493 this.takeGas(15000)
494 }
495
496 // delete
497 if (valIsZero && oldValue) {
498 this.environment.gasRefund += 15000
499 this.environment.state.delete(path)
500 } else {
501 this.environment.state.set(path, value)
502 }
503 }
504
505 /**
506 * reterives a value at a given path in long term storage
507 * @param {interger} pathOffest the memory offset to load the the path from
508 * @param {interger} resultOffset the memory offset to load the value from
509 */
510 storageLoad (pathOffset, resultOffset) {
511 this.takeGas(50)
512
513 const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
514 const result = this.environment.state.get(path) || new Uint8Array(32)
515 this.setMemory(resultOffset, U256_SIZE_BYTES, result)
516 }
517
518 /**
519 * Halt execution returning output data.
520 * @param {integer} offset the offset of the output data.
521 * @param {integer} length the length of the output data.
522 */
523 return (offset, length) {
524 if (length) {
525 this.environment.returnValue = this.getMemory(offset, length).slice(0)
526 }
527 }
528
529 /**
530 * Halt execution and register account for later deletion giving the remaining
531 * balance to an address path
532 * @param {integer} offset the offset to load the address from
533 */
534 selfDestruct (addressOffset) {
535 this.environment.selfDestruct = true
536 this.environment.selfDestructAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES))
537 this.environment.gasRefund += 24000
538 }
539
540 getMemory (offset, length) {
541 return new Uint8Array(this.module.exports.memory, offset, length)
542 }
543
544 setMemory (offset, length, value) {
545 const memory = new Uint8Array(this.module.exports.memory, offset, length)
546 memory.set(value)
547 }
548
549 /*
550 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
551 * because every caller of this method is trusted.
552 */
553 takeGas (amount) {
554 if (this.environment.gasLeft < amount) {
555 throw new Error('Ran out of gas')
556 }
557 this.environment.gasLeft -= amount
558 }
559}
560
561// converts a 64 bit number to a JS number
562function from64bit (high, low) {
563 if (high < 0) {
564 // convert from a 32-bit two's compliment
565 high = 0x100000000 - high
566 }
567 if (low < 0) {
568 // convert from a 32-bit two's compliment
569 low = 0x100000000 - low
570 }
571 // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
572 return (high * 4294967296) + low
573}
574

Built with git-ssb-web