git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: da7b20096a9e2da5c16128a768e529a9070114a8

Files: da7b20096a9e2da5c16128a768e529a9070114a8 / interface.js

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

Built with git-ssb-web