Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BOOST-4684] test: add agora vote example #160

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions examples/agora-vote/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_ALCHEMY_API_KEY=
15 changes: 15 additions & 0 deletions examples/agora-vote/hardhat.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require('dotenv').config();
const { optimism } = require('viem/chains');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any issue with using public rpc for this? lol

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only issue I can see is potentially blocking CI but not a huge deal 🤷


module.exports = {
networks: {
hardhat: {
// We might not need the mine() function if we use this code https://github.com/NomicFoundation/hardhat/pull/5394/files
chainId: optimism.id,
//hardfork: 'cancun',
forking: {
url: optimism.rpcUrls.default.http[0],
},
},
},
};
16 changes: 16 additions & 0 deletions examples/agora-vote/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "agora-vote",
"version": "1.0.0",
"scripts": {
"test": "npx vitest",
"test:ci": "CI=true npx vitest"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@boostxyz/sdk": "workspace:*",
"@boostxyz/signatures": "workspace:*",
"@boostxyz/test": "workspace:*"
}
}
267 changes: 267 additions & 0 deletions examples/agora-vote/src/agora-vote.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import {
type ActionStep,
FilterType,
PrimitiveType,
SignatureType,
} from '@boostxyz/sdk';
import events from '@boostxyz/signatures/events';
import { accounts } from '@boostxyz/test/accounts';
import {
type BudgetFixtures,
type Fixtures,
deployFixtures,
fundBudget,
} from '@boostxyz/test/helpers';
import { setupConfig, testAccount } from '@boostxyz/test/viem';
import {
loadFixture,
mine,
reset,
} from '@nomicfoundation/hardhat-toolbox-viem/network-helpers';
import {
http,
type AbiEvent,
type Address,
type Hex,
createTestClient,
encodeAbiParameters,
parseEther,
publicActions,
toHex,
walletActions,
} from 'viem';
import { optimism } from 'viem/chains';
import { beforeAll, describe, expect, test } from 'vitest';

const walletClient = createTestClient({
transport: http('http://127.0.0.1:8545'),
chain: optimism,
mode: 'hardhat',
})
.extend(publicActions)
.extend(walletActions);

const defaultOptions = {
account: testAccount,
config: setupConfig(walletClient),
};

let fixtures: Fixtures, budgets: BudgetFixtures;
// This is the Agora contract we're going to push a transaction against
const targetContract: Address = '0xcDF27F107725988f2261Ce2256bDfCdE8B382B10';
// We take the raw inputData off of an existing historical transaction
// https://optimistic.etherscan.io/tx/0x3d281344e4d0578dfc5af517c59e87770d4ded9465456cee0bd1e93484976e88
const inputData =
'0x5678138877d106504340c0bb50e5748cc9bd714e946816c7726ae7b15f132a7daa0705c40000000000000000000000000000000000000000000000000000000000000001';
// This is only for a single incentive boost
const incentiveQuantity = 1;
const referrer = accounts[1].account;

// We take the address of the imposter from the transaction above
const boostImpostor: Address = '0xc47F2266b6076b79C0a6a9906C6592b34C03c914';
const trustedSigner = accounts[0];
const OPT_CHAIN_BLOCK = BigInt('125541463');
const selector = events.selectors[
'VoteCast(address indexed,uint256,uint8,uint256,string)'
] as Hex;

describe('Boost with Voting Incentive', () => {
beforeAll(async () => {
await walletClient.reset({
jsonRpcUrl: optimism.rpcUrls.default.http[0],
blockNumber: OPT_CHAIN_BLOCK - 1n,
});
fixtures = await loadFixture(deployFixtures(defaultOptions, optimism.id));
budgets = await loadFixture(fundBudget(defaultOptions, fixtures));
});

test('should create a boost for incentivizing votes', async () => {
const { budget, erc20 } = budgets;
const { core } = fixtures;

const owner = defaultOptions.account.address;

// Step defining the action for VoteCast event
const eventActionStepOne: ActionStep = {
chainid: optimism.id,
signature: selector, // VoteCast event signature
signatureType: SignatureType.EVENT, // We're working with an event
actionType: 0, // Custom action type (set as 0 for now)
targetContract: targetContract, // Address of the ERC20 contract
// We want to target the ProposalId property on the VoteCast event
actionParameter: {
filterType: FilterType.EQUAL, // Filter to check for equality
fieldType: PrimitiveType.UINT, // The field we're filtering is a uint
fieldIndex: 1, // Targeting the 'proposalId' uint
filterData: toHex(
BigInt(
'54194543592303757979358957212312678549449891089859364558242427871997305750980',
),
), // Filtering based on the proposal id
},
};

const eventActionStepTwo: ActionStep = {
chainid: optimism.id,
signature: selector, // VoteCast event signature
signatureType: SignatureType.EVENT, // We're working with an event
actionType: 0, // Custom action type (set as 0 for now)
targetContract: targetContract, // Address of the ERC20 contract
// We want to target the Support property on the VoteCast event
actionParameter: {
filterType: FilterType.EQUAL, // Filter to check for equality
fieldType: PrimitiveType.UINT, // The field we're filtering is a uint
fieldIndex: 2, // Targeting the 'support' uint
filterData: toHex(1n, { size: 1 }), // Filtering based on the support value (uint8 is 1 byte)
},
};

// Define EventActionPayload manually
const eventActionPayload = {
actionClaimant: {
chainid: optimism.id,
signatureType: SignatureType.EVENT,
signature: selector, // VoteCast(address,uint256,uint8,uint256,string) event signature
fieldIndex: 0, // Targeting the 'voter' address
targetContract: targetContract, // The Agora vote contract we're monitoring
},
actionSteps: [eventActionStepOne, eventActionStepTwo],
};
// Initialize EventAction with the custom payload
const eventAction = core.EventAction(eventActionPayload);
// Create the boost using the custom EventAction
await core.createBoost({
protocolFee: 1n,
referralFee: 2n,
maxParticipants: 100n,
budget: budget, // Use the ManagedBudget
action: eventAction, // Pass the manually created EventAction
validator: core.SignerValidator({
signers: [owner, trustedSigner.account], // Whichever account we're going to sign with needs to be a signer
validatorCaller: fixtures.core.assertValidAddress(), // Only core should be calling into the validate otherwise it's possible to burn signatures
}),
allowList: core.SimpleAllowList({
owner: owner,
allowed: [owner],
}),
incentives: [
core.ERC20VariableIncentive({
asset: erc20.assertValidAddress(),
reward: parseEther('0.1'),
limit: parseEther('1'),
}),
],
});

// Make sure the boost was created as expected
expect(await core.getBoostCount()).toBe(1n);
const boost = await core.getBoost(0n);
const action = boost.action;
expect(action).toBeDefined();

// Use viem to send the transaction from the impersonated account
await walletClient.impersonateAccount({
address: boostImpostor,
});
await walletClient.setBalance({
address: boostImpostor,
value: parseEther('10'),
});

const txHash = await walletClient.sendTransaction({
data: inputData,
account: boostImpostor,
to: targetContract,
});
const txReceipt = await walletClient.getTransactionReceipt({
hash: txHash,
});
await walletClient.mine({ blocks: 1 });

// Make sure that the transaction was sent as expected and validates the action
expect(txHash).toBeDefined();

const event = (events.abi as Record<Hex, AbiEvent>)[selector] as AbiEvent;

if (!event) {
throw new Error(`No known ABI for given event signature: ${selector}`);
}

const logs = await walletClient.getLogs({
address: targetContract,
event,
fromBlock: OPT_CHAIN_BLOCK,
toBlock: 'latest',
});
const validation = await action.validateActionSteps({
logs,
});
expect(validation).toBe(true);

const amountOfVotes = await walletClient.readContract({
address: '0xcdf27f107725988f2261ce2256bdfcde8b382b10',
abi: [
{
inputs: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
{
internalType: 'uint256',
name: 'blockNumber',
type: 'uint256',
},
],
name: 'getVotes',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
],
functionName: 'getVotes',
args: [boostImpostor, txReceipt.blockNumber],
});

console.log({ amountOfVotes})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean up log 🪵


// If the amountOfVotes is greater than 100, then the reward should be 0.1 ETH, otherwise it will be 0.01 ETH
const rewardAmount =
amountOfVotes >= parseEther('100')
? parseEther('0.1')
: parseEther('0.01');

// Generate the signature using the trusted signer
const claimDataPayload = await boost.validator.encodeClaimData({
signer: trustedSigner,
incentiveData: encodeAbiParameters(
[{ name: '', type: 'uint256' }],
[rewardAmount],
),
chainId: optimism.id,
incentiveQuantity,
claimant: boostImpostor,
boostId: boost.id,
});


// TODO: claim with data payload for votes that aren't very much

// Claim the incentive for the imposter
await core.claimIncentiveFor(
boost.id,
0n,
referrer,
claimDataPayload,
boostImpostor,
{ value: parseEther('0.000075') },
);
});
});
16 changes: 16 additions & 0 deletions examples/agora-vote/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"rootDir": "./src",
"outDir": "dist",
"module": "Preserve",
"moduleResolution": "Bundler"
},
"exclude": ["dist", "node_modules"],
"references": [
{
"path": "../../test"
}
]
}
15 changes: 15 additions & 0 deletions examples/agora-vote/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { loadEnv } from 'vite';
import { defineConfig } from 'vitest/config';

export default defineConfig({
define: {
__DEFAULT_CHAIN_ID__: 10,
},
test: {
fileParallelism: false,
env: loadEnv('', process.cwd(), ''),
globalSetup: ['../../test/src/setup.hardhat.ts'],
hookTimeout: 30_000,
testTimeout: 15_000,
},
});
3 changes: 3 additions & 0 deletions packages/sdk/src/Actions/EventAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,9 @@ export class EventAction extends DeployableTarget<
if (criteria.fieldType === PrimitiveType.ADDRESS) {
return isAddressEqual(criteria.filterData, fieldValue as Address);
}
if (criteria.fieldType === PrimitiveType.UINT) {
return BigInt(fieldValue) === BigInt(criteria.filterData);
}
return fieldValue === criteria.filterData;

case FilterType.NOT_EQUAL:
Expand Down
1 change: 1 addition & 0 deletions packages/signatures/manifests/events.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Purchased(address indexed,address indexed,uint256 indexed,uint256,uint256)",
"NameRegistered(string,bytes32,address,uint256,uint256,uint256)",
"DelegateChanged(address indexed,address indexed,address indexed)",
"VoteCast(address indexed,uint256,uint8,uint256,string)",
"// Test signatures",
"InfoIndexed(address indexed,string indexed)",
"Info(address,string)"
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions test/src/viem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
zeroHash,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base, hardhat, sepolia } from 'viem/chains';
import { base, hardhat, optimism, sepolia } from 'viem/chains';
import { accounts } from './accounts';

const { account, key } = accounts.at(0) || {
Expand All @@ -35,7 +35,7 @@ export type TestClient = ReturnType<typeof makeTestClient>;
export function setupConfig(walletClient = makeTestClient()) {
return createConfig({
ssr: true,
chains: [hardhat, base, sepolia],
chains: [hardhat, base, sepolia, optimism],
client: () => walletClient,
});
}
Loading