Skip to content

Commit

Permalink
fix: use anvil for local rewards distribution script (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
woodenfurniture authored May 30, 2024
1 parent 6396040 commit 83b361d
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 163 deletions.
15 changes: 15 additions & 0 deletions foundry/script/DeployMockFoxToken.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import "forge-std/Script.sol";
import {MockFOXToken} from "../test/utils/MockFOXToken.sol";

contract DeployMockFoxToken is Script {
function run() public {
vm.startBroadcast();
MockFOXToken mockFoxToken = new MockFOXToken();
vm.stopBroadcast();

console.log("Contract deployed at:", address(mockFoxToken));
}
}
16 changes: 11 additions & 5 deletions scripts/rewards-distribution/constants.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { localhost } from "viem/chains";

const ANVIL_JSON_RPC_URL = "http://127.0.0.1:8545";

export const localChain = {
...localhost,
id: 31337,
} as const;

export const localWalletClient = createWalletClient({
export const localOwnerWalletClient = createWalletClient({
chain: localChain,
account: privateKeyToAccount(process.env.OWNER_PRIVATE_KEY as `0x${string}`),
transport: http(process.env.ANVIL_JSON_RPC_URL),
});

export const localUserWalletClient = createWalletClient({
chain: localChain,
transport: http(ANVIL_JSON_RPC_URL),
account: privateKeyToAccount(process.env.USER_PRIVATE_KEY as `0x${string}`),
transport: http(process.env.ANVIL_JSON_RPC_URL),
});

export const localPublicClient = createPublicClient({
chain: localChain,
transport: http(ANVIL_JSON_RPC_URL),
transport: http(process.env.ANVIL_JSON_RPC_URL),
});
115 changes: 43 additions & 72 deletions scripts/rewards-distribution/events.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,52 @@
import { AbiEvent, Address, Log, parseEventLogs } from "viem";
import { Log, getAbiItem } from "viem";
import { stakingV1Abi } from "./generated/abi-types";

type StakingEventName = "Stake" | "Unstake";
export const stakeEvent = getAbiItem({ abi: stakingV1Abi, name: "Stake" });
export const unstakeEvent = getAbiItem({ abi: stakingV1Abi, name: "Unstake" });
export const updateCooldownPeriodEvent = getAbiItem({
abi: stakingV1Abi,
name: "UpdateCooldownPeriod",
});
export const withdrawEvent = getAbiItem({
abi: stakingV1Abi,
name: "Withdraw",
});
export const setRuneAddressEvent = getAbiItem({
abi: stakingV1Abi,
name: "SetRuneAddress",
});

type StakingAbiEvent<T extends StakingEventName> = {
type: "event";
anonymous: false;
inputs: [
{
name: "account";
type: "address";
indexed: true;
},
{
name: "amount";
type: "uint256";
indexed: false;
},
// TOOD(gomes): if runeAddress is part of the staking fn, then it should be part of the Stake event too and should be reflected here
];
name: T;
};
export const rFoxEvents = [
stakeEvent,
unstakeEvent,
updateCooldownPeriodEvent,
withdrawEvent,
setRuneAddressEvent,
] as const;

type GenericStakingEventLog<T extends StakingEventName> = Log<
export type RFoxEvent = (typeof rFoxEvents)[number];

export type StakeLog = Log<bigint, number, false, typeof stakeEvent, true>;
export type UnstakeLog = Log<bigint, number, false, typeof unstakeEvent, true>;
export type UpdateCooldownPeriodLog = Log<
bigint,
number,
false,
AbiEvent,
true,
StakingAbiEvent<T>[],
T
typeof updateCooldownPeriodEvent,
true
>;

// explicit union of all possible event logs to ensure event args are correctly parsed by ts (fixes defaulting to unknown)
export type StakingEventLog =
| GenericStakingEventLog<"Stake">
| GenericStakingEventLog<"Unstake">;

const addressA: Address = "0xA";
const addressB: Address = "0xB";
const addressC: Address = "0xC";
const addressD: Address = "0xD";
const addressE: Address = "0xE";

export type StakingLog = Pick<
StakingEventLog,
"blockNumber" | "eventName" | "args"
export type WithdrawLog = Log<bigint, number, false, typeof withdrawEvent>;
export type SetRuneAddressLog = Log<
bigint,
number,
false,
typeof setRuneAddressEvent,
true
>;

export const logs: StakingLog[] = [
{
blockNumber: 20n,
eventName: "Stake",
args: { account: addressA, amount: 100n },
},
{
blockNumber: 25n,
eventName: "Stake",
args: { account: addressB, amount: 150n },
},
{
blockNumber: 32n,
eventName: "Stake",
args: { account: addressC, amount: 10000n },
},
{
blockNumber: 33n,
eventName: "Stake",
args: { account: addressD, amount: 1200n },
},
{
blockNumber: 60n,
eventName: "Unstake",
args: { account: addressA, amount: 100n },
},
{
blockNumber: 65n,
eventName: "Stake",
args: { account: addressE, amount: 500n },
},
];
export type RFoxLog =
| StakeLog
| UnstakeLog
| UpdateCooldownPeriodLog
| WithdrawLog
| SetRuneAddressLog;
68 changes: 29 additions & 39 deletions scripts/rewards-distribution/index.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
import { Address, parseAbiItem } from "viem";
import { StakingLog } from "./events";
import { Address } from "viem";
import { StakeLog, UnstakeLog, stakeEvent, unstakeEvent } from "./events";
import { assertUnreachable } from "./helpers";
import { simulateStaking } from "./simulateStaking";
import { localPublicClient } from "./constants";
import { assert } from "console";

const getLogs = async ({
const getStakingLogs = async ({
fromBlock,
toBlock,
}: {
fromBlock: bigint;
toBlock: bigint;
}) => {
const logs = await localPublicClient.getLogs({
// address: '0x'
events: [
parseAbiItem(
"event Stake(address indexed account, uint256 amount, string runeAddress)",
),
parseAbiItem("event Unstake(address indexed user, uint256 amount)"),
],
events: [stakeEvent, unstakeEvent],
fromBlock,
toBlock,
});
return logs as StakingLog[];
return logs;
};

const getStakingAmount = (log: StakingLog): bigint => {
switch (log.eventName) {
const getStakingAmount = (log: StakeLog | UnstakeLog): bigint => {
const eventName = log.eventName;
switch (eventName) {
case "Stake":
return log.args.amount;
case "Unstake":
return -log.args.amount;
default:
assertUnreachable(log.eventName);
assertUnreachable(eventName);
}

throw Error("should be unreachable");
Expand All @@ -49,29 +45,30 @@ const getEpochBlockReward = (_epochEndBlockNumber: bigint) => {
const getEpochBlockRange = () => {
// Monkey-patched to 0 and 5 for testing for now since the current simulation only goes up to block 5
const previousEpochEndBlockNumber = 0n;
const currentBlockNumber = 5n;
const currentBlockNumber = 500n;
return {
fromBlockNumber: previousEpochEndBlockNumber,
toBlockNumber: currentBlockNumber,
fromBlock: previousEpochEndBlockNumber,
toBlock: currentBlockNumber,
};
};

// TODO: this should only process 1 epoch at a time
const main = async () => {
await simulateStaking();
// While testing, and with the current simulation flow we only need logs from block 1 to 5 but this may change
const logs = await getLogs({ fromBlock: 0n, toBlock: 5n });

// iterate all blocks for the current epoch
const { fromBlock, toBlock } = getEpochBlockRange();

// Grab the first 500 or so blocks so we can simulate rewards distribution without worrying about how many blocks elapsed during contract deployment
const logs = await getStakingLogs({ fromBlock, toBlock });
// index logs by block number
const logsByBlockNumber = logs.reduce<Record<string, StakingLog[]>>(
(acc, log) => {
if (!acc[log.blockNumber.toString()]) {
acc[log.blockNumber.toString()] = [];
}
acc[log.blockNumber.toString()].push(log);
return acc;
},
{},
);
const logsByBlockNumber = logs.reduce<Record<string, any[]>>((acc, log) => {
if (!acc[log.blockNumber.toString()]) {
acc[log.blockNumber.toString()] = [];
}
acc[log.blockNumber.toString()].push(log);
return acc;
}, {});

// TODO: these will be initialized from the last epoch's state
let totalStaked = 0n;
Expand All @@ -80,17 +77,10 @@ const main = async () => {
// this must be initialized to empty
const epochRewardByAccount: Record<Address, number> = {};

// iterate all blocks for the current epoch
const { fromBlockNumber, toBlockNumber } = getEpochBlockRange();

const epochBlockReward = getEpochBlockReward(toBlockNumber);
const epochBlockReward = getEpochBlockReward(toBlock);

for (
let blockNumber = fromBlockNumber;
blockNumber <= toBlockNumber;
blockNumber++
) {
const incomingLogs: StakingLog[] | undefined =
for (let blockNumber = fromBlock; blockNumber <= toBlock; blockNumber++) {
const incomingLogs: (StakeLog | UnstakeLog)[] | undefined =
logsByBlockNumber[blockNumber.toString()];

// process logs if there are any
Expand Down
Loading

0 comments on commit 83b361d

Please sign in to comment.