git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 87507eebc829483ad8dbe3fde2dc21268871785b

Files: 87507eebc829483ad8dbe3fde2dc21268871785b / interface.js

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

Built with git-ssb-web