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