git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 52733a7622044cda7ec1de6a666ed8c8747162c6

Files: 52733a7622044cda7ec1de6a666ed8c8747162c6 / interface.js

15975 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 // FIXME: should the return value reflect this or not?
77 this.takeGas(2)
78
79 return this.environment.gasLimit
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)
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.parent.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)
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)
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 * Gets the size of code running in current environment.
169 * @return {interger}
170 */
171 getCodeSize () {
172 this.takeGas(2)
173
174 return this.environment.code.length
175 }
176
177 /**
178 * Copys the code running in current environment to memory.
179 * @param {integer} offset the memory offset
180 * @param {integer} codeOffset the code offset
181 * @param {integer} length the length of code to copy
182 */
183 codeCopy (resultOffset, codeOffset, length) {
184 this.takeGas(3 + ((length / 32) * 3))
185
186 const code = new Uint8Array(this.environment.code, codeOffset, length)
187 this.setMemory(resultOffset, length, code)
188 }
189
190 /**
191 * Get size of an account’s code.
192 * @param {integer} addressOffset the offset in memory to load the address from
193 * @return {integer}
194 */
195 getExternalCodeSize (addressOffset) {
196 this.takeGas(20)
197
198 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
199 const code = this.environment.getCode(address)
200 return code.length
201 }
202
203 /**
204 * Copys the code of an account to memory.
205 * @param {integer} addressOffset the memory offset of the address
206 * @param {integer} resultOffset the memory offset
207 * @param {integer} codeOffset the code offset
208 * @param {integer} length the length of code to copy
209 */
210 externalCodeCopy (addressOffset, resultOffset, codeOffset, length) {
211 this.takeGas(20 + ((length / 32) * 3))
212
213 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
214 let code = this.environment.getCode(address)
215 code = new Uint8Array(code, codeOffset, length)
216 this.setMemory(resultOffset, length, code)
217 }
218
219 /**
220 * Gets price of gas in current environment.
221 * @return {integer}
222 */
223 getTxGasPrice () {
224 this.takeGas(2)
225
226 return this.environment.gasPrice
227 }
228
229 /**
230 * Gets the hash of one of the 256 most recent complete blocks.
231 * @param {integer} number which block to load
232 * @param {integer} offset the offset to load the hash into
233 */
234 getBlockHash (number, offset) {
235 this.takeGas(20)
236
237 const diff = this.environment.number - number
238 let hash
239
240 if (diff > 256 || diff <= 0) {
241 hash = new U256(0)
242 } else {
243 hash = new U256(this.environment.getBlockHash(number))
244 }
245 this.setMemory(offset, 32, hash.toBuffer())
246 }
247
248 /**
249 * Gets the block’s beneficiary address and loads into memory.
250 * @param offset
251 */
252 getBlockCoinbase (offset) {
253 this.takeGas(2)
254
255 this.setMemory(offset, constants.ADDRESS_SIZE_BYTES, this.environment.coinbase)
256 }
257
258 /**
259 * Get the block’s timestamp.
260 * @return {integer}
261 */
262 getBlockTimestamp () {
263 this.takeGas(2)
264
265 return this.environment.timestamp
266 }
267
268 /**
269 * Get the block’s number.
270 * @return {integer}
271 */
272 getBlockNumber () {
273 this.takeGas(2)
274
275 return this.environment.number
276 }
277
278 /**
279 * Get the block’s difficulty.
280 * @return {integer}
281 */
282 getBlockDifficulty () {
283 this.takeGas(2)
284
285 return this.environment.difficulty
286 }
287
288 /**
289 * Get the block’s gas limit.
290 * @return {integer}
291 */
292 getBlockGasLimit () {
293 this.takeGas(2)
294
295 return this.environment.gasLimit
296 }
297
298 /**
299 * Creates a new log in the current environment
300 * @param {integer} dataOffset the offset in memory to load the memory
301 * @param {integer} length the data length
302 * TODO: replace with variadic
303 */
304 log (dataOffset, length, topic1, topic2, topic3, topic4, topic5) {
305 // FIXME: calculate gas for topics set
306 this.takeGas(375 + length * 8)
307
308 const data = this.getMemory(dataOffset, length)
309 this.environment.logs.push({
310 data: data,
311 topics: [topic1, topic2, topic3, topic4, topic5]
312 })
313 }
314
315 /**
316 * Creates a new contract with a given value.
317 * @param {integer} valueOffset the offset in memory to the value from
318 * @param {integer} dataOffset the offset to load the code for the new contract from
319 * @param {integer} length the data length
320 */
321 create (valueOffset, dataOffset, length) {
322 this.takeGas(32000)
323
324 const value = new U256(this.getMemory(valueOffset, constants.BALANCE_SIZE_BYTES))
325 const data = this.getMemory(dataOffset, length)
326 const result = this.environment.create(value, data)
327 return result
328 }
329
330 /**
331 * Sends a message with arbiatary data to a given address path
332 * @param {integer} addressOffset the offset to load the address path from
333 * @param {integer} valueOffset the offset to load the value from
334 * @param {integer} dataOffset the offset to load data from
335 * @param {integer} dataLength the length of data
336 * @param {integer} resultOffset the offset to store the result data at
337 * @param {integer} resultLength
338 * @param {integer} gas
339 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
340 */
341 call (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
342 // FIXME: count properly
343 this.takeGas(40)
344
345 if (gas === undefined) {
346 gas = this.gasLeft()
347 }
348 // Load the params from mem
349 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
350 const value = new U256(this.getMemory(valueOffset, constants.BALANCE_SIZE_BYTES))
351 const data = this.getMemory(dataOffset, dataLength)
352 // Run the call
353 const [result, errorCode] = this.environment.call(gas, address, value, data)
354 this.setMemory(resultOffset, resultLength, result)
355 return errorCode
356 }
357
358 /**
359 * Message-call into this account with an alternative account’s code.
360 * @param {integer} addressOffset the offset to load the address path from
361 * @param {integer} valueOffset the offset to load the value from
362 * @param {integer} dataOffset the offset to load data from
363 * @param {integer} dataLength the length of data
364 * @param {integer} resultOffset the offset to store the result data at
365 * @param {integer} resultLength
366 * @param {integer} gas
367 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
368 */
369 callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
370 // FIXME: count properly
371 this.takeGas(40)
372
373 // Load the params from mem
374 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
375 const value = new U256(this.getMemory(valueOffset, constants.BALANCE_SIZE_BYTES))
376 const data = this.getMemory(dataOffset, dataLength)
377 // Run the call
378 const [result, errorCode] = this.environment.callCode(gas, address, value, data)
379 this.setMemory(resultOffset, resultLength, result)
380 return errorCode
381 }
382
383 /**
384 * Message-call into this account with an alternative account’s code, but
385 * persisting the current values for sender and value.
386 * @param {integer} gas
387 * @param {integer} addressOffset the offset to load the address path from
388 * @param {integer} valueOffset the offset to load the value from
389 * @param {integer} dataOffset the offset to load data from
390 * @param {integer} dataLength the length of data
391 * @param {integer} resultOffset the offset to store the result data at
392 * @param {integer} resultLength
393 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
394 */
395 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
396 // FIXME: count properly
397 this.takeGas(40)
398
399 const data = this.getMemory(dataOffset, dataLength)
400 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
401 const [result, errorCode] = this.environment.callDelegate(gas, address, data)
402 this.setMemory(resultOffset, resultLength, result)
403 return errorCode
404 }
405
406 /**
407 * store a value at a given path in long term storage which are both loaded
408 * from Memory
409 * @param {interger} pathOffest the memory offset to load the the path from
410 * @param {interger} valueOffset the memory offset to load the value from
411 */
412 storageStore (pathOffset, valueOffset) {
413 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
414 // copy the value
415 const value = this.getMemory(valueOffset, 32).slice(0)
416 const oldValue = this.environment.state.get(path)
417 const valIsZero = value.every((i) => i === 0)
418
419 // FIXME: gas counting has more cases then the below
420
421 // write
422 if (!valIsZero && !oldValue) {
423 this.takeGas(15000)
424 }
425
426 // delete
427 if (valIsZero && oldValue) {
428 this.environment.gasRefund += 15000
429 this.environment.state.delete(path)
430 } else {
431 this.environment.state.set(path, value)
432 }
433 }
434
435 /**
436 * reterives a value at a given path in long term storage
437 * @param {interger} pathOffest the memory offset to load the the path from
438 * @param {interger} resultOffset the memory offset to load the value from
439 */
440 storageLoad (pathOffset, resultOffset) {
441 this.takeGas(50)
442
443 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
444 const result = this.environment.state.get(path)
445 this.setMemory(resultOffset, 32, result)
446 }
447
448 /**
449 * Halt execution returning output data.
450 * @param {integer} offset the offset of the output data.
451 * @param {integer} length the length of the output data.
452 */
453 return (offset, length) {
454 this.environment.returnValue = this.getMemory(offset, length)
455 }
456
457 /**
458 * Halt execution and register account for later deletion giving the remaining
459 * balance to an address path
460 * @param {integer} offset the offset to load the address from
461 */
462 selfDestruct (addressOffset) {
463 this.environment.suicideAddress = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
464 this.environment.gasRefund += 24000
465 }
466
467 getMemory (offset, length) {
468 return new Uint8Array(this.module.exports.memory, offset, length)
469 }
470
471 setMemory (offset, length, value) {
472 const memory = new Uint8Array(this.module.exports.memory, offset, length)
473 memory.set(value)
474 }
475
476 /*
477 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
478 * because every caller of this method is trusted.
479 */
480 takeGas (amount) {
481 if (this.environment.gasLimit < amount) {
482 throw new Error('Ran out of gas')
483 }
484 this.environment.gasLimit -= amount
485 }
486}
487
488//
489// Polyfill required unless this is sorted: https://bugs.chromium.org/p/chromium/issues/detail?id=633895
490//
491// Polyfill from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
492//
493Function.prototype.bind = function (oThis) { // eslint-disable-line
494 if (typeof this !== 'function') {
495 // closest thing possible to the ECMAScript 5
496 // internal IsCallable function
497 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
498 }
499
500 var aArgs = Array.prototype.slice.call(arguments, 1)
501 var fToBind = this
502 var fNOP = function () {}
503 var fBound = function () {
504 return fToBind.apply(this instanceof fNOP ? this : oThis,
505 aArgs.concat(Array.prototype.slice.call(arguments)))
506 }
507
508 if (this.prototype) {
509 // Function.prototype doesn't have a prototype property
510 fNOP.prototype = this.prototype
511 }
512
513 fBound.prototype = new fNOP() // eslint-disable-line new-cap
514
515 return fBound
516}
517

Built with git-ssb-web