|
| 1 | +const solc = require('solc'); |
| 2 | +import { Provider } from '@ethersproject/providers'; |
| 3 | +import { Contract } from '@ethersproject/contracts'; |
| 4 | +import { AbiCoder } from '@ethersproject/abi'; |
| 5 | +import { keccak256 } from '@ethersproject/keccak256'; |
| 6 | +import { getContractAddress } from '@ethersproject/address'; |
| 7 | + |
| 8 | +interface Opts { |
| 9 | + network?: string, |
| 10 | + version?: number |
| 11 | +}; |
| 12 | + |
| 13 | +const defaultOpts = { |
| 14 | + network: 'mainnet', |
| 15 | + version: 1 |
| 16 | +}; |
| 17 | + |
| 18 | +const sleuthDeployer = process.env['SLEUTH_ADDRESS'] ?? '0x84C3e20985d9E7aEc46F80d2EB52b731D8CC40F8'; |
| 19 | + |
| 20 | +export class Sleuth { |
| 21 | + provider: Provider; |
| 22 | + network: string; |
| 23 | + version: number; |
| 24 | + sleuthAddr: string; |
| 25 | + |
| 26 | + constructor(provider: Provider, opts: Opts = {}) { |
| 27 | + this.provider = provider; |
| 28 | + this.network = opts.network ?? defaultOpts.network; |
| 29 | + this.version = opts.version ?? defaultOpts.version; |
| 30 | + this.sleuthAddr = getContractAddress({ from: sleuthDeployer, nonce: this.version - 1 }); |
| 31 | + console.log('Sleuth address', this.sleuthAddr); |
| 32 | + } |
| 33 | + |
| 34 | + async query(q: string) { |
| 35 | + const input = { |
| 36 | + language: 'Solidity', |
| 37 | + sources: { |
| 38 | + 'query.sol': { |
| 39 | + content: q |
| 40 | + } |
| 41 | + }, |
| 42 | + settings: { |
| 43 | + outputSelection: { |
| 44 | + '*': { |
| 45 | + '*': ['*'] |
| 46 | + } |
| 47 | + } |
| 48 | + } |
| 49 | + }; |
| 50 | + |
| 51 | + let result = JSON.parse(solc.compile(JSON.stringify(input))); |
| 52 | + if (result.errors) { |
| 53 | + throw new Error("Compilation Error: " + JSON.stringify(result.errors)); |
| 54 | + } |
| 55 | + let contract = result.contracts['query.sol']; |
| 56 | + if (!contract) { |
| 57 | + throw new Error(`Missing query.sol compiled contract in ${JSON.stringify(Object.keys(result.contracts))}`); |
| 58 | + } |
| 59 | + let c = Object.values(contract)[0] as any; |
| 60 | + if (!c) { |
| 61 | + throw new Error(`Query does not contain any contract definitions`); |
| 62 | + } else if (Object.keys(contract).length > 1) { |
| 63 | + console.warn(`Query contains multiple contracts, using ${Object.keys(contract)[0]}`); |
| 64 | + } |
| 65 | + let b = c.evm.bytecode.object; |
| 66 | + let abi = c.abi; |
| 67 | + let queryAbi = abi.find(({type, name}: any) => type === 'function' && name === 'query'); |
| 68 | + if (!queryAbi) { |
| 69 | + throw new Error(`Query must include function \`query()\``); |
| 70 | + } |
| 71 | + let sleuthCtx = new Contract(this.sleuthAddr, ['function query(bytes) public view returns (bytes)'], this.provider); |
| 72 | + let queryResult = await sleuthCtx.query('0x' + b); |
| 73 | + let res = new AbiCoder().decode(queryAbi.outputs, queryResult); |
| 74 | + if (res.length === 1) { |
| 75 | + return res[0] |
| 76 | + } else { |
| 77 | + return res; |
| 78 | + } |
| 79 | + } |
| 80 | +} |
0 commit comments