Files: be42af81974876ce832fa149178075c8fca67321 / interface.js
18171 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 Address = require('./address.js') |
6 | const U256 = require('./u256.js') |
7 | const fs = require('fs') |
8 | const path = require('path') |
9 | |
10 | const U128_SIZE_BYTES = 16 |
11 | const ADDRESS_SIZE_BYTES = 20 |
12 | const U256_SIZE_BYTES = 32 |
13 | |
14 | // The interface exposed to the WebAessembly Core |
15 | module.exports = class Interface { |
16 | constructor (environment) { |
17 | this.environment = environment |
18 | const shimBin = fs.readFileSync(path.join(__dirname, '/wasm/interface.wasm')) |
19 | const shimMod = WebAssembly.Module(shimBin) |
20 | this.shims = WebAssembly.Instance(shimMod, { |
21 | 'interface': { |
22 | 'useGas': this._useGas.bind(this), |
23 | 'getGasLeftHigh': this._getGasLeftHigh.bind(this), |
24 | 'getGasLeftLow': this._getGasLeftLow.bind(this) |
25 | } |
26 | }) |
27 | } |
28 | |
29 | get exportTable () { |
30 | let exportMethods = [ |
31 | // include all the public methods according to the Ethereum Environment Interface (EEI) r1 |
32 | 'getAddress', |
33 | 'getBalance', |
34 | 'getTxOrigin', |
35 | 'getCaller', |
36 | 'getCallValue', |
37 | 'getCallDataSize', |
38 | 'callDataCopy', |
39 | 'callDataCopy256', |
40 | 'getCodeSize', |
41 | 'codeCopy', |
42 | 'getExternalCodeSize', |
43 | 'externalCodeCopy', |
44 | 'getTxGasPrice', |
45 | 'getBlockHash', |
46 | 'getBlockCoinbase', |
47 | 'getBlockTimestamp', |
48 | 'getBlockNumber', |
49 | 'getBlockDifficulty', |
50 | 'getBlockGasLimit', |
51 | 'log', |
52 | 'create', |
53 | 'call', |
54 | 'callCode', |
55 | 'callDelegate', |
56 | 'storageStore', |
57 | 'storageLoad', |
58 | 'return', |
59 | 'selfDestruct' |
60 | ] |
61 | let ret = {} |
62 | exportMethods.forEach((method) => { |
63 | ret[method] = this[method].bind(this) |
64 | }) |
65 | return ret |
66 | } |
67 | |
68 | setModule (mod) { |
69 | this.module = mod |
70 | } |
71 | |
72 | /** |
73 | * Subtracts an amount to the gas counter |
74 | * @param {integer} amount the amount to subtract to the gas counter |
75 | */ |
76 | _useGas (high, low) { |
77 | if (high < 0) { |
78 | // convert from a 32-bit two's compliment |
79 | high = 0x100000000 - high |
80 | } |
81 | |
82 | if (low < 0) { |
83 | // convert from a 32-bit two's compliment |
84 | low = 0x100000000 - low |
85 | } |
86 | |
87 | // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32 |
88 | const amount = (high * 4294967296) + low |
89 | this.takeGas(amount) |
90 | } |
91 | |
92 | /** |
93 | * Returns the current amount of gas |
94 | * @return {integer} |
95 | */ |
96 | _getGasLeftHigh () { |
97 | return Math.floor(this.environment.gasLeft / 4294967296) |
98 | } |
99 | |
100 | /** |
101 | * Returns the current amount of gas |
102 | * @return {integer} |
103 | */ |
104 | _getGasLeftLow () { |
105 | return this.environment.gasLeft |
106 | } |
107 | |
108 | /** |
109 | * Gets address of currently executing account and loads it into memory at |
110 | * the given offset. |
111 | * @param {integer} offset |
112 | */ |
113 | getAddress (offset) { |
114 | this.takeGas(2) |
115 | |
116 | this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.address.toMemory()) |
117 | } |
118 | |
119 | /** |
120 | * Gets balance of the given account and loads it into memory at the given |
121 | * offset. |
122 | * @param {integer} addressOffset the memory offset to laod the address |
123 | * @param {integer} resultOffset |
124 | */ |
125 | getBalance (addressOffset, offset) { |
126 | this.takeGas(20) |
127 | |
128 | const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)) |
129 | // call the parent contract and ask for the balance of one of its child contracts |
130 | const balance = this.environment.getBalance(address) |
131 | this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES)) |
132 | } |
133 | |
134 | /** |
135 | * Gets the execution's origination address and loads it into memory at the |
136 | * given offset. This is the sender of original transaction; it is never an |
137 | * account with non-empty associated code. |
138 | * @param {integer} offset |
139 | */ |
140 | getTxOrigin (offset) { |
141 | this.takeGas(2) |
142 | |
143 | this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.origin.toMemory()) |
144 | } |
145 | |
146 | /** |
147 | * Gets caller address and loads it into memory at the given offset. This is |
148 | * the address of the account that is directly responsible for this execution. |
149 | * @param {integer} offset |
150 | */ |
151 | getCaller (offset) { |
152 | this.takeGas(2) |
153 | |
154 | this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.caller.toMemory()) |
155 | } |
156 | |
157 | /** |
158 | * Gets the deposited value by the instruction/transaction responsible for |
159 | * this execution and loads it into memory at the given location. |
160 | * @param {integer} offset |
161 | */ |
162 | getCallValue (offset) { |
163 | this.takeGas(2) |
164 | |
165 | this.setMemory(offset, U128_SIZE_BYTES, this.environment.callValue.toMemory(U128_SIZE_BYTES)) |
166 | } |
167 | |
168 | /** |
169 | * Get size of input data in current environment. This pertains to the input |
170 | * data passed with the message call instruction or transaction. |
171 | * @return {integer} |
172 | */ |
173 | getCallDataSize () { |
174 | this.takeGas(2) |
175 | |
176 | return this.environment.callData.length |
177 | } |
178 | |
179 | /** |
180 | * Copys the input data in current environment to memory. This pertains to |
181 | * the input data passed with the message call instruction or transaction. |
182 | * @param {integer} offset the offset in memory to load into |
183 | * @param {integer} dataOffset the offset in the input data |
184 | * @param {integer} length the length of data to copy |
185 | */ |
186 | callDataCopy (offset, dataOffset, length) { |
187 | this.takeGas(3 + Math.ceil(length / 32) * 3) |
188 | |
189 | if (length) { |
190 | const callData = this.environment.callData.slice(dataOffset, dataOffset + length) |
191 | this.setMemory(offset, length, callData) |
192 | } |
193 | } |
194 | |
195 | /** |
196 | * Copys the input data in current environment to memory. This pertains to |
197 | * the input data passed with the message call instruction or transaction. |
198 | * @param {integer} offset the offset in memory to load into |
199 | * @param {integer} dataOffset the offset in the input data |
200 | */ |
201 | callDataCopy256 (offset, dataOffset) { |
202 | this.takeGas(3) |
203 | const callData = this.environment.callData.slice(dataOffset, dataOffset + 32) |
204 | this.setMemory(offset, U256_SIZE_BYTES, callData) |
205 | } |
206 | |
207 | /** |
208 | * Gets the size of code running in current environment. |
209 | * @return {interger} |
210 | */ |
211 | getCodeSize () { |
212 | this.takeGas(2) |
213 | |
214 | return this.environment.code.length |
215 | } |
216 | |
217 | /** |
218 | * Copys the code running in current environment to memory. |
219 | * @param {integer} offset the memory offset |
220 | * @param {integer} codeOffset the code offset |
221 | * @param {integer} length the length of code to copy |
222 | */ |
223 | codeCopy (resultOffset, codeOffset, length) { |
224 | this.takeGas(3 + Math.ceil(length / 32) * 3) |
225 | |
226 | if (length) { |
227 | const code = this.environment.code.slice(codeOffset, codeOffset + length) |
228 | this.setMemory(resultOffset, length, code) |
229 | } |
230 | } |
231 | |
232 | /** |
233 | * Get size of an account’s code. |
234 | * @param {integer} addressOffset the offset in memory to load the address from |
235 | * @return {integer} |
236 | */ |
237 | getExternalCodeSize (addressOffset) { |
238 | this.takeGas(20) |
239 | |
240 | const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)) |
241 | const code = this.environment.getCode(address) |
242 | return code.length |
243 | } |
244 | |
245 | /** |
246 | * Copys the code of an account to memory. |
247 | * @param {integer} addressOffset the memory offset of the address |
248 | * @param {integer} resultOffset the memory offset |
249 | * @param {integer} codeOffset the code offset |
250 | * @param {integer} length the length of code to copy |
251 | */ |
252 | externalCodeCopy (addressOffset, resultOffset, codeOffset, length) { |
253 | this.takeGas(20 + Math.ceil(length / 32) * 3) |
254 | |
255 | if (length) { |
256 | const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)) |
257 | let code = this.environment.getCode(address) |
258 | code = code.slice(codeOffset, codeOffset + length) |
259 | this.setMemory(resultOffset, length, code) |
260 | } |
261 | } |
262 | |
263 | /** |
264 | * Gets price of gas in current environment. |
265 | * @return {integer} |
266 | */ |
267 | getTxGasPrice () { |
268 | this.takeGas(2) |
269 | |
270 | return this.environment.gasPrice |
271 | } |
272 | |
273 | /** |
274 | * Gets the hash of one of the 256 most recent complete blocks. |
275 | * @param {integer} number which block to load |
276 | * @param {integer} offset the offset to load the hash into |
277 | */ |
278 | getBlockHash (number, offset) { |
279 | this.takeGas(20) |
280 | |
281 | const diff = this.environment.block.number - number |
282 | let hash |
283 | |
284 | if (diff > 256 || diff <= 0) { |
285 | hash = new U256(0) |
286 | } else { |
287 | hash = new U256(this.environment.getBlockHash(number)) |
288 | } |
289 | this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory()) |
290 | } |
291 | |
292 | /** |
293 | * Gets the block’s beneficiary address and loads into memory. |
294 | * @param offset |
295 | */ |
296 | getBlockCoinbase (offset) { |
297 | this.takeGas(2) |
298 | |
299 | this.setMemory(offset, ADDRESS_SIZE_BYTES, this.environment.block.coinbase.toMemory()) |
300 | } |
301 | |
302 | /** |
303 | * Get the block’s timestamp. |
304 | * @return {integer} |
305 | */ |
306 | getBlockTimestamp () { |
307 | this.takeGas(2) |
308 | |
309 | return this.environment.block.timestamp |
310 | } |
311 | |
312 | /** |
313 | * Get the block’s number. |
314 | * @return {integer} |
315 | */ |
316 | getBlockNumber () { |
317 | this.takeGas(2) |
318 | |
319 | return this.environment.block.number |
320 | } |
321 | |
322 | /** |
323 | * Get the block’s difficulty. |
324 | * @return {integer} |
325 | */ |
326 | getBlockDifficulty (offset) { |
327 | this.takeGas(2) |
328 | |
329 | this.setMemory(offset, U256_SIZE_BYTES, this.environment.block.difficulty.toMemory()) |
330 | } |
331 | |
332 | /** |
333 | * Get the block’s gas limit. |
334 | * @return {integer} |
335 | */ |
336 | getBlockGasLimit () { |
337 | this.takeGas(2) |
338 | |
339 | return this.environment.block.gasLimit |
340 | } |
341 | |
342 | /** |
343 | * Creates a new log in the current environment |
344 | * @param {integer} dataOffset the offset in memory to load the memory |
345 | * @param {integer} length the data length |
346 | * @param {integer} number of topics |
347 | */ |
348 | log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) { |
349 | if (numberOfTopics < 0 || numberOfTopics > 4) { |
350 | throw new Error('Invalid numberOfTopics') |
351 | } |
352 | |
353 | this.takeGas(375 + length * 8 + numberOfTopics * 375) |
354 | |
355 | const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([]) |
356 | const topics = [] |
357 | |
358 | if (numberOfTopics > 0) { |
359 | topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES))) |
360 | } |
361 | |
362 | if (numberOfTopics > 1) { |
363 | topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES))) |
364 | } |
365 | |
366 | if (numberOfTopics > 2) { |
367 | topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES))) |
368 | } |
369 | |
370 | if (numberOfTopics > 3) { |
371 | topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES))) |
372 | } |
373 | |
374 | this.environment.logs.push({ |
375 | data: data, |
376 | topics: topics |
377 | }) |
378 | } |
379 | |
380 | /** |
381 | * Creates a new contract with a given value. |
382 | * @param {integer} valueOffset the offset in memory to the value from |
383 | * @param {integer} dataOffset the offset to load the code for the new contract from |
384 | * @param {integer} length the data length |
385 | * @param (integer} resultOffset the offset to write the new contract address to |
386 | * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not |
387 | */ |
388 | create (valueOffset, dataOffset, length, resultOffset) { |
389 | this.takeGas(32000) |
390 | |
391 | const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES)) |
392 | if (length) { |
393 | const data = this.getMemory(dataOffset, length).slice(0) |
394 | // const [errorCode, address] = this.environment.create(value, data) |
395 | } |
396 | let address |
397 | if (value.gt(this.environment.value)) { |
398 | address = new Address() |
399 | } else { |
400 | address = new Address('0x945304eb96065b2a98b57a48a06ae28d285a71b5') |
401 | } |
402 | this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address.toMemory()) |
403 | // return errorCode |
404 | } |
405 | |
406 | /** |
407 | * Sends a message with arbiatary data to a given address path |
408 | * @param {integer} addressOffset the offset to load the address path from |
409 | * @param {integer} valueOffset the offset to load the value from |
410 | * @param {integer} dataOffset the offset to load data from |
411 | * @param {integer} dataLength the length of data |
412 | * @param {integer} resultOffset the offset to store the result data at |
413 | * @param {integer} resultLength |
414 | * @param {integer} gas |
415 | * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not |
416 | */ |
417 | call (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) { |
418 | // Load the params from mem |
419 | const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)) |
420 | const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES)) |
421 | |
422 | this.takeGas(40) |
423 | |
424 | // const data = this.getMemory(dataOffset, dataLength).slice(0) |
425 | |
426 | // // Special case for calling into empty account |
427 | // if (!this.environment.isAccountPresent(address)) { |
428 | // this.takeGas(25000) |
429 | // } |
430 | if (address.lt(new U256(4))) { |
431 | this.takeGas(25000) |
432 | } |
433 | // // Special case for non-zero value |
434 | if (!value.isZero()) { |
435 | this.takeGas(9000 - 2300 + gas) |
436 | this.takeGas(-gas) |
437 | } |
438 | |
439 | // const [errorCode, result] = this.environment.call(gas, address, value, data) |
440 | // this.setMemory(resultOffset, resultLength, result) |
441 | // return errorCode |
442 | // |
443 | return 1 |
444 | } |
445 | |
446 | /** |
447 | * Message-call into this account with an alternative account’s code. |
448 | * @param {integer} addressOffset the offset to load the address path from |
449 | * @param {integer} valueOffset the offset to load the value from |
450 | * @param {integer} dataOffset the offset to load data from |
451 | * @param {integer} dataLength the length of data |
452 | * @param {integer} resultOffset the offset to store the result data at |
453 | * @param {integer} resultLength |
454 | * @param {integer} gas |
455 | * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not |
456 | */ |
457 | callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength) { |
458 | // Load the params from mem |
459 | const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)) |
460 | const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES)) |
461 | |
462 | this.takeGas(40) |
463 | |
464 | // const data = this.getMemory(dataOffset, dataLength).slice(0) |
465 | |
466 | // // Special case for calling into empty account |
467 | // if (!this.environment.isAccountPresent(address)) { |
468 | // this.takeGas(25000) |
469 | // } |
470 | // // Special case for non-zero value |
471 | if (!value.isZero()) { |
472 | this.takeGas(9000 - 2300 + gas) |
473 | this.takeGas(-gas) |
474 | } |
475 | |
476 | // const [errorCode, result] = this.environment.call(gas, address, value, data) |
477 | // this.setMemory(resultOffset, resultLength, result) |
478 | // return errorCode |
479 | // |
480 | return 1 |
481 | } |
482 | |
483 | /** |
484 | * Message-call into this account with an alternative account’s code, but |
485 | * persisting the current values for sender and value. |
486 | * @param {integer} gas |
487 | * @param {integer} addressOffset the offset to load the address path from |
488 | * @param {integer} valueOffset the offset to load the value from |
489 | * @param {integer} dataOffset the offset to load data from |
490 | * @param {integer} dataLength the length of data |
491 | * @param {integer} resultOffset the offset to store the result data at |
492 | * @param {integer} resultLength |
493 | * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not |
494 | */ |
495 | callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) { |
496 | // FIXME: count properly |
497 | this.takeGas(40) |
498 | |
499 | const data = this.getMemory(dataOffset, dataLength).slice(0) |
500 | const address = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)) |
501 | const [errorCode, result] = this.environment.callDelegate(gas, address, data) |
502 | this.setMemory(resultOffset, resultLength, result) |
503 | return errorCode |
504 | } |
505 | |
506 | /** |
507 | * store a value at a given path in long term storage which are both loaded |
508 | * from Memory |
509 | * @param {interger} pathOffest the memory offset to load the the path from |
510 | * @param {interger} valueOffset the memory offset to load the value from |
511 | */ |
512 | storageStore (pathOffset, valueOffset) { |
513 | const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex') |
514 | // copy the value |
515 | const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0) |
516 | const oldValue = this.environment.state.get(path) |
517 | const valIsZero = value.every((i) => i === 0) |
518 | |
519 | this.takeGas(5000) |
520 | |
521 | // write |
522 | if (!valIsZero && !oldValue) { |
523 | this.takeGas(15000) |
524 | } |
525 | |
526 | // delete |
527 | if (valIsZero && oldValue) { |
528 | this.environment.gasRefund += 15000 |
529 | this.environment.state.delete(path) |
530 | } else { |
531 | this.environment.state.set(path, value) |
532 | } |
533 | } |
534 | |
535 | /** |
536 | * reterives a value at a given path in long term storage |
537 | * @param {interger} pathOffest the memory offset to load the the path from |
538 | * @param {interger} resultOffset the memory offset to load the value from |
539 | */ |
540 | storageLoad (pathOffset, resultOffset) { |
541 | this.takeGas(50) |
542 | |
543 | const path = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex') |
544 | const result = this.environment.state.get(path) || new Uint8Array(32) |
545 | this.setMemory(resultOffset, U256_SIZE_BYTES, result) |
546 | } |
547 | |
548 | /** |
549 | * Halt execution returning output data. |
550 | * @param {integer} offset the offset of the output data. |
551 | * @param {integer} length the length of the output data. |
552 | */ |
553 | return (offset, length) { |
554 | if (length) { |
555 | this.environment.returnValue = this.getMemory(offset, length).slice(0) |
556 | } |
557 | } |
558 | |
559 | /** |
560 | * Halt execution and register account for later deletion giving the remaining |
561 | * balance to an address path |
562 | * @param {integer} offset the offset to load the address from |
563 | */ |
564 | selfDestruct (addressOffset) { |
565 | this.environment.selfDestruct = true |
566 | this.environment.selfDestructAddress = Address.fromMemory(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)) |
567 | this.environment.gasRefund += 24000 |
568 | } |
569 | |
570 | getMemory (offset, length) { |
571 | return new Uint8Array(this.module.exports.memory, offset, length) |
572 | } |
573 | |
574 | setMemory (offset, length, value) { |
575 | const memory = new Uint8Array(this.module.exports.memory, offset, length) |
576 | memory.set(value) |
577 | } |
578 | |
579 | /* |
580 | * Takes gas from the tank. Only needs to check if there's gas left to be taken, |
581 | * because every caller of this method is trusted. |
582 | */ |
583 | takeGas (amount) { |
584 | if (this.environment.gasLeft < amount) { |
585 | throw new Error('Ran out of gas') |
586 | } |
587 | this.environment.gasLeft -= amount |
588 | } |
589 | } |
590 |
Built with git-ssb-web