diff --git a/.changeset/odd-pumpkins-notice.md b/.changeset/odd-pumpkins-notice.md new file mode 100644 index 000000000..73057c104 --- /dev/null +++ b/.changeset/odd-pumpkins-notice.md @@ -0,0 +1,5 @@ +--- +"@boostxyz/sdk": patch +--- + +improve detection of variable criteria incentive types diff --git a/packages/sdk/src/Incentives/Incentive.test.ts b/packages/sdk/src/Incentives/Incentive.test.ts index 0df3935e0..2c032b9e7 100644 --- a/packages/sdk/src/Incentives/Incentive.test.ts +++ b/packages/sdk/src/Incentives/Incentive.test.ts @@ -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'; @@ -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 () => { @@ -19,6 +25,7 @@ describe('Incentive', () => { reward: 1n, limit: 1n, }); + // @ts-expect-error private method await incentive.deploy(); expect( await incentiveFromAddress( @@ -33,6 +40,7 @@ describe('Incentive', () => { allowList: zeroAddress, limit: 3n, }); + // @ts-expect-error private method await incentive.deploy(); expect( await incentiveFromAddress( @@ -51,6 +59,7 @@ describe('Incentive', () => { rewardDecay: 1n, manager: zeroAddress, }); + // @ts-expect-error private method await incentive.deploy(); expect( await incentiveFromAddress( @@ -68,6 +77,7 @@ describe('Incentive', () => { limit: 10n, manager: zeroAddress, }); + // @ts-expect-error private method await incentive.deploy(); expect( await incentiveFromAddress( @@ -84,6 +94,7 @@ describe('Incentive', () => { limit: 10n, manager: zeroAddress, }); + // @ts-expect-error private method await incentive.deploy(); expect( await incentiveFromAddress( @@ -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); + }); }); diff --git a/packages/sdk/src/Incentives/Incentive.ts b/packages/sdk/src/Incentives/Incentive.ts index a63477710..e9e04b25c 100644 --- a/packages/sdk/src/Incentives/Incentive.ts +++ b/packages/sdk/src/Incentives/Incentive.ts @@ -1,4 +1,8 @@ -import { aIncentiveAbi } from '@boostxyz/evm'; +import { + aIncentiveAbi, + readErc20PeggedVariableCriteriaIncentiveV2GetIncentiveCriteria, + readErc20VariableCriteriaIncentiveV2GetIncentiveCriteria, +} from '@boostxyz/evm'; import { AAllowListIncentive, ACGDAIncentive, @@ -9,7 +13,6 @@ import { AERC20VariableCriteriaIncentive, AERC20VariableCriteriaIncentiveV2, AERC20VariableIncentive, - // AERC20VariableCriteriaIncentive APointsIncentive, } from '@boostxyz/evm/deploys/componentInterfaces.json'; import { readContract } from '@wagmi/core'; @@ -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, }; /** @@ -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); }