git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 29003849d596ca5f1379096bd408c4c1f82c460f

Files: 29003849d596ca5f1379096bd408c4c1f82c460f / interface.js

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

Built with git-ssb-web