git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 865f135bbd80624eceea4b85923acc13137ffd39

Files: 865f135bbd80624eceea4b85923acc13137ffd39 / interface.js

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

Built with git-ssb-web