1
- const solc = require ( 'solc' ) ;
2
1
import { Provider } from '@ethersproject/providers' ;
3
2
import { Contract } from '@ethersproject/contracts' ;
4
- import { AbiCoder } from '@ethersproject/abi' ;
3
+ import { AbiCoder , FormatTypes , FunctionFragment , Fragment , Interface , ParamType } from '@ethersproject/abi' ;
5
4
import { keccak256 } from '@ethersproject/keccak256' ;
6
5
import { getContractAddress } from '@ethersproject/address' ;
6
+ import { parse } from '../parser/pkg/parser' ;
7
7
8
8
interface Opts {
9
9
network ?: string ,
@@ -15,24 +15,179 @@ const defaultOpts = {
15
15
version : 1
16
16
} ;
17
17
18
- const sleuthDeployer = process . env [ 'SLEUTH_ADDRESS' ] ?? '0x84C3e20985d9E7aEc46F80d2EB52b731D8CC40F8' ;
18
+ const sleuthDeployer = process . env [ 'SLEUTH_DEPLOYER' ] ?? '0x84C3e20985d9E7aEc46F80d2EB52b731D8CC40F8' ;
19
+
20
+ interface Query < T , A extends any [ ] = [ ] > {
21
+ bytecode : string ,
22
+ callargs ?: string ,
23
+ fn : FunctionFragment
24
+ }
25
+
26
+ interface Source {
27
+ name : string ,
28
+ address : string ,
29
+ iface : Interface
30
+ }
31
+
32
+ interface SolidityQueryOpts {
33
+ queryFunctionName ?: string ;
34
+ }
35
+
36
+ interface SolcInput {
37
+ language ?: string ,
38
+ sources : {
39
+ [ fileName : string ] : {
40
+ content : string
41
+ }
42
+ } ,
43
+ settings : object
44
+ }
45
+
46
+ interface SolcContract {
47
+ evm ?: {
48
+ bytecode ?: {
49
+ object : string
50
+ }
51
+ } ,
52
+ bytecode ?: {
53
+ object : string
54
+ } ,
55
+ abi : Fragment [ ]
56
+ }
57
+
58
+ interface SolcOutput {
59
+ contracts : {
60
+ [ fileName : string ] : {
61
+ [ contractName : string ] : SolcContract
62
+ }
63
+ } ,
64
+ errors ?: string [ ] ,
65
+ }
66
+
67
+ function solcCompile ( input : SolcInput ) : SolcOutput {
68
+ let solc ;
69
+ try {
70
+ solc = require ( 'solc' ) ;
71
+ } catch ( e ) {
72
+ throw new Error ( `solc.js yarn dependency not found. Please build with optional dependencies included` ) ;
73
+ }
74
+ return JSON . parse ( solc . compile ( JSON . stringify ( input ) ) ) ;
75
+ }
76
+
77
+ function hexify ( v : string ) : string {
78
+ return v . startsWith ( '0x' ) ? v : `0x${ v } ` ;
79
+ }
19
80
20
81
export class Sleuth {
21
82
provider : Provider ;
22
83
network : string ;
23
84
version : number ;
24
85
sleuthAddr : string ;
86
+ sources : Source [ ] ;
87
+ coder : AbiCoder ;
25
88
26
89
constructor ( provider : Provider , opts : Opts = { } ) {
27
90
this . provider = provider ;
28
91
this . network = opts . network ?? defaultOpts . network ;
29
92
this . version = opts . version ?? defaultOpts . version ;
30
93
this . sleuthAddr = getContractAddress ( { from : sleuthDeployer , nonce : this . version - 1 } ) ;
31
- console . log ( 'Sleuth address' , this . sleuthAddr ) ;
94
+ this . sources = [ ] ;
95
+ this . coder = new AbiCoder ( ) ;
32
96
}
33
97
34
- async query ( q : string ) {
98
+ query < T > ( q : string ) : Query < T , [ ] > {
99
+ let registrations = this . sources . map ( ( source ) => {
100
+ let iface = JSON . stringify ( source . iface . format ( FormatTypes . full ) ) ;
101
+ return `REGISTER CONTRACT ${ source . name } AT ${ source . address } WITH INTERFACE ${ iface } ;`
102
+ } ) . join ( "\n" ) ;
103
+ let fullQuery = `${ registrations } ${ q } ` ;
104
+ console . log ( "Full Query" , fullQuery ) ;
105
+ let [ tuple , yul ] = parse ( fullQuery ) . split ( ';' , 2 ) ;
106
+ console . log ( "Tuple" , tuple , "Yul" , yul ) ;
35
107
const input = {
108
+ language : 'Yul' ,
109
+ sources : {
110
+ 'query.yul' : {
111
+ content : yul
112
+ }
113
+ } ,
114
+ settings : {
115
+ outputSelection : {
116
+ '*' : {
117
+ '*' : [ '*' ]
118
+ }
119
+ }
120
+ }
121
+ } ;
122
+
123
+ let result = solcCompile ( input ) ;
124
+ console . log ( result . contracts [ 'query.yul' ] ) ;
125
+ if ( result . errors && result . errors . length > 0 ) {
126
+ throw new Error ( "Compilation Error: " + JSON . stringify ( result . errors ) ) ;
127
+ }
128
+
129
+ let bytecode = result ?. contracts [ 'query.yul' ] ?. Query ?. evm ?. bytecode ?. object ;
130
+
131
+ if ( ! bytecode ) {
132
+ throw new Error ( `Missing bytecode from compilation result: ${ JSON . stringify ( result ) } ` ) ;
133
+ }
134
+
135
+ return {
136
+ bytecode : bytecode ,
137
+ fn : FunctionFragment . from ( {
138
+ name : 'query' ,
139
+ inputs : [ ] ,
140
+ outputs : ParamType . from ( tuple ) . components ,
141
+ stateMutability : 'pure' ,
142
+ type : 'function'
143
+ } )
144
+ } ;
145
+ }
146
+
147
+ static querySol < T , A extends any [ ] = [ ] > ( q : string | object , opts : SolidityQueryOpts = { } ) : Query < T , A > {
148
+ if ( typeof ( q ) === 'string' ) {
149
+ let r ;
150
+ try {
151
+ // Try to parse as JSON, if that fails, then consider a query
152
+ r = JSON . parse ( q ) ;
153
+ } catch ( e ) {
154
+ // Ignore
155
+ }
156
+
157
+ if ( r ) {
158
+ return this . querySolOutput ( r , opts ) ;
159
+ } else {
160
+ // This must be a source file, try to compile
161
+ return this . querySolSource ( q , opts ) ;
162
+ }
163
+
164
+ } else {
165
+ // This was passed in as a pre-parsed contract. Or at least, it should have been.
166
+ return this . querySolOutput ( q as SolcContract , opts ) ;
167
+ }
168
+ }
169
+
170
+ static querySolOutput < T , A extends any [ ] = [ ] > ( c : SolcContract , opts : SolidityQueryOpts = { } ) : Query < T , A > {
171
+ let queryFunctionName = opts . queryFunctionName ?? 'query' ;
172
+ let b = c . evm ?. bytecode ?. object ?? c . bytecode ?. object ;
173
+ if ( ! b ) {
174
+ throw new Error ( `Missing (evm.)bytecode.object in contract ${ JSON . stringify ( c , null , 4 ) } ` ) ;
175
+ }
176
+ let abi = c . abi ;
177
+ let queryAbi = abi . find ( ( { type, name} : any ) => type === 'function' && name === queryFunctionName ) ;
178
+ if ( ! queryAbi ) {
179
+ throw new Error ( `Query must include function \`${ queryFunctionName } ()\`` ) ;
180
+ }
181
+
182
+ return {
183
+ bytecode : b ,
184
+ fn : queryAbi as FunctionFragment
185
+ } ;
186
+ }
187
+
188
+ static querySolSource < T , A extends any [ ] = [ ] > ( q : string , opts : SolidityQueryOpts = { } ) : Query < T , A > {
189
+ let fnName = opts . queryFunctionName ?? 'query' ;
190
+ let input = {
36
191
language : 'Solidity' ,
37
192
sources : {
38
193
'query.sol' : {
@@ -48,8 +203,8 @@ export class Sleuth {
48
203
}
49
204
} ;
50
205
51
- let result = JSON . parse ( solc . compile ( JSON . stringify ( input ) ) ) ;
52
- if ( result . errors ) {
206
+ let result = solcCompile ( input ) ;
207
+ if ( result . errors && result . errors . length > 0 ) {
53
208
throw new Error ( "Compilation Error: " + JSON . stringify ( result . errors ) ) ;
54
209
}
55
210
let contract = result . contracts [ 'query.sol' ] ;
@@ -62,19 +217,35 @@ export class Sleuth {
62
217
} else if ( Object . keys ( contract ) . length > 1 ) {
63
218
console . warn ( `Query contains multiple contracts, using ${ Object . keys ( contract ) [ 0 ] } ` ) ;
64
219
}
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()\`` ) ;
220
+ return this . querySolOutput ( c , opts ) ;
221
+ }
222
+
223
+ async addSource ( name : string , address : string , iface : string [ ] | Interface ) {
224
+ if ( Array . isArray ( iface ) ) {
225
+ iface = new Interface ( iface ) ;
70
226
}
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 ]
227
+ this . sources . push ( { name, address, iface} ) ;
228
+ }
229
+
230
+ async fetch < T , A extends any [ ] = [ ] > ( q : Query < T , A > , args ?: A ) : Promise < T > {
231
+ let sleuthCtx = new Contract ( this . sleuthAddr , [
232
+ 'function query(bytes,bytes) public view returns (bytes)'
233
+ ] , this . provider ) ;
234
+ let iface = new Interface ( [ q . fn ] ) ;
235
+ let argsCoded = iface . encodeFunctionData ( q . fn . name , args ?? [ ] ) ;
236
+ let queryResult = await sleuthCtx . query ( hexify ( q . bytecode ) , argsCoded ) ;
237
+ console . log ( q . fn ) ;
238
+ console . log ( queryResult ) ;
239
+ let r = this . coder . decode ( q . fn . outputs ?? [ ] , queryResult ) as unknown ;
240
+ if ( Array . isArray ( r ) && r . length === 1 ) {
241
+ return r [ 0 ] as T ;
76
242
} else {
77
- return res ;
243
+ return r as T ;
78
244
}
79
245
}
246
+
247
+ async fetchSql < T > ( q : string ) : Promise < T > {
248
+ let query = this . query < T > ( q ) ;
249
+ return this . fetch < T , [ ] > ( query , [ ] ) ;
250
+ }
80
251
}
0 commit comments