Skip to content

Commit df0b34f

Browse files
committed
Add in cmds for disputes in cli
1 parent 03cab69 commit df0b34f

File tree

2 files changed

+258
-0
lines changed

2 files changed

+258
-0
lines changed
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import consola from 'consola'
2+
import yargs, { Argv } from 'yargs'
3+
import { constants, utils, Wallet, providers, BigNumber } from 'ethers'
4+
import { createAttestation, Attestation, Receipt } from '@graphprotocol/common-ts'
5+
6+
import { sendTransaction } from '../../network'
7+
import { loadEnv, CLIArgs, CLIEnvironment } from '../../env'
8+
9+
const { HashZero } = constants
10+
const { defaultAbiCoder: abi, arrayify, concat, hexlify, randomBytes, parseUnits } = utils
11+
12+
interface ChannelKey {
13+
privKey: string
14+
pubKey: string
15+
address: string
16+
}
17+
18+
const logger = consola.create({})
19+
export const randomHexBytes = (n = 32): string => hexlify(randomBytes(n))
20+
export const toGRT = (value: string | number): BigNumber => {
21+
return parseUnits(typeof value === 'number' ? value.toString() : value, '18')
22+
}
23+
export const getProvider = (providerUrl: string, network?: number): providers.JsonRpcProvider =>
24+
new providers.JsonRpcProvider(providerUrl, network)
25+
26+
export const getChainID = (): number => {
27+
return 4 // Only works for rinkeby right now
28+
}
29+
30+
async function buildAttestation(receipt: Receipt, signer: string, disputeManagerAddress: string) {
31+
const attestation = await createAttestation(signer, getChainID(), disputeManagerAddress, receipt)
32+
return attestation
33+
}
34+
35+
export const deriveChannelKey = (): ChannelKey => {
36+
const w = Wallet.createRandom()
37+
return { privKey: w.privateKey, pubKey: w.publicKey, address: utils.computeAddress(w.publicKey) }
38+
}
39+
40+
function encodeAttestation(attestation: Attestation): string {
41+
const data = arrayify(
42+
abi.encode(
43+
['bytes32', 'bytes32', 'bytes32'],
44+
[attestation.requestCID, attestation.responseCID, attestation.subgraphDeploymentID],
45+
),
46+
)
47+
const sig = concat([
48+
arrayify(hexlify(attestation.v)),
49+
arrayify(attestation.r),
50+
arrayify(attestation.s),
51+
])
52+
return hexlify(concat([data, sig]))
53+
}
54+
55+
async function setupIndexer(
56+
cli: CLIEnvironment,
57+
cliArgs: CLIArgs,
58+
indexerChannelKey: ChannelKey,
59+
receipt: Receipt,
60+
accountIndex: number,
61+
) {
62+
const indexer = Wallet.fromMnemonic(cliArgs.mnemonic, `m/44'/60'/0'/0/${accountIndex}`).connect(
63+
getProvider(cliArgs.providerUrl),
64+
)
65+
66+
const grt = cli.contracts.GraphToken
67+
const staking = cli.contracts.Staking
68+
69+
const indexerTokens = toGRT('100000')
70+
const indexerAllocatedTokens = toGRT('10000')
71+
const metadata = HashZero
72+
73+
logger.log('Transferring tokens to the indexer...')
74+
await sendTransaction(cli.wallet, grt, 'transfer', [indexer.address, indexerTokens])
75+
logger.log('Approving the staking address to pull tokens...')
76+
await sendTransaction(cli.wallet, grt, 'approve', [staking.address, indexerTokens])
77+
logger.log('Staking...')
78+
await sendTransaction(cli.wallet, staking, 'stake', [indexerTokens])
79+
logger.log('Allocating...')
80+
await sendTransaction(
81+
cli.wallet,
82+
staking,
83+
'allocate',
84+
[receipt.subgraphDeploymentID, indexerAllocatedTokens, indexerChannelKey.address, metadata],
85+
)
86+
}
87+
88+
// This just creates any query dispute conflict to test the subgraph, no real data is sent
89+
export const createTestQueryDisputeConflict = async (
90+
cli: CLIEnvironment,
91+
cliArgs: CLIArgs,
92+
): Promise<void> => {
93+
// Derive some channel keys for each indexer used to sign attestations
94+
const indexer1ChannelKey = deriveChannelKey()
95+
const indexer2ChannelKey = deriveChannelKey()
96+
97+
// Create an attesation receipt for the dispute
98+
const receipt: Receipt = {
99+
requestCID: randomHexBytes(),
100+
responseCID: randomHexBytes(),
101+
subgraphDeploymentID: randomHexBytes(),
102+
}
103+
104+
const receipt1 = receipt
105+
const receipt2 = { ...receipt1, responseCID: randomHexBytes() }
106+
107+
await setupIndexer(cli, cliArgs, indexer1ChannelKey, receipt1, 1)
108+
await setupIndexer(cli, cliArgs, indexer2ChannelKey, receipt2, 2)
109+
110+
const disputeManager = cli.contracts.DisputeManager
111+
const disputeManagerAddr = disputeManager.address
112+
113+
const attestation1 = await buildAttestation(
114+
receipt1,
115+
indexer1ChannelKey.privKey,
116+
disputeManagerAddr,
117+
)
118+
const attestation2 = await buildAttestation(
119+
receipt2,
120+
indexer2ChannelKey.privKey,
121+
disputeManagerAddr,
122+
)
123+
124+
logger.log(`Creating conflicting attestations...`)
125+
await sendTransaction(
126+
cli.wallet,
127+
disputeManager,
128+
'createQueryDisputeConflict',
129+
[encodeAttestation(attestation1), encodeAttestation(attestation2)],
130+
)
131+
}
132+
133+
// This just creates any indexing dispute to test the subgraph, no real data is sent
134+
export const createTestIndexingDispute = async (
135+
cli: CLIEnvironment,
136+
cliArgs: CLIArgs,
137+
): Promise<void> => {
138+
// Derive some channel keys for each indexer used to sign attestations
139+
const indexerChannelKey = deriveChannelKey()
140+
141+
// Create an attesation receipt for the dispute
142+
const receipt: Receipt = {
143+
requestCID: randomHexBytes(),
144+
responseCID: randomHexBytes(),
145+
subgraphDeploymentID: randomHexBytes(),
146+
}
147+
148+
await setupIndexer(cli, cliArgs, indexerChannelKey, receipt, 0)
149+
150+
// min deposit is 100 GRT, so we do 1000 for safe measure
151+
const deposit = toGRT('1000')
152+
const disputeManager = cli.contracts.DisputeManager
153+
const grt = cli.contracts.GraphToken
154+
155+
logger.log('Approving the dispute address to pull tokens...')
156+
await sendTransaction(cli.wallet, grt, 'approve', [disputeManager.address, deposit])
157+
158+
logger.log(`Creating indexing dispute...`)
159+
await sendTransaction(
160+
cli.wallet,
161+
disputeManager,
162+
'createIndexingDispute',
163+
[indexerChannelKey.address, deposit],
164+
)
165+
}
166+
167+
export const accept = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
168+
const disputeManager = cli.contracts.DisputeManager
169+
const disputeID = cliArgs.disputeID
170+
logger.log(`Accepting...`)
171+
await sendTransaction(cli.wallet, disputeManager, 'acceptDispute', ...[disputeID])
172+
}
173+
174+
export const reject = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
175+
const disputeManager = cli.contracts.DisputeManager
176+
const disputeID = cliArgs.disputeID
177+
logger.log(`Rejecting...`)
178+
await sendTransaction(cli.wallet, disputeManager, 'rejectDispute', ...[disputeID])
179+
}
180+
181+
export const draw = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<void> => {
182+
const disputeManager = cli.contracts.DisputeManager
183+
const disputeID = cliArgs.disputeID
184+
logger.log(`Drawing...`)
185+
await sendTransaction(cli.wallet, disputeManager, 'drawDispute', ...[disputeID])
186+
}
187+
188+
export const disputeManagerCommand = {
189+
command: 'disputeManager',
190+
describe: 'Dispute manager calls',
191+
builder: (yargs: Argv): yargs.Argv => {
192+
return yargs
193+
.command({
194+
command: 'query-dispute-conflict-test',
195+
describe: 'Just create any query dispute to test the subgraph',
196+
handler: async (argv: CLIArgs): Promise<void> => {
197+
return createTestQueryDisputeConflict(await loadEnv(argv), argv)
198+
},
199+
})
200+
.command({
201+
command: 'indexing-dispute-test',
202+
describe: 'Just create any query dispute to test the subgraph',
203+
handler: async (argv: CLIArgs): Promise<void> => {
204+
return createTestIndexingDispute(await loadEnv(argv), argv)
205+
},
206+
})
207+
.command({
208+
command: 'accept',
209+
describe: 'Accept a dispute',
210+
builder: (yargs: Argv) => {
211+
return yargs.option('disputeID', {
212+
description: 'The Dispute ID in question',
213+
type: 'string',
214+
requiresArg: true,
215+
demandOption: true,
216+
})
217+
},
218+
handler: async (argv: CLIArgs): Promise<void> => {
219+
return accept(await loadEnv(argv), argv)
220+
},
221+
})
222+
.command({
223+
command: 'reject',
224+
describe: 'Reject a dispute',
225+
builder: (yargs: Argv) => {
226+
return yargs.option('disputeID', {
227+
description: 'The Dispute ID in question',
228+
type: 'string',
229+
requiresArg: true,
230+
demandOption: true,
231+
})
232+
},
233+
handler: async (argv: CLIArgs): Promise<void> => {
234+
return reject(await loadEnv(argv), argv)
235+
},
236+
})
237+
.command({
238+
command: 'draw',
239+
describe: 'Draw a dispute',
240+
builder: (yargs: Argv) => {
241+
return yargs.option('disputeID', {
242+
description: 'The Dispute ID in question',
243+
type: 'string',
244+
requiresArg: true,
245+
demandOption: true,
246+
})
247+
},
248+
handler: async (argv: CLIArgs): Promise<void> => {
249+
return draw(await loadEnv(argv), argv)
250+
},
251+
})
252+
},
253+
handler: (argv: CLIArgs): void => {
254+
yargs.showHelp()
255+
},
256+
}

cli/commands/contracts/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { stakingCommand } from './staking'
1111
import { anyCommand } from './any'
1212

1313
import { CLIArgs } from '../../env'
14+
import { disputeManagerCommand } from './disputeManager'
1415

1516
export const contractsCommand = {
1617
command: 'contracts',
@@ -26,6 +27,7 @@ export const contractsCommand = {
2627
.command(gdaiCommand)
2728
.command(stakingCommand)
2829
.command(anyCommand)
30+
.command(disputeManagerCommand)
2931
},
3032
handler: (argv: CLIArgs): void => {
3133
yargs.showHelp()

0 commit comments

Comments
 (0)