Skip to content

Commit 87e376a

Browse files
committed
feat: added HorizonDisputeManager
1 parent cc057f9 commit 87e376a

11 files changed

+1717
-4
lines changed

abis/HorizonDisputeManager.json

Lines changed: 1454 additions & 0 deletions
Large diffs are not rendered by default.

config/addresses.template.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export class Addresses {
44
graphToken: string
55
epochManager: string
66
disputeManager: string
7+
horizonDisputeManager: string
78
staking: string
89
stakingExtension: string
910
curation: string
@@ -31,6 +32,7 @@ export let addresses: Addresses = {
3132
graphToken: '{{graphToken}}',
3233
epochManager: '{{epochManager}}',
3334
disputeManager: '{{disputeManager}}',
35+
horizonDisputeManager: '{{horizonDisputeManager}}',
3436
staking: '{{staking}}',
3537
stakingExtension: '{{stakingExtension}}',
3638
curation: '{{curation}}',

config/arbitrumSepoliaAddressScript.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ export let addresses: Addresses = {
1313
graphToken: '{{arbsep.L2GraphToken.address}}',
1414
epochManager: '{{arbsep.EpochManager.address}}',
1515
disputeManager: '{{arbsep.DisputeManager.address}}',
16-
staking: '{{arbsep.HorizonStaking.address}}',
17-
stakingExtension: '{{arbsep.HorizonStaking.address}}',
16+
horizonDisputeManager: '{{arbsep.HorizonDisputeManager.address}}',
17+
staking: '{{arbsep.L2Staking.address}}',
18+
stakingExtension: '{{arbsep.StakingExtension.address}}',
1819
curation: '{{arbsep.L2Curation.address}}',
1920
rewardsManager: '{{arbsep.RewardsManager.address}}',
2021
serviceRegistry: '{{arbsep.ServiceRegistry.address}}',
@@ -54,6 +55,9 @@ const main = (): void => {
5455
if(output.graphPayments == '') {
5556
output.graphPayments = '0x0000000000000000000000000000000000000000' // to avoid crashes due to bad config
5657
}
58+
if(output.horizonDisputeManager == '') {
59+
output.horizonDisputeManager = '0x0000000000000000000000000000000000000000' // to avoid crashes due to bad config
60+
}
5761
fs.writeFileSync(__dirname + '/generatedAddresses.json', JSON.stringify(output, null, 2))
5862
} catch (e) {
5963
console.log(`Error saving artifacts: ${e.message}`)

config/localNetworkAddressScript.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export let addresses: Addresses = {
1414
graphToken: '{{localnetwork.L2GraphToken.address}}',
1515
epochManager: '{{localnetwork.EpochManager.address}}',
1616
disputeManager: '{{localnetwork.DisputeManager.address}}',
17+
horizonDisputeManager: '{{localnetwork.HorizonDisputeManager.address}}',
1718
staking: '{{localnetwork.HorizonStaking.address}}',
1819
stakingExtension: '{{localnetwork.HorizonStaking.address}}',
1920
curation: '{{localnetwork.L2Curation.address}}',
@@ -55,6 +56,9 @@ const main = (): void => {
5556
if(output.graphPayments == '') {
5657
output.graphPayments = '0x0000000000000000000000000000000000000000' // to avoid crashes due to bad config
5758
}
59+
if(output.horizonDisputeManager == '') {
60+
output.horizonDisputeManager = '0x0000000000000000000000000000000000000000' // to avoid crashes due to bad config
61+
}
5862
fs.writeFileSync(__dirname + '/generatedAddresses.json', JSON.stringify(output, null, 2))
5963
} catch (e) {
6064
console.log(`Error saving artifacts: ${e.message}`)

config/mainnetArbitrumAddressScript.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export let addresses: Addresses = {
1313
graphToken: '{{arbitrum.L2GraphToken.address}}',
1414
epochManager: '{{arbitrum.EpochManager.address}}',
1515
disputeManager: '{{arbitrum.DisputeManager.address}}',
16+
horizonDisputeManager: '{{arbitrum.HorizonDisputeManager.address}}',
1617
staking: '{{arbitrum.L2Staking.address}}',
1718
stakingExtension: '{{arbitrum.StakingExtension.address}}',
1819
curation: '{{arbitrum.L2Curation.address}}',
@@ -55,6 +56,9 @@ const main = (): void => {
5556
if(output.graphPayments == '') {
5657
output.graphPayments = '0x0000000000000000000000000000000000000000' // to avoid crashes due to bad config
5758
}
59+
if(output.horizonDisputeManager == '') {
60+
output.horizonDisputeManager = '0x0000000000000000000000000000000000000000' // to avoid crashes due to bad config
61+
}
5862
fs.writeFileSync(__dirname + '/generatedAddresses.json', JSON.stringify(output, null, 2))
5963
} catch (e) {
6064
console.log(`Error saving artifacts: ${e.message}`)

config/testAddressesL1.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export let addresses: Addresses = {
1313
graphToken: '0x0000000000000000000000000000000000000000',
1414
epochManager: '0x0000000000000000000000000000000000000000',
1515
disputeManager: '0x0000000000000000000000000000000000000000',
16+
horizonDisputeManager: '0x0000000000000000000000000000000000000000',
1617
staking: '0x0000000000000000000000000000000000000000',
1718
stakingExtension: '0x0000000000000000000000000000000000000000',
1819
curation: '0x0000000000000000000000000000000000000000',

config/testAddressesL2.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export let addresses: Addresses = {
1313
graphToken: '0x0000000000000000000000000000000000000000',
1414
epochManager: '0x0000000000000000000000000000000000000000',
1515
disputeManager: '0x0000000000000000000000000000000000000000',
16+
horizonDisputeManager: '0x0000000000000000000000000000000000000000',
1617
staking: '0x0000000000000000000000000000000000000000',
1718
stakingExtension: '0x0000000000000000000000000000000000000000',
1819
curation: '0x0000000000000000000000000000000000000000',

schema.graphql

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,12 @@ type GraphNetwork @entity {
219219
slashingPercentage: Int!
220220
"Minimum deposit to create a dispute"
221221
minimumDisputeDeposit: BigInt!
222-
"Reward to Fisherman on successful disputes. In parts per million"
222+
"[LEGACY] Reward to Fisherman on successful disputes. In parts per million"
223223
fishermanRewardPercentage: Int!
224+
"[HORIZON]Reward to Fisherman on successful disputes. In parts per million"
225+
fishermanRewardCut: Int!
226+
"Maximum Penalty to Indexer on successful disputes for indexing disputes. In parts per million"
227+
maxSlashingCut: Int!
224228

225229
# Bridge totals (Only available on L1 networks)
226230
"Total amount of GRT deposited to the L1 gateway. Note that the actual amount claimed in L2 might be lower due to tickets not redeemed."

src/mappings/helpers/helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,8 @@ export function createOrLoadGraphNetwork(
773773
graphNetwork.indexingSlashingPercentage = 0
774774
graphNetwork.slashingPercentage = 0 // keeping it for backwards compatibility for now
775775
graphNetwork.minimumDisputeDeposit = BigInt.fromI32(0)
776-
graphNetwork.fishermanRewardPercentage = 0
776+
graphNetwork.fishermanRewardCut = 0
777+
graphNetwork.maxSlashingCut = 0
777778

778779
graphNetwork.totalGRTDeposited = BigInt.fromI32(0)
779780
graphNetwork.totalGRTDepositedConfirmed = BigInt.fromI32(0)
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { Address, BigDecimal, BigInt, ByteArray, log } from '@graphprotocol/graph-ts'
2+
import { Allocation, Dispute, Attestation } from '../types/schema'
3+
import {
4+
QueryDisputeCreated,
5+
IndexingDisputeCreated,
6+
DisputeRejected,
7+
DisputeAccepted,
8+
DisputeDrawn,
9+
DisputeLinked,
10+
ArbitratorSet,
11+
FishermanRewardCutSet,
12+
MaxSlashingCutSet,
13+
DisputeCancelled,
14+
} from '../types/HorizonDisputeManager/HorizonDisputeManager'
15+
import { createOrLoadGraphNetwork } from './helpers/helpers'
16+
17+
// Define constants locally
18+
const BIGINT_ZERO = BigInt.fromI32(0)
19+
const BIGDECIMAL_ZERO = BigDecimal.fromString('0')
20+
const PPM_DIVISOR = BigDecimal.fromString('1000000')
21+
const ADDRESS_ZERO = Address.fromString('0x0000000000000000000000000000000000000000')
22+
23+
// Dispute Status constants
24+
const STATUS_UNDECIDED = 'Undecided'
25+
const STATUS_ACCEPTED = 'Accepted'
26+
const STATUS_REJECTED = 'Rejected'
27+
const STATUS_DRAWN = 'Draw'
28+
const STATUS_CANCELLED = 'Cancelled'
29+
30+
// Dispute Type constants
31+
const TYPE_SINGLE_QUERY = 'SingleQuery'
32+
const TYPE_INDEXING = 'Indexing'
33+
const TYPE_CONFLICTING = 'Conflicting'
34+
35+
// This handles Single query and Conflicting disputes
36+
export function handleQueryDisputeCreated(event: QueryDisputeCreated): void {
37+
let id = event.params.disputeId.toHexString()
38+
let dispute = new Dispute(id)
39+
dispute.subgraphDeployment = event.params.subgraphDeploymentId.toHexString()
40+
dispute.fisherman = event.params.fisherman.toHexString()
41+
dispute.deposit = event.params.tokens
42+
dispute.createdAt = event.block.timestamp.toI32()
43+
dispute.status = STATUS_UNDECIDED
44+
dispute.tokensSlashed = BIGDECIMAL_ZERO
45+
dispute.tokensRewarded = BIGINT_ZERO
46+
dispute.tokensBurned = BIGDECIMAL_ZERO
47+
dispute.closedAt = 0
48+
dispute.type = TYPE_SINGLE_QUERY // It starts off as single query, but if it gets linked, it is updated to Conflicting. The events emitted are QueryDisputeCreated 1, QueryDisputeCreated 2, DisputeLinked
49+
50+
dispute.indexer = event.params.indexer.toHexString()
51+
52+
let attestationData = event.params.attestation.toHexString()
53+
let request = '0x'.concat(attestationData.slice(2, 66))
54+
let response = '0x'.concat(attestationData.slice(66, 130))
55+
let attestation = new Attestation(request.concat('-').concat(response))
56+
let v = attestationData.slice(194, 196)
57+
let r = attestationData.slice(196, 260)
58+
let s = attestationData.slice(260, 324)
59+
attestation.responseCID = response
60+
attestation.requestCID = request
61+
attestation.subgraphDeployment = dispute.subgraphDeployment
62+
attestation.v = ByteArray.fromHexString(v).toI32()
63+
attestation.r = '0x'.concat(r)
64+
attestation.s = '0x'.concat(s)
65+
attestation.save()
66+
67+
dispute.attestation = attestation.id
68+
dispute.save()
69+
}
70+
71+
// Just handles indexing disputes
72+
export function handleIndexingDisputeCreated(event: IndexingDisputeCreated): void {
73+
let allocation = Allocation.load(event.params.allocationId.toHexString())!
74+
let id = event.params.disputeId.toHexString()
75+
let dispute = new Dispute(id)
76+
dispute.subgraphDeployment = allocation.subgraphDeployment
77+
dispute.fisherman = event.params.fisherman.toHexString()
78+
dispute.deposit = event.params.tokens
79+
dispute.createdAt = event.block.timestamp.toI32()
80+
dispute.status = STATUS_UNDECIDED
81+
dispute.tokensSlashed = BigDecimal.fromString('0')
82+
dispute.tokensBurned = BigDecimal.fromString('0')
83+
dispute.tokensRewarded = BigInt.fromI32(0)
84+
dispute.type = TYPE_INDEXING
85+
dispute.indexer = event.params.indexer.toHexString()
86+
dispute.allocation = allocation.id
87+
dispute.closedAt = 0
88+
dispute.save()
89+
}
90+
91+
export function handleDisputeAccepted(event: DisputeAccepted): void {
92+
let id = event.params.disputeId.toHexString()
93+
let dispute = Dispute.load(id)!
94+
dispute.status = STATUS_ACCEPTED
95+
dispute.tokensRewarded = event.params.tokens.minus(dispute.deposit) // See event, it adds them
96+
dispute.closedAt = event.block.timestamp.toI32()
97+
98+
let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address)
99+
let fishermanRewardPercentage = graphNetwork.fishermanRewardCut
100+
101+
// The fisherman reward is a function of the total slashed tokens. Therefore
102+
// if fishermanReward is 10%, slashed reward should be 9x that --> (1,000,000 / 100,000) - 1 = 9
103+
// It must be done like this since the event only emits limited information
104+
// Note - there is an edge case bug here, where if fishermanReward% = 0, we can't get
105+
// the tokensSlashed. That is okay for now. There should always be a fisherman reward %
106+
let slashedRewardRatio =
107+
fishermanRewardPercentage == 0
108+
? BigDecimal.fromString('0')
109+
: BigDecimal.fromString('1000000')
110+
.div(BigDecimal.fromString(fishermanRewardPercentage.toString()))
111+
.minus(BigDecimal.fromString('1'))
112+
dispute.tokensBurned = dispute.tokensRewarded.toBigDecimal().times(slashedRewardRatio)
113+
dispute.tokensSlashed = dispute.tokensRewarded.toBigDecimal().plus(dispute.tokensBurned)
114+
dispute.save()
115+
116+
if (dispute.linkedDispute != null) {
117+
let rejectedDispute = Dispute.load(dispute.linkedDispute!)!
118+
rejectedDispute.status = STATUS_REJECTED
119+
rejectedDispute.closedAt = event.block.timestamp.toI32()
120+
rejectedDispute.save()
121+
}
122+
}
123+
124+
// Note - it is impossible to call reject on a conflicting dispute, it is either draw or accept
125+
// This is because if you accept 1 in a conflict, the other is rejected
126+
export function handleDisputeRejected(event: DisputeRejected): void {
127+
let id = event.params.disputeId.toHexString()
128+
let dispute = Dispute.load(id)!
129+
dispute.status = STATUS_REJECTED
130+
dispute.closedAt = event.block.timestamp.toI32()
131+
dispute.save()
132+
}
133+
134+
export function handleDisputeDrawn(event: DisputeDrawn): void {
135+
let id = event.params.disputeId.toHexString()
136+
let dispute = Dispute.load(id)!
137+
dispute.status = STATUS_DRAWN
138+
dispute.closedAt = event.block.timestamp.toI32()
139+
dispute.save()
140+
141+
if (dispute.linkedDispute != null) {
142+
let linkedDispute = Dispute.load(dispute.linkedDispute!)!
143+
linkedDispute.status = STATUS_DRAWN
144+
linkedDispute.closedAt = event.block.timestamp.toI32()
145+
linkedDispute.save()
146+
}
147+
}
148+
149+
export function handleDisputeLinked(event: DisputeLinked): void {
150+
let id1 = event.params.disputeId1.toHexString()
151+
let id2 = event.params.disputeId2.toHexString()
152+
let dispute1 = Dispute.load(id1)!
153+
let dispute2 = Dispute.load(id2)!
154+
155+
dispute1.linkedDispute = id2
156+
dispute1.type = TYPE_CONFLICTING
157+
dispute1.save()
158+
159+
dispute2.linkedDispute = id1
160+
dispute2.type = TYPE_CONFLICTING
161+
dispute2.save()
162+
}
163+
164+
165+
// Handles Horizon DisputeCancelled events
166+
export function handleDisputeCancelled(event: DisputeCancelled): void {
167+
let disputeId = event.params.disputeId.toHexString()
168+
let dispute = Dispute.load(disputeId)!
169+
170+
dispute.status = STATUS_CANCELLED
171+
dispute.closedAt = event.block.timestamp.toI32()
172+
dispute.save()
173+
}
174+
175+
// Handles ArbitratorSet events
176+
export function handleArbitratorSet(event: ArbitratorSet): void {
177+
let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address)
178+
graphNetwork.arbitrator = event.params.arbitrator
179+
graphNetwork.save()
180+
}
181+
182+
// Handles FishermanRewardCutSet events
183+
export function handleFishermanRewardCutSet(event: FishermanRewardCutSet): void {
184+
let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address)
185+
graphNetwork.fishermanRewardCut = event.params.fishermanRewardCut.toI32()
186+
graphNetwork.save()
187+
}
188+
189+
// Handles MaxSlashingCutSet events
190+
export function handleMaxSlashingCutSet(event: MaxSlashingCutSet): void {
191+
let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address)
192+
graphNetwork.maxSlashingCut = event.params.maxSlashingCut.toI32()
193+
graphNetwork.save()
194+
}
195+

0 commit comments

Comments
 (0)