git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 410bf961ea8b42dea73d52435bec504893649c03

Files: 410bf961ea8b42dea73d52435bec504893649c03 / interface.js

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

Built with git-ssb-web