|
| 1 | +import { |
| 2 | + describe, |
| 3 | + test, |
| 4 | + beforeEach, |
| 5 | + clearStore, |
| 6 | + assert, |
| 7 | + newTypedMockEvent, |
| 8 | +} from "matchstick-as" |
| 9 | +import { Address, BigInt, Bytes, ethereum } from "@graphprotocol/graph-ts" |
| 10 | +import { |
| 11 | + DelegationFeeCutSet, |
| 12 | + HorizonStakeDeposited, |
| 13 | + ProvisionCreated, |
| 14 | +} from "../generated/HorizonStaking/HorizonStaking" |
| 15 | +import { handleDelegationFeeCutSet } from "../src/handlers/feeCut" |
| 16 | +import { handleHorizonStakeDeposited } from "../src/handlers/staking" |
| 17 | +import { handleProvisionCreated } from "../src/handlers/provision" |
| 18 | +import { getProvisionFeeCutId } from "../src/entities/provisionFeeCut" |
| 19 | +import { getProvisionId } from "../src/entities/provision" |
| 20 | + |
| 21 | +// Test addresses |
| 22 | +const SP_ADDRESS = Address.fromString("0x1234567890123456789012345678901234567890") |
| 23 | +const VERIFIER_ADDRESS = Address.fromString("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd") |
| 24 | + |
| 25 | +// Payment types (from IGraphPayments.PaymentTypes enum) |
| 26 | +const PAYMENT_TYPE_QUERY_FEE = 0 |
| 27 | +const PAYMENT_TYPE_INDEXING_FEE = 1 |
| 28 | +const PAYMENT_TYPE_INDEXING_REWARD = 2 |
| 29 | + |
| 30 | +// Helper to create stake deposit |
| 31 | +function createStakeDepositedEvent(serviceProvider: Address, tokens: BigInt): HorizonStakeDeposited { |
| 32 | + let event = newTypedMockEvent<HorizonStakeDeposited>() |
| 33 | + event.parameters = new Array() |
| 34 | + event.parameters.push(new ethereum.EventParam("serviceProvider", ethereum.Value.fromAddress(serviceProvider))) |
| 35 | + event.parameters.push(new ethereum.EventParam("tokens", ethereum.Value.fromUnsignedBigInt(tokens))) |
| 36 | + event.block.number = BigInt.fromI32(100) |
| 37 | + event.block.timestamp = BigInt.fromI32(1000) |
| 38 | + return event |
| 39 | +} |
| 40 | + |
| 41 | +// Helper to create provision |
| 42 | +function createProvisionCreatedEvent( |
| 43 | + serviceProvider: Address, |
| 44 | + verifier: Address, |
| 45 | + tokens: BigInt |
| 46 | +): ProvisionCreated { |
| 47 | + let event = newTypedMockEvent<ProvisionCreated>() |
| 48 | + event.parameters = new Array() |
| 49 | + event.parameters.push(new ethereum.EventParam("serviceProvider", ethereum.Value.fromAddress(serviceProvider))) |
| 50 | + event.parameters.push(new ethereum.EventParam("verifier", ethereum.Value.fromAddress(verifier))) |
| 51 | + event.parameters.push(new ethereum.EventParam("tokens", ethereum.Value.fromUnsignedBigInt(tokens))) |
| 52 | + event.parameters.push(new ethereum.EventParam("maxVerifierCut", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(100000)))) |
| 53 | + event.parameters.push(new ethereum.EventParam("thawingPeriod", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(2592000)))) |
| 54 | + event.block.number = BigInt.fromI32(200) |
| 55 | + event.block.timestamp = BigInt.fromI32(2000) |
| 56 | + return event |
| 57 | +} |
| 58 | + |
| 59 | +// Helper to create DelegationFeeCutSet event |
| 60 | +function createDelegationFeeCutSetEvent( |
| 61 | + serviceProvider: Address, |
| 62 | + verifier: Address, |
| 63 | + paymentType: i32, |
| 64 | + feeCut: BigInt |
| 65 | +): DelegationFeeCutSet { |
| 66 | + let event = newTypedMockEvent<DelegationFeeCutSet>() |
| 67 | + event.parameters = new Array() |
| 68 | + event.parameters.push(new ethereum.EventParam("serviceProvider", ethereum.Value.fromAddress(serviceProvider))) |
| 69 | + event.parameters.push(new ethereum.EventParam("verifier", ethereum.Value.fromAddress(verifier))) |
| 70 | + event.parameters.push(new ethereum.EventParam("paymentType", ethereum.Value.fromI32(paymentType))) |
| 71 | + event.parameters.push(new ethereum.EventParam("feeCut", ethereum.Value.fromUnsignedBigInt(feeCut))) |
| 72 | + event.block.number = BigInt.fromI32(300) |
| 73 | + event.block.timestamp = BigInt.fromI32(3000) |
| 74 | + return event |
| 75 | +} |
| 76 | + |
| 77 | +function setupServiceProviderAndProvision(): void { |
| 78 | + // Deposit stake |
| 79 | + let stakeTokens = BigInt.fromString("10000000000000000000000") // 10000 GRT |
| 80 | + let depositEvent = createStakeDepositedEvent(SP_ADDRESS, stakeTokens) |
| 81 | + handleHorizonStakeDeposited(depositEvent) |
| 82 | + |
| 83 | + // Create provision |
| 84 | + let provisionTokens = BigInt.fromString("5000000000000000000000") // 5000 GRT |
| 85 | + let provisionEvent = createProvisionCreatedEvent(SP_ADDRESS, VERIFIER_ADDRESS, provisionTokens) |
| 86 | + handleProvisionCreated(provisionEvent) |
| 87 | +} |
| 88 | + |
| 89 | +function getFeeCutIdString(sp: Address, verifier: Address, paymentType: i32): string { |
| 90 | + return getProvisionFeeCutId( |
| 91 | + Bytes.fromHexString(sp.toHexString()), |
| 92 | + Bytes.fromHexString(verifier.toHexString()), |
| 93 | + paymentType |
| 94 | + ).toHexString() |
| 95 | +} |
| 96 | + |
| 97 | +function getProvisionIdString(sp: Address, verifier: Address): string { |
| 98 | + return getProvisionId( |
| 99 | + Bytes.fromHexString(sp.toHexString()), |
| 100 | + Bytes.fromHexString(verifier.toHexString()) |
| 101 | + ).toHexString() |
| 102 | +} |
| 103 | + |
| 104 | +describe("DelegationFeeCutSet", () => { |
| 105 | + beforeEach(() => { |
| 106 | + clearStore() |
| 107 | + }) |
| 108 | + |
| 109 | + test("creates ProvisionFeeCut entity for query fee type", () => { |
| 110 | + setupServiceProviderAndProvision() |
| 111 | + |
| 112 | + let feeCut = BigInt.fromI32(100000) // 10% in PPM |
| 113 | + let event = createDelegationFeeCutSetEvent( |
| 114 | + SP_ADDRESS, |
| 115 | + VERIFIER_ADDRESS, |
| 116 | + PAYMENT_TYPE_QUERY_FEE, |
| 117 | + feeCut |
| 118 | + ) |
| 119 | + handleDelegationFeeCutSet(event) |
| 120 | + |
| 121 | + let entityId = getFeeCutIdString(SP_ADDRESS, VERIFIER_ADDRESS, PAYMENT_TYPE_QUERY_FEE) |
| 122 | + let provisionId = getProvisionIdString(SP_ADDRESS, VERIFIER_ADDRESS) |
| 123 | + |
| 124 | + assert.entityCount("ProvisionFeeCut", 1) |
| 125 | + assert.fieldEquals("ProvisionFeeCut", entityId, "provision", provisionId) |
| 126 | + assert.fieldEquals("ProvisionFeeCut", entityId, "paymentType", PAYMENT_TYPE_QUERY_FEE.toString()) |
| 127 | + assert.fieldEquals("ProvisionFeeCut", entityId, "feeCut", feeCut.toString()) |
| 128 | + assert.fieldEquals("ProvisionFeeCut", entityId, "updatedAtBlock", "300") |
| 129 | + assert.fieldEquals("ProvisionFeeCut", entityId, "updatedAt", "3000") |
| 130 | + }) |
| 131 | + |
| 132 | + test("creates separate entities for different payment types", () => { |
| 133 | + setupServiceProviderAndProvision() |
| 134 | + |
| 135 | + // Set query fee cut |
| 136 | + let queryFeeCut = BigInt.fromI32(100000) // 10% |
| 137 | + let queryEvent = createDelegationFeeCutSetEvent( |
| 138 | + SP_ADDRESS, |
| 139 | + VERIFIER_ADDRESS, |
| 140 | + PAYMENT_TYPE_QUERY_FEE, |
| 141 | + queryFeeCut |
| 142 | + ) |
| 143 | + handleDelegationFeeCutSet(queryEvent) |
| 144 | + |
| 145 | + // Set indexing fee cut |
| 146 | + let indexingFeeCut = BigInt.fromI32(200000) // 20% |
| 147 | + let indexingEvent = createDelegationFeeCutSetEvent( |
| 148 | + SP_ADDRESS, |
| 149 | + VERIFIER_ADDRESS, |
| 150 | + PAYMENT_TYPE_INDEXING_FEE, |
| 151 | + indexingFeeCut |
| 152 | + ) |
| 153 | + indexingEvent.block.number = BigInt.fromI32(301) |
| 154 | + indexingEvent.block.timestamp = BigInt.fromI32(3100) |
| 155 | + handleDelegationFeeCutSet(indexingEvent) |
| 156 | + |
| 157 | + // Set indexing reward cut |
| 158 | + let rewardFeeCut = BigInt.fromI32(50000) // 5% |
| 159 | + let rewardEvent = createDelegationFeeCutSetEvent( |
| 160 | + SP_ADDRESS, |
| 161 | + VERIFIER_ADDRESS, |
| 162 | + PAYMENT_TYPE_INDEXING_REWARD, |
| 163 | + rewardFeeCut |
| 164 | + ) |
| 165 | + rewardEvent.block.number = BigInt.fromI32(302) |
| 166 | + rewardEvent.block.timestamp = BigInt.fromI32(3200) |
| 167 | + handleDelegationFeeCutSet(rewardEvent) |
| 168 | + |
| 169 | + assert.entityCount("ProvisionFeeCut", 3) |
| 170 | + |
| 171 | + let queryId = getFeeCutIdString(SP_ADDRESS, VERIFIER_ADDRESS, PAYMENT_TYPE_QUERY_FEE) |
| 172 | + let indexingId = getFeeCutIdString(SP_ADDRESS, VERIFIER_ADDRESS, PAYMENT_TYPE_INDEXING_FEE) |
| 173 | + let rewardId = getFeeCutIdString(SP_ADDRESS, VERIFIER_ADDRESS, PAYMENT_TYPE_INDEXING_REWARD) |
| 174 | + |
| 175 | + assert.fieldEquals("ProvisionFeeCut", queryId, "paymentType", PAYMENT_TYPE_QUERY_FEE.toString()) |
| 176 | + assert.fieldEquals("ProvisionFeeCut", queryId, "feeCut", queryFeeCut.toString()) |
| 177 | + |
| 178 | + assert.fieldEquals("ProvisionFeeCut", indexingId, "paymentType", PAYMENT_TYPE_INDEXING_FEE.toString()) |
| 179 | + assert.fieldEquals("ProvisionFeeCut", indexingId, "feeCut", indexingFeeCut.toString()) |
| 180 | + |
| 181 | + assert.fieldEquals("ProvisionFeeCut", rewardId, "paymentType", PAYMENT_TYPE_INDEXING_REWARD.toString()) |
| 182 | + assert.fieldEquals("ProvisionFeeCut", rewardId, "feeCut", rewardFeeCut.toString()) |
| 183 | + }) |
| 184 | + |
| 185 | + test("updates existing fee cut for same payment type", () => { |
| 186 | + setupServiceProviderAndProvision() |
| 187 | + |
| 188 | + // Initial fee cut |
| 189 | + let initialFeeCut = BigInt.fromI32(100000) // 10% |
| 190 | + let event1 = createDelegationFeeCutSetEvent( |
| 191 | + SP_ADDRESS, |
| 192 | + VERIFIER_ADDRESS, |
| 193 | + PAYMENT_TYPE_QUERY_FEE, |
| 194 | + initialFeeCut |
| 195 | + ) |
| 196 | + handleDelegationFeeCutSet(event1) |
| 197 | + |
| 198 | + let entityId = getFeeCutIdString(SP_ADDRESS, VERIFIER_ADDRESS, PAYMENT_TYPE_QUERY_FEE) |
| 199 | + assert.fieldEquals("ProvisionFeeCut", entityId, "feeCut", initialFeeCut.toString()) |
| 200 | + assert.fieldEquals("ProvisionFeeCut", entityId, "updatedAtBlock", "300") |
| 201 | + |
| 202 | + // Update fee cut |
| 203 | + let updatedFeeCut = BigInt.fromI32(150000) // 15% |
| 204 | + let event2 = createDelegationFeeCutSetEvent( |
| 205 | + SP_ADDRESS, |
| 206 | + VERIFIER_ADDRESS, |
| 207 | + PAYMENT_TYPE_QUERY_FEE, |
| 208 | + updatedFeeCut |
| 209 | + ) |
| 210 | + event2.block.number = BigInt.fromI32(400) |
| 211 | + event2.block.timestamp = BigInt.fromI32(4000) |
| 212 | + handleDelegationFeeCutSet(event2) |
| 213 | + |
| 214 | + // Should still be 1 entity, but updated |
| 215 | + assert.entityCount("ProvisionFeeCut", 1) |
| 216 | + assert.fieldEquals("ProvisionFeeCut", entityId, "feeCut", updatedFeeCut.toString()) |
| 217 | + assert.fieldEquals("ProvisionFeeCut", entityId, "updatedAtBlock", "400") |
| 218 | + assert.fieldEquals("ProvisionFeeCut", entityId, "updatedAt", "4000") |
| 219 | + }) |
| 220 | + |
| 221 | + test("handles fee cut set to zero", () => { |
| 222 | + setupServiceProviderAndProvision() |
| 223 | + |
| 224 | + // Set fee cut to zero (100% goes to delegators) |
| 225 | + let feeCut = BigInt.fromI32(0) |
| 226 | + let event = createDelegationFeeCutSetEvent( |
| 227 | + SP_ADDRESS, |
| 228 | + VERIFIER_ADDRESS, |
| 229 | + PAYMENT_TYPE_QUERY_FEE, |
| 230 | + feeCut |
| 231 | + ) |
| 232 | + handleDelegationFeeCutSet(event) |
| 233 | + |
| 234 | + let entityId = getFeeCutIdString(SP_ADDRESS, VERIFIER_ADDRESS, PAYMENT_TYPE_QUERY_FEE) |
| 235 | + assert.fieldEquals("ProvisionFeeCut", entityId, "feeCut", "0") |
| 236 | + }) |
| 237 | + |
| 238 | + test("handles max fee cut (100%)", () => { |
| 239 | + setupServiceProviderAndProvision() |
| 240 | + |
| 241 | + // Set fee cut to 100% (1000000 PPM) |
| 242 | + let feeCut = BigInt.fromI32(1000000) // 100% in PPM |
| 243 | + let event = createDelegationFeeCutSetEvent( |
| 244 | + SP_ADDRESS, |
| 245 | + VERIFIER_ADDRESS, |
| 246 | + PAYMENT_TYPE_QUERY_FEE, |
| 247 | + feeCut |
| 248 | + ) |
| 249 | + handleDelegationFeeCutSet(event) |
| 250 | + |
| 251 | + let entityId = getFeeCutIdString(SP_ADDRESS, VERIFIER_ADDRESS, PAYMENT_TYPE_QUERY_FEE) |
| 252 | + assert.fieldEquals("ProvisionFeeCut", entityId, "feeCut", feeCut.toString()) |
| 253 | + }) |
| 254 | +}) |
0 commit comments