git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 448365b0d86410e9ec040c1956c8ad27e5b482d5

Files: 448365b0d86410e9ec040c1956c8ad27e5b482d5 / interface.js

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

Built with git-ssb-web