git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 026b99c4df17c82be9f9255083a7271d8d3437b6

Files: 026b99c4df17c82be9f9255083a7271d8d3437b6 / interface.js

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

Built with git-ssb-web