Skip to content

Commit

Permalink
feat: cli update multiple rfox staking contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
kaladinlight committed Nov 12, 2024
1 parent 647bb76 commit 2d29fb0
Show file tree
Hide file tree
Showing 5 changed files with 405 additions and 162 deletions.
41 changes: 30 additions & 11 deletions cli/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { arbitrum } from 'viem/chains'
import { stakingV1Abi } from '../generated/abi'
import { RFOX_REWARD_RATE } from './constants'
import { error, info, warn } from './logging'
import { RewardDistribution } from './types'
import { CalculateRewardsArgs, RewardDistribution } from './types'

const INFURA_API_KEY = process.env['INFURA_API_KEY']

Expand All @@ -17,9 +17,13 @@ if (!INFURA_API_KEY) {
}

const AVERAGE_BLOCK_TIME_BLOCKS = 1000
const ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS = '0xac2a4fd70bcd8bab0662960455c363735f0e2b56'
const THORCHAIN_PRECISION = 8

export const ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX: Address = '0xaC2a4fD70BCD8Bab0662960455c363735f0e2b56'
export const ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_UNIV2_ETH_FOX: Address = '0x5F6Ce0Ca13B87BD738519545d3E018e70E339c24'

export const stakingContracts = [ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX]

type Revenue = {
addresses: string[]
amount: string
Expand Down Expand Up @@ -145,6 +149,7 @@ export class Client {
}
}

// TODO: get price for UNI-V2 ETH/FOX pool
async getPrice(): Promise<Price> {
try {
const { data } = await axios.get<Pool>(
Expand Down Expand Up @@ -176,12 +181,13 @@ export class Client {
}

private async getClosingStateByStakingAddress(
stakingContract: Address,
addresses: Address[],
startBlock: bigint,
endBlock: bigint,
): Promise<Record<string, ClosingState>> {
const contract = getContract({
address: ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS,
address: stakingContract,
abi: stakingV1Abi,
client: { public: this.rpc },
})
Expand Down Expand Up @@ -235,17 +241,22 @@ export class Client {
return distributionsByStakingAddress
}

async calculateRewards(
startBlock: bigint,
endBlock: bigint,
secondsInEpoch: bigint,
totalDistribution: BigNumber,
): Promise<{ totalRewardUnits: string; distributionsByStakingAddress: Record<string, RewardDistribution> }> {
async calculateRewards({
stakingContract,
startBlock,
endBlock,
secondsInEpoch,
distributionRate,
totalRevenue,
}: CalculateRewardsArgs): Promise<{
totalRewardUnits: string
distributionsByStakingAddress: Record<string, RewardDistribution>
}> {
const spinner = ora('Calculating reward distribution').start()

try {
const stakeEvents = await this.rpc.getContractEvents({
address: ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS,
address: stakingContract,
abi: stakingV1Abi,
eventName: 'Stake',
fromBlock: 'earliest',
Expand All @@ -256,7 +267,15 @@ export class Client {
...new Set(stakeEvents.map(event => event.args.account).filter(address => Boolean(address))),
] as Address[]

const closingStateByStakingAddress = await this.getClosingStateByStakingAddress(addresses, startBlock, endBlock)
const totalDistribution = BigNumber(totalRevenue).times(distributionRate)

const closingStateByStakingAddress = await this.getClosingStateByStakingAddress(
stakingContract,
addresses,
startBlock,
endBlock,
)

const distributionsByStakingAddress = await this.getDistributionsByStakingAddress(
closingStateByStakingAddress,
totalDistribution,
Expand Down
100 changes: 75 additions & 25 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import ora from 'ora'
import { Client } from './client'
import { ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX, Client, stakingContracts } from './client'
import { MONTHS } from './constants'
import { isEpochDistributionStarted } from './file'
import { IPFS } from './ipfs'
import { error, info, success, warn } from './logging'
import { create, recoverKeystore } from './mnemonic'
import { Epoch, RFOXMetadata } from './types'
import { Epoch, EpochDetails, RFOXMetadata } from './types'
import { Wallet } from './wallet'
import { Command } from 'commander'

Expand Down Expand Up @@ -48,13 +48,28 @@ const processEpoch = async () => {
`Total ${month} revenue earned by ${revenue.addresses}: ${BigNumber(revenue.amount).div(100000000).toFixed(8)} RUNE`,
)

info(`Share of total revenue to be distributed as rewards: ${metadata.distributionRate * 100}%`)
info(`Share of total revenue to buy back fox and burn: ${metadata.burnRate * 100}%`)

const totalDistribution = BigNumber(BigNumber(revenue.amount).times(metadata.distributionRate).toFixed(0))
const totalDistributionRate = Object.entries(metadata.distributionRateByStakingContract).reduce(
(prev, [stakingContract, distributionRate]) => {
info(
`Share of total revenue to be distributed as rewards for staking contract: ${stakingContract}: ${distributionRate * 100}%`,
)
const totalDistribution = BigNumber(BigNumber(revenue.amount).times(distributionRate).toFixed(0))
info(
`Total rewards to be distributed for staking contract: ${stakingContract}: ${totalDistribution.div(100000000).toFixed()} RUNE`,
)
return prev + distributionRate
},
0,
)

info(`Share of total revenue to be distributed as rewards: ${totalDistributionRate * 100}%`)
const totalDistribution = BigNumber(BigNumber(revenue.amount).times(totalDistributionRate).toFixed(0))
info(`Total rewards to be distributed: ${totalDistribution.div(100000000).toFixed()} RUNE`)

info(`Share of total revenue to buy back fox and burn: ${metadata.burnRate * 100}%`)
const totalBurn = BigNumber(BigNumber(revenue.amount).times(metadata.burnRate).toFixed(0))
info(`Total amount to be used to buy back fox and burn: ${totalBurn.div(100000000).toFixed()} RUNE`)

const spinner = ora('Detecting epoch start and end blocks...').start()

const startBlock = await client.getBlockByTimestamp(
Expand All @@ -76,30 +91,41 @@ const processEpoch = async () => {

const secondsInEpoch = BigInt(Math.floor((metadata.epochEndTimestamp - metadata.epochStartTimestamp) / 1000))

const { totalRewardUnits, distributionsByStakingAddress } = await client.calculateRewards(
startBlock,
endBlock,
secondsInEpoch,
totalDistribution,
)

const { assetPriceUsd, runePriceUsd } = await client.getPrice()

const detailsByStakingContract: Record<string, EpochDetails> = {}
for (const stakingContract of stakingContracts) {
const distributionRate = metadata.distributionRateByStakingContract[stakingContract]

const { totalRewardUnits, distributionsByStakingAddress } = await client.calculateRewards({
stakingContract,
startBlock,
endBlock,
secondsInEpoch,
distributionRate,
totalRevenue: revenue.amount,
})

detailsByStakingContract[stakingContract] = {
assetPriceUsd,
distributionRate,
distributionsByStakingAddress,
totalRewardUnits,
}
}

const epochHash = await ipfs.addEpoch({
number: metadata.epoch,
startTimestamp: metadata.epochStartTimestamp,
endTimestamp: metadata.epochEndTimestamp,
startBlock: Number(startBlock),
endBlock: Number(endBlock),
treasuryAddress: metadata.treasuryAddress,
totalRevenue: revenue.amount,
totalRewardUnits,
distributionRate: metadata.distributionRate,
burnRate: metadata.burnRate,
assetPriceUsd,
runePriceUsd,
treasuryAddress: metadata.treasuryAddress,
distributionStatus: 'pending',
distributionsByStakingAddress,
detailsByStakingContract,
})

const nextEpochStartDate = new Date(metadata.epochEndTimestamp + 1)
Expand Down Expand Up @@ -195,6 +221,24 @@ const update = async () => {
)
}

const migrate = async () => {
const ipfs = await IPFS.new()

const metadata = await ipfs.migrate()
const hash = await ipfs.updateMetadata(metadata)

if (!hash) return

success(`rFOX metadata has been updated!`)

info(
'Please update the rFOX Wiki (https://github.com/shapeshift/rFOX/wiki/rFOX-Metadata) and notify the DAO accordingly. Thanks!',
)
warn(
'Important: CURRENT_EPOCH_METADATA_IPFS_HASH must be updated in web (https://github.com/shapeshift/web/blob/develop/src/pages/RFOX/constants.ts).',
)
}

const processDistribution = async (metadata: RFOXMetadata, epoch: Epoch, wallet: Wallet, ipfs: IPFS) => {
const client = await Client.new()

Expand All @@ -205,12 +249,11 @@ const processDistribution = async (metadata: RFOXMetadata, epoch: Epoch, wallet:

const { assetPriceUsd, runePriceUsd } = await client.getPrice()

const processedEpochHash = await ipfs.addEpoch({
...processedEpoch,
assetPriceUsd,
runePriceUsd,
distributionStatus: 'complete',
})
processedEpoch.runePriceUsd = runePriceUsd
processedEpoch.distributionStatus = 'complete'
processedEpoch.detailsByStakingContract[ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX].assetPriceUsd = assetPriceUsd

const processedEpochHash = await ipfs.addEpoch(processedEpoch)

const metadataHash = await ipfs.updateMetadata(metadata, {
epoch: { number: processedEpoch.number, hash: processedEpochHash },
Expand Down Expand Up @@ -241,7 +284,7 @@ const main = async () => {
}
}

const choice = await prompts.select<'process' | 'run' | 'recover' | 'update'>({
const choice = await prompts.select<'process' | 'run' | 'recover' | 'update' | 'migrate'>({
message: 'What do you want to do?',
choices: [
{
Expand All @@ -264,6 +307,11 @@ const main = async () => {
value: 'update',
description: 'Start here to update an rFOX metadata.',
},
{
name: 'Migrate rFOX metadata',
value: 'migrate',
description: 'Start here to migrate an existing rFOX metadata to the new format.',
},
],
})

Expand All @@ -276,6 +324,8 @@ const main = async () => {
return recover()
case 'update':
return update()
case 'migrate':
return migrate()
default:
error(`Invalid choice: ${choice}, exiting.`)
process.exit(1)
Expand Down
Loading

0 comments on commit 2d29fb0

Please sign in to comment.