git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 0031bf1b8a9976595e38cc4e2b49124a79ff927d

Files: 0031bf1b8a9976595e38cc4e2b49124a79ff927d / interface.js

16433 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.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 // Load the params from mem
359 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
360 const value = new U256(this.getMemory(valueOffset, constants.BALANCE_SIZE_BYTES))
361 const data = this.getMemory(dataOffset, dataLength)
362 // Run the call
363 const [result, errorCode] = this.environment.call(gas, address, value, data)
364 this.setMemory(resultOffset, resultLength, result)
365 return errorCode
366 }
367
368 /**
369 * Message-call into this account with an alternative account’s code.
370 * @param {integer} addressOffset the offset to load the address path from
371 * @param {integer} valueOffset the offset to load the value from
372 * @param {integer} dataOffset the offset to load data from
373 * @param {integer} dataLength the length of data
374 * @param {integer} resultOffset the offset to store the result data at
375 * @param {integer} resultLength
376 * @param {integer} gas
377 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
378 */
379 callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) {
380 // FIXME: count properly
381 this.takeGas(40)
382
383 // Load the params from mem
384 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
385 const value = new U256(this.getMemory(valueOffset, constants.BALANCE_SIZE_BYTES))
386 const data = this.getMemory(dataOffset, dataLength)
387 // Run the call
388 const [result, errorCode] = this.environment.callCode(gas, address, value, data)
389 this.setMemory(resultOffset, resultLength, result)
390 return errorCode
391 }
392
393 /**
394 * Message-call into this account with an alternative account’s code, but
395 * persisting the current values for sender and value.
396 * @param {integer} gas
397 * @param {integer} addressOffset the offset to load the address path from
398 * @param {integer} valueOffset the offset to load the value from
399 * @param {integer} dataOffset the offset to load data from
400 * @param {integer} dataLength the length of data
401 * @param {integer} resultOffset the offset to store the result data at
402 * @param {integer} resultLength
403 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
404 */
405 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
406 // FIXME: count properly
407 this.takeGas(40)
408
409 const data = this.getMemory(dataOffset, dataLength)
410 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
411 const [result, errorCode] = this.environment.callDelegate(gas, address, data)
412 this.setMemory(resultOffset, resultLength, result)
413 return errorCode
414 }
415
416 /**
417 * store a value at a given path in long term storage which are both loaded
418 * from Memory
419 * @param {interger} pathOffest the memory offset to load the the path from
420 * @param {interger} valueOffset the memory offset to load the value from
421 */
422 storageStore (pathOffset, valueOffset) {
423 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
424 // copy the value
425 const value = this.getMemory(valueOffset, 32).slice(0)
426 const oldValue = this.environment.state.get(path)
427 const valIsZero = value.every((i) => i === 0)
428
429 this.takeGas(5000)
430
431 // write
432 if (!valIsZero && !oldValue) {
433 this.takeGas(15000)
434 }
435
436 // delete
437 if (valIsZero && oldValue) {
438 this.environment.gasRefund += 15000
439 this.environment.state.delete(path)
440 } else {
441 this.environment.state.set(path, value)
442 }
443 }
444
445 /**
446 * reterives a value at a given path in long term storage
447 * @param {interger} pathOffest the memory offset to load the the path from
448 * @param {interger} resultOffset the memory offset to load the value from
449 */
450 storageLoad (pathOffset, resultOffset) {
451 this.takeGas(50)
452
453 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
454 const result = this.environment.state.get(path)
455 this.setMemory(resultOffset, 32, result)
456 }
457
458 /**
459 * Halt execution returning output data.
460 * @param {integer} offset the offset of the output data.
461 * @param {integer} length the length of the output data.
462 */
463 return (offset, length) {
464 this.environment.returnValue = this.getMemory(offset, length)
465 }
466
467 /**
468 * Halt execution and register account for later deletion giving the remaining
469 * balance to an address path
470 * @param {integer} offset the offset to load the address from
471 */
472 selfDestruct (addressOffset) {
473 this.environment.suicideAddress = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
474 this.environment.gasRefund += 24000
475 }
476
477 getMemory (offset, length) {
478 return new Uint8Array(this.module.exports.memory, offset, length)
479 }
480
481 setMemory (offset, length, value) {
482 const memory = new Uint8Array(this.module.exports.memory, offset, length)
483 memory.set(value)
484 }
485
486 /*
487 * Takes gas from the tank. Only needs to check if there's gas left to be taken,
488 * because every caller of this method is trusted.
489 */
490 takeGas (amount) {
491 if (this.environment.gasLeft < amount) {
492 throw new Error('Ran out of gas')
493 }
494 this.environment.gasLeft -= amount
495 }
496}
497
498//
499// Polyfill required unless this is sorted: https://bugs.chromium.org/p/chromium/issues/detail?id=633895
500//
501// Polyfill from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
502//
503Function.prototype.bind = function (oThis) { // eslint-disable-line
504 if (typeof this !== 'function') {
505 // closest thing possible to the ECMAScript 5
506 // internal IsCallable function
507 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
508 }
509
510 var aArgs = Array.prototype.slice.call(arguments, 1)
511 var fToBind = this
512 var fNOP = function () {}
513 var fBound = function () {
514 return fToBind.apply(this instanceof fNOP ? this : oThis,
515 aArgs.concat(Array.prototype.slice.call(arguments)))
516 }
517
518 if (this.prototype) {
519 // Function.prototype doesn't have a prototype property
520 fNOP.prototype = this.prototype
521 }
522
523 fBound.prototype = new fNOP() // eslint-disable-line new-cap
524
525 return fBound
526}
527

Built with git-ssb-web