git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 53d0a8449b654d395b0965a2e0d05bb3cd6782fa

Files: 53d0a8449b654d395b0965a2e0d05bb3cd6782fa / interface.js

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

Built with git-ssb-web