Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/odd-pumpkins-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@boostxyz/sdk": patch
---

improve detection of variable criteria incentive types
128 changes: 127 additions & 1 deletion packages/sdk/src/Incentives/Incentive.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { zeroAddress } from 'viem';
import { pad, zeroAddress } from 'viem';
import { describe, expect, test } from 'vitest';
import { defaultOptions } from '@boostxyz/test/helpers';
import { StrategyType } from '../claiming';
Expand All @@ -7,9 +7,15 @@ import {
CGDAIncentive,
ERC20Incentive,
ERC20VariableIncentive,
ERC20VariableCriteriaIncentive,
ERC20VariableCriteriaIncentiveV2,
ERC20PeggedVariableCriteriaIncentive,
ERC20PeggedVariableCriteriaIncentiveV2,
ERC20PeggedIncentive,
incentiveFromAddress,
} from './Incentive';
import { PointsIncentive } from './PointsIncentive';
import { SignatureType, ValueType } from '../Actions/EventAction';

describe('Incentive', () => {
test('can automatically instantiate PointsIncentive given an address', async () => {
Expand All @@ -19,6 +25,7 @@ describe('Incentive', () => {
reward: 1n,
limit: 1n,
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
Expand All @@ -33,6 +40,7 @@ describe('Incentive', () => {
allowList: zeroAddress,
limit: 3n,
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
Expand All @@ -51,6 +59,7 @@ describe('Incentive', () => {
rewardDecay: 1n,
manager: zeroAddress,
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
Expand All @@ -68,6 +77,7 @@ describe('Incentive', () => {
limit: 10n,
manager: zeroAddress,
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
Expand All @@ -84,6 +94,7 @@ describe('Incentive', () => {
limit: 10n,
manager: zeroAddress,
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
Expand All @@ -92,4 +103,119 @@ describe('Incentive', () => {
),
).toBeInstanceOf(ERC20VariableIncentive);
});

test('can automatically instantiate ERC20PeggedIncentive given an address', async () => {
const incentive = new ERC20PeggedIncentive(defaultOptions, {
asset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
peg: zeroAddress,
limit: 1000000n,
reward: 100000n,
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
defaultOptions,
incentive.assertValidAddress(),
),
).toBeInstanceOf(ERC20PeggedIncentive);
});

test('can automatically instantiate ERC20VariableCriteriaIncentive (V1) given an address', async () => {
const incentive = new ERC20VariableCriteriaIncentive(defaultOptions, {
asset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
limit: 1000000n,
reward: 1000000n,
criteria: {
criteriaType: SignatureType.FUNC,
signature:
pad("0xa9059cbb"),
fieldIndex: 1,
targetContract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
},
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
defaultOptions,
incentive.assertValidAddress(),
),
).toBeInstanceOf(ERC20VariableCriteriaIncentive);
});

test('can automatically instantiate ERC20VariableCriteriaIncentiveV2 given an address', async () => {
const incentive = new ERC20VariableCriteriaIncentiveV2(defaultOptions, {
asset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
limit: 1000000n,
reward: 1000000n,
criteria: {
criteriaType: SignatureType.FUNC,
signature:
pad("0xa9059cbb"),
fieldIndex: 1,
targetContract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
valueType: ValueType.WAD,
},
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
defaultOptions,
incentive.assertValidAddress(),
),
).toBeInstanceOf(ERC20VariableCriteriaIncentiveV2);
});

test('can automatically instantiate ERC20PeggedVariableCriteriaIncentive (V1) given an address', async () => {
const incentive = new ERC20PeggedVariableCriteriaIncentive(defaultOptions, {
asset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
peg: zeroAddress,
limit: 1000000n,
reward: 1000000n,
maxReward: 1000000n,
criteria: {
criteriaType: SignatureType.FUNC,
signature:
pad("0xa9059cbb"),
fieldIndex: 1,
targetContract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
},
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
defaultOptions,
incentive.assertValidAddress(),
),
).toBeInstanceOf(ERC20PeggedVariableCriteriaIncentive);
});

test('can automatically instantiate ERC20PeggedVariableCriteriaIncentiveV2 given an address', async () => {
const incentive = new ERC20PeggedVariableCriteriaIncentiveV2(defaultOptions, {
asset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
peg: zeroAddress,
limit: 1000000n,
reward: 1000000n,
maxReward: 1000000n,
criteria: {
criteriaType: SignatureType.FUNC,
signature:
pad("0xa9059cbb"),
fieldIndex: 1,
targetContract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
valueType: ValueType.WAD,
},
});
// @ts-expect-error private method
await incentive.deploy();
expect(
await incentiveFromAddress(
defaultOptions,
incentive.assertValidAddress(),
),
).toBeInstanceOf(ERC20PeggedVariableCriteriaIncentiveV2);
});
});
37 changes: 34 additions & 3 deletions packages/sdk/src/Incentives/Incentive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { aIncentiveAbi } from '@boostxyz/evm';
import {
aIncentiveAbi,
readErc20PeggedVariableCriteriaIncentiveV2GetIncentiveCriteria,
readErc20VariableCriteriaIncentiveV2GetIncentiveCriteria,
} from '@boostxyz/evm';
import {
AAllowListIncentive,
ACGDAIncentive,
Expand All @@ -9,7 +13,6 @@ import {
AERC20VariableCriteriaIncentive,
AERC20VariableCriteriaIncentiveV2,
AERC20VariableIncentive,
// AERC20VariableCriteriaIncentive
APointsIncentive,
} from '@boostxyz/evm/deploys/componentInterfaces.json';
import { readContract } from '@wagmi/core';
Expand Down Expand Up @@ -79,8 +82,8 @@ export const IncentiveByComponentInterface = {
// [AERC1155Incentive as Hex]: ERC1155Incentive,
[ACGDAIncentive as Hex]: CGDAIncentive,
[AERC20VariableIncentive as Hex]: ERC20VariableIncentive,
[AERC20VariableCriteriaIncentiveV2 as Hex]: ERC20VariableCriteriaIncentiveV2,
[AERC20VariableCriteriaIncentive as Hex]: ERC20VariableCriteriaIncentive,
[AERC20VariableCriteriaIncentiveV2 as Hex]: ERC20VariableCriteriaIncentiveV2,
};

/**
Expand Down Expand Up @@ -111,5 +114,33 @@ export async function incentiveFromAddress(
interfaceId as Hex,
);
}

/*
* Because the interfaceId is identical for V1 and V2 variable criteria incentive types,
* a V2-specific read is performed. This read is expected to succeed only for V2 contracts,
* allowing for selection of the correct SDK constructor.
*/
if (interfaceId === AERC20VariableCriteriaIncentive) {
try {
await readErc20VariableCriteriaIncentiveV2GetIncentiveCriteria(
options.config,
{ address, ...params },
);
return new ERC20VariableCriteriaIncentiveV2(options, address);
} catch {
return new ERC20VariableCriteriaIncentive(options, address);
}
} else if (interfaceId === AERC20PeggedVariableCriteriaIncentive) {
try {
await readErc20PeggedVariableCriteriaIncentiveV2GetIncentiveCriteria(
options.config,
{ address, ...params },
);
return new ERC20PeggedVariableCriteriaIncentiveV2(options, address);
} catch {
return new ERC20PeggedVariableCriteriaIncentive(options, address);
}
}

return new Ctor(options, address);
}
Loading