git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: f148072e804e4e10c0a513d11513ed7e67030f0b

Files: f148072e804e4e10c0a513d11513ed7e67030f0b / interface.js

13215 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 // FIXME: this shouldn't be needed
54 get env () {
55 return this.environment
56 }
57
58 setModule (mod) {
59 this.module = mod
60 }
61
62 /**
63 * Subtracts an amount to the gas counter
64 * @param {integer} amount the amount to subtract to the gas counter
65 */
66 useGas (amount) {
67 if (amount > 0) {
68 if (this.environment.gasLimit < amount) {
69 throw new Error('Ran out of gas')
70 }
71 this.environment.gasLimit -= amount
72 }
73 }
74
75 /**
76 * Returns the current amount of gas
77 * @return {integer}
78 */
79 gas () {
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 address (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 balance (addressOffset, offset) {
99 const 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)
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 origin (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 caller (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 callValue (offset) {
130 this.setMemory(offset, constants.BALANCE_SIZE_BYTES, this.environment.callValue)
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 callDataSize () {
139 return this.environment.callData.byteLength
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 = new Uint8Array(this.environment.callData, offset, 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 codeSize () {
159 return this.environment.code.byteLength
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 (offset, codeOffset, length) {
169 const code = new Uint8Array(this.environment.code, codeOffset, length)
170 this.setMemory(offset, 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 extCodeSize (addressOffset) {
179 const address = this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES)
180 const code = this.environment.getCode(address)
181 return code.byteLength
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} offset the memory offset
188 * @param {integer} codeOffset the code offset
189 * @param {integer} length the length of code to copy
190 */
191 extCodeCopy (addressOffset, offset, codeOffset, length) {
192 const 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(offset, length, code)
196 }
197
198 /**
199 * Gets price of gas in current environment.
200 * @return {integer}
201 */
202 gasPrice () {
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 blockHash (number, offset) {
212 const hash = this.environment.getBlockHash(number)
213 this.setMemory(offset, 32, hash)
214 }
215
216 /**
217 * Gets the block’s beneficiary address and loads into memory.
218 * @param offset
219 */
220 coinbase (offset) {
221 this.setMemory(offset, constants.ADDRESS_SIZE_BYTES, this.environment.coinbase)
222 }
223
224 /**
225 * Get the block’s timestamp.
226 * @return {integer}
227 */
228 timestamp () {
229 return this.environment.timestamp
230 }
231
232 /**
233 * Get the block’s number.
234 * @return {integer}
235 */
236 number () {
237 return this.environment.number
238 }
239
240 /**
241 * Get the block’s difficulty.
242 * @return {integer}
243 */
244 difficulty () {
245 return this.environment.difficulty
246 }
247
248 /**
249 * Get the block’s gas limit.
250 * @return {integer}
251 */
252 gasLimit () {
253 return this.environment.gasLimit
254 }
255
256 /**
257 * Creates a new log in the current environment
258 * @param {integer} dataOffset the offset in memory to load the memory
259 * @param {integer} length the data length
260 * TODO: replace with variadic
261 */
262 log (dataOffset, length, topic1, topic2, topic3, topic4, topic5) {
263 const data = this.getMemory(dataOffset, length)
264 this.environment.logs.push({
265 data: data,
266 topics: [topic1, topic2, topic3, topic4, topic5]
267 })
268 }
269
270 /**
271 * Creates a new contract with a given value.
272 * @param {integer} valueOffset the offset in memory to the value from
273 * @param {integer} dataOffset the offset to load the code for the new contract from
274 * @param {integer} length the data length
275 */
276 create (valueOffset, dataOffset, length) {
277 const value = this.getMemory(valueOffset, constants.BALANCE_SIZE_BYTES)
278 const data = this.getMemory(dataOffset, length)
279 const result = this.environment.create(value, data)
280 return result
281 }
282
283 /**
284 * Sends a message with arbiatary data to a given address path
285 * @param {integer} addressOffset the offset to load the address path from
286 * @param {integer} valueOffset the offset to load the value from
287 * @param {integer} dataOffset the offset to load data from
288 * @param {integer} dataLength the length of data
289 * @param {integer} resultOffset the offset to store the result data at
290 * @param {integer} resultLength
291 * @param {integer} gas
292 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
293 * TODO: add proper gas counting
294 */
295 call (addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, gas) {
296 if (gas === undefined) {
297 gas = this.gasLeft()
298 }
299 // Load the params from mem
300 const address = this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES)
301 const value = this.getMemory(valueOffset, constants.BALANCE_SIZE_BYTES)
302 const data = this.getMemory(dataOffset, dataLength)
303 // Run the call
304 const [result, errorCode] = this.environment.call(gas, address, value, data)
305 this.setMemory(resultOffset, resultLength, result)
306
307 return errorCode
308 }
309
310 /**
311 * Message-call into this account with an alternative account’s code, but
312 * persisting the current values for sender and value.
313 * @param {integer} gas
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 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
321 */
322 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
323 const data = this.getMemory(dataOffset, dataLength)
324 const address = this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES)
325 const [result, errorCode] = this.environment.callDelegate(gas, address, data)
326 this.setMemory(resultOffset, resultLength, result)
327
328 return errorCode
329 }
330
331 /**
332 * store a value at a given path in long term storage which are both loaded
333 * from Memory
334 * @param {interger} pathOffest the memory offset to load the the path from
335 * @param {interger} valueOffset the memory offset to load the value from
336 */
337 sstore (pathOffset, valueOffset) {
338 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
339 // copy the value
340 const value = this.getMemory(valueOffset, 32).slice(0)
341 const oldValue = this.environment.state.get(path)
342 const valIsZero = value.every((i) => i === 0)
343
344 // write
345 if (!valIsZero && !oldValue) {
346 this.environment.gasLimit -= 15000
347 }
348
349 // delete
350 if (valIsZero && oldValue) {
351 this.environment.gasRefund += 15000
352 this.environment.state.delete(path)
353 } else {
354 this.environment.state.set(path, value)
355 }
356 }
357
358 /**
359 * reterives a value at a given path in long term storage
360 * @param {interger} pathOffest the memory offset to load the the path from
361 * @param {interger} resultOffset the memory offset to load the value from
362 */
363 sload (pathOffset, resultOffset) {
364 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
365 const result = this.environment.state.get(path)
366 this.setMemory(resultOffset, 32, result)
367 }
368
369 /**
370 * Halt execution returning output data.
371 * @param {integer} offset the offset of the output data.
372 * @param {integer} length the length of the output data.
373 */
374 return (offset, length) {
375 this.environment.returnValue = this.getMemory(offset, length)
376 }
377
378 /**
379 * Halt execution and register account for later deletion giving the remaining
380 * balance to an address path
381 * @param {integer} offset the offset to load the address from
382 */
383 suicide (addressOffset) {
384 this.environment.suicideAddress = this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES)
385 }
386
387 getMemory (offset, length) {
388 return new Uint8Array(this.module.exports.memory, offset, length)
389 }
390
391 setMemory (offset, length, value) {
392 const memory = new Uint8Array(this.module.exports.memory, offset, length)
393 memory.set(value)
394 }
395}
396
397//
398// Polyfill required unless this is sorted: https://bugs.chromium.org/p/chromium/issues/detail?id=633895
399//
400// Polyfill from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
401//
402Function.prototype.bind = function (oThis) { // eslint-disable-line
403 if (typeof this !== 'function') {
404 // closest thing possible to the ECMAScript 5
405 // internal IsCallable function
406 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
407 }
408
409 var aArgs = Array.prototype.slice.call(arguments, 1)
410 var fToBind = this
411 var fNOP = function () {}
412 var fBound = function () {
413 return fToBind.apply(this instanceof fNOP ? this : oThis,
414 aArgs.concat(Array.prototype.slice.call(arguments)))
415 }
416
417 if (this.prototype) {
418 // Function.prototype doesn't have a prototype property
419 fNOP.prototype = this.prototype
420 }
421
422 fBound.prototype = new fNOP() // eslint-disable-line new-cap
423
424 return fBound
425}
426

Built with git-ssb-web