git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 1cb861903ccb26cbd39eaad7ab6060463380986e

Files: 1cb861903ccb26cbd39eaad7ab6060463380986e / interface.js

13241 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.toBuffer())
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.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 = Buffer.from(this.environment.callData.slice(dataOffset, dataOffset + length)).reverse()
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.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 (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.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} 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 return errorCode
307 }
308
309 /**
310 * Message-call into this account with an alternative account’s code, but
311 * persisting the current values for sender and value.
312 * @param {integer} gas
313 * @param {integer} addressOffset the offset to load the address path from
314 * @param {integer} valueOffset the offset to load the value from
315 * @param {integer} dataOffset the offset to load data from
316 * @param {integer} dataLength the length of data
317 * @param {integer} resultOffset the offset to store the result data at
318 * @param {integer} resultLength
319 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
320 */
321 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
322 const data = this.getMemory(dataOffset, dataLength)
323 const address = this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES)
324 const [result, errorCode] = this.environment.callDelegate(gas, address, data)
325 this.setMemory(resultOffset, resultLength, result)
326 return errorCode
327 }
328
329 /**
330 * store a value at a given path in long term storage which are both loaded
331 * from Memory
332 * @param {interger} pathOffest the memory offset to load the the path from
333 * @param {interger} valueOffset the memory offset to load the value from
334 */
335 sstore (pathOffset, valueOffset) {
336 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
337 // copy the value
338 const value = this.getMemory(valueOffset, 32).slice(0)
339 const oldValue = this.environment.state.get(path)
340 const valIsZero = value.every((i) => i === 0)
341
342 // write
343 if (!valIsZero && !oldValue) {
344 this.environment.gasLimit -= 15000
345 }
346
347 // delete
348 if (valIsZero && oldValue) {
349 this.environment.gasRefund += 15000
350 this.environment.state.delete(path)
351 } else {
352 this.environment.state.set(path, value)
353 }
354 }
355
356 /**
357 * reterives a value at a given path in long term storage
358 * @param {interger} pathOffest the memory offset to load the the path from
359 * @param {interger} resultOffset the memory offset to load the value from
360 */
361 sload (pathOffset, resultOffset) {
362 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
363 const result = this.environment.state.get(path)
364 this.setMemory(resultOffset, 32, result)
365 }
366
367 /**
368 * Halt execution returning output data.
369 * @param {integer} offset the offset of the output data.
370 * @param {integer} length the length of the output data.
371 */
372 return (offset, length) {
373 this.environment.returnValue = this.getMemory(offset, length)
374 }
375
376 /**
377 * Halt execution and register account for later deletion giving the remaining
378 * balance to an address path
379 * @param {integer} offset the offset to load the address from
380 */
381 suicide (addressOffset) {
382 this.environment.suicideAddress = this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES)
383 }
384
385 getMemory (offset, length) {
386 return new Uint8Array(this.module.exports.memory, offset, length)
387 }
388
389 setMemory (offset, length, value) {
390 const memory = new Uint8Array(this.module.exports.memory, offset, length)
391 memory.set(value)
392 }
393}
394
395//
396// Polyfill required unless this is sorted: https://bugs.chromium.org/p/chromium/issues/detail?id=633895
397//
398// Polyfill from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
399//
400Function.prototype.bind = function (oThis) { // eslint-disable-line
401 if (typeof this !== 'function') {
402 // closest thing possible to the ECMAScript 5
403 // internal IsCallable function
404 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
405 }
406
407 var aArgs = Array.prototype.slice.call(arguments, 1)
408 var fToBind = this
409 var fNOP = function () {}
410 var fBound = function () {
411 return fToBind.apply(this instanceof fNOP ? this : oThis,
412 aArgs.concat(Array.prototype.slice.call(arguments)))
413 }
414
415 if (this.prototype) {
416 // Function.prototype doesn't have a prototype property
417 fNOP.prototype = this.prototype
418 }
419
420 fBound.prototype = new fNOP() // eslint-disable-line new-cap
421
422 return fBound
423}
424

Built with git-ssb-web