git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: d39de3e772adfb891056fd28b873a362f305e36d

Files: d39de3e772adfb891056fd28b873a362f305e36d / interface.js

13596 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)
18 // FIXME: this currently doesn't match EEI r0
19 'useGas',
20 'gas',
21 'address',
22 'balance',
23 'origin',
24 'caller',
25 'callValue',
26 'callDataSize',
27 'callDataCopy',
28 'codeSize',
29 'codeCopy',
30 'extCodeSize',
31 'extCodeCopy',
32 'gasPrice',
33 'blockHash',
34 'coinbase',
35 'timestamp',
36 'number',
37 'difficulty',
38 'gasLimit',
39 'log',
40 'create',
41 'call',
42 'callDelegate',
43 'sstore',
44 'sload',
45 'return',
46 'suicide'
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 gas () {
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 address (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 balance (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 origin (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 caller (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 callValue (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 callDataSize () {
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 = Buffer.from(this.environment.callData.slice(dataOffset, dataOffset + length)).reverse()
148 this.setMemory(offset, length, callData)
149 }
150
151 /**
152 * Gets the size of code running in current environment.
153 * @return {interger}
154 */
155 codeSize () {
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 (offset, codeOffset, length) {
166 const code = new Uint8Array(this.environment.code, codeOffset, length)
167 this.setMemory(offset, 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 extCodeSize (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} offset the memory offset
185 * @param {integer} codeOffset the code offset
186 * @param {integer} length the length of code to copy
187 */
188 extCodeCopy (addressOffset, offset, 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(offset, length, code)
193 }
194
195 /**
196 * Gets price of gas in current environment.
197 * @return {integer}
198 */
199 gasPrice () {
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 blockHash (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 coinbase (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 timestamp () {
233 return this.environment.timestamp
234 }
235
236 /**
237 * Get the block’s number.
238 * @return {integer}
239 */
240 number () {
241 return this.environment.number
242 }
243
244 /**
245 * Get the block’s difficulty.
246 * @return {integer}
247 */
248 difficulty () {
249 return this.environment.difficulty
250 }
251
252 /**
253 * Get the block’s gas limit.
254 * @return {integer}
255 */
256 gasLimit () {
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 (addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, gas) {
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, but
315 * persisting the current values for sender and value.
316 * @param {integer} gas
317 * @param {integer} addressOffset the offset to load the address path from
318 * @param {integer} valueOffset the offset to load the value from
319 * @param {integer} dataOffset the offset to load data from
320 * @param {integer} dataLength the length of data
321 * @param {integer} resultOffset the offset to store the result data at
322 * @param {integer} resultLength
323 * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
324 */
325 callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
326 const data = this.getMemory(dataOffset, dataLength)
327 const address = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
328 const [result, errorCode] = this.environment.callDelegate(gas, address, data)
329 this.setMemory(resultOffset, resultLength, result)
330 return errorCode
331 }
332
333 /**
334 * store a value at a given path in long term storage which are both loaded
335 * from Memory
336 * @param {interger} pathOffest the memory offset to load the the path from
337 * @param {interger} valueOffset the memory offset to load the value from
338 */
339 sstore (pathOffset, valueOffset) {
340 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
341 // copy the value
342 const value = this.getMemory(valueOffset, 32).slice(0)
343 const oldValue = this.environment.state.get(path)
344 const valIsZero = value.every((i) => i === 0)
345
346 // write
347 if (!valIsZero && !oldValue) {
348 this.environment.gasLimit -= 15000
349 }
350
351 // delete
352 if (valIsZero && oldValue) {
353 this.environment.gasRefund += 15000
354 this.environment.state.delete(path)
355 } else {
356 this.environment.state.set(path, value)
357 }
358 }
359
360 /**
361 * reterives a value at a given path in long term storage
362 * @param {interger} pathOffest the memory offset to load the the path from
363 * @param {interger} resultOffset the memory offset to load the value from
364 */
365 sload (pathOffset, resultOffset) {
366 const path = new Buffer(this.getMemory(pathOffset, 32)).toString('hex')
367 const result = this.environment.state.get(path)
368 this.setMemory(resultOffset, 32, result)
369 }
370
371 /**
372 * Halt execution returning output data.
373 * @param {integer} offset the offset of the output data.
374 * @param {integer} length the length of the output data.
375 */
376 return (offset, length) {
377 this.environment.returnValue = this.getMemory(offset, length)
378 }
379
380 /**
381 * Halt execution and register account for later deletion giving the remaining
382 * balance to an address path
383 * @param {integer} offset the offset to load the address from
384 */
385 suicide (addressOffset) {
386 this.environment.suicideAddress = new Address(this.getMemory(addressOffset, constants.ADDRESS_SIZE_BYTES))
387 }
388
389 getMemory (offset, length) {
390 return new Uint8Array(this.module.exports.memory, offset, length)
391 }
392
393 setMemory (offset, length, value) {
394 const memory = new Uint8Array(this.module.exports.memory, offset, length)
395 memory.set(value)
396 }
397}
398
399//
400// Polyfill required unless this is sorted: https://bugs.chromium.org/p/chromium/issues/detail?id=633895
401//
402// Polyfill from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
403//
404Function.prototype.bind = function (oThis) { // eslint-disable-line
405 if (typeof this !== 'function') {
406 // closest thing possible to the ECMAScript 5
407 // internal IsCallable function
408 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
409 }
410
411 var aArgs = Array.prototype.slice.call(arguments, 1)
412 var fToBind = this
413 var fNOP = function () {}
414 var fBound = function () {
415 return fToBind.apply(this instanceof fNOP ? this : oThis,
416 aArgs.concat(Array.prototype.slice.call(arguments)))
417 }
418
419 if (this.prototype) {
420 // Function.prototype doesn't have a prototype property
421 fNOP.prototype = this.prototype
422 }
423
424 fBound.prototype = new fNOP() // eslint-disable-line new-cap
425
426 return fBound
427}
428

Built with git-ssb-web