git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 3c7fe6b5acfd0a626b54b4a24a1d167e67c8f8e4

Files: 3c7fe6b5acfd0a626b54b4a24a1d167e67c8f8e4 / interface.js

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

Built with git-ssb-web