git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: a3b9c88c4884b2b2f42a88e75023168e5f85a08a

Files: a3b9c88c4884b2b2f42a88e75023168e5f85a08a / interface.js

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

Built with git-ssb-web