git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 75c73cf778a32316679e669b47a9b5ce085b8587

Files: 75c73cf778a32316679e669b47a9b5ce085b8587 / interface.js

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

Built with git-ssb-web