git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 935cf8fa5bf1fa8accb03404084eb4c6f1a187aa

Files: 935cf8fa5bf1fa8accb03404084eb4c6f1a187aa / interface.js

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

Built with git-ssb-web