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

Add TACo operators' support #117

Merged
merged 5 commits into from
Jan 2, 2024
Merged
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
53 changes: 0 additions & 53 deletions src/scripts/gen_rewards_dist.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const ethers = require("ethers")
const Subgraph = require("./pre-rewards/subgraph.js")
const Rewards = require("./pre-rewards/rewards.js")
const MerkleDist = require("./merkle_dist/merkle_dist.js")
const BigNumber = require("bignumber.js")

// The following parameters must be modified for each distribution
const bonusWeight = 0.0
Expand Down Expand Up @@ -77,56 +76,15 @@ async function main() {
endTime
)
earnedPreRewards = Rewards.calculatePreRewards(preStakes, preWeight)
console.log("======= EARNED PRE REWARDS =======")
console.log(earnedPreRewards)
}

// Special case: Dec 8th 23 distribution. ###################################
// Nov 22nd 23, legacy stakes (i.e. T stakes that originally came from Keep
// and Nu staking contracts) were deactivated. There is a period in which
// these stakers can migrate to T and stake their tokens without losing the
// rewards associated to this migration period.
// This period ended for the legacy Nu stakers at Dec 8th 23 00:00.
// So, if a legacy stake migrated their legacy Nu (nuInT) tokens before
// the deadline, the period in which their tokens weren't staked (Nov 22nd -
// Dec 8th) will not be considered, so they will earn the corresponding
// rewards as there was no disruption in the staking.
//
// In addition, no rewards were distributed for stakes that had legacy
// staking, even if part of the staking was in tStake and not in legacy stake
// (nuInT, keepInT). So

// More info can be found here:
// https://github.com/threshold-network/solidity-contracts/issues/141
// https://forum.threshold.network/t/transition-guide-for-legacy-stakers/719
// https://etherscan.io/tx/0x68ddee6b5651d5348a40555b0079b5066d05a63196e3832323afafae0095a656
// https://github.com/threshold-network/merkle-distribution/pull/111

// We need the legacy stakes to delete the Keep legacy stakes
const blockNumber = 18624792 // Block height in which legacy stakes were deac
const legacyStakes = await Subgraph.getLegacyStakes(
graphqlApi,
blockNumber - 1
)

const legacyNuRewards = await Subgraph.getLegacyNuRewards(graphqlApi)
console.log("======= LEGACY NU REWARDS =======")
console.log(legacyNuRewards)

Object.keys(legacyNuRewards).map((stake) => {
// Caution: we are assuming that each legacyNuReward stake will have the
// same stake in earnedPreRewardAmount. This is true for this time
const stakeAdd = ethers.utils.getAddress(stake)
const earnedPreRewardAmount = BigNumber(earnedPreRewards[stakeAdd].amount)
const legacyNuRewardAmount = BigNumber(legacyNuRewards[stake])
earnedPreRewards[stakeAdd].amount = earnedPreRewardAmount
.plus(legacyNuRewardAmount)
.toFixed(0)
})

console.log("======= LEGACY NU + PRE REWARDS =======")
console.log(earnedPreRewards)

// tBTCv2 rewards calculation
if (tbtcv2Weight > 0) {
console.log("Calculating tBTCv2 rewards...")
Expand All @@ -138,8 +96,6 @@ async function main() {
tbtcv2RewardsRaw,
tbtcv2Weight
)
console.log("======= EARNED TBTC REWARDS BEFORE LEGACY REMOVE =======")
console.log(earnedTbtcv2Rewards)
}

// Delete the Keep legacy stakes in earned rewards
Expand All @@ -148,17 +104,11 @@ async function main() {
delete earnedTbtcv2Rewards[legacyStakeAddress]
})

console.log("======= EARNED TBTC REWARDS AFTER LEGACY REMOVE =======")
console.log(earnedTbtcv2Rewards)

// Delete the Keep legacy stakes in rewards details file
const rewardsDetailsPath =
"distributions/2023-12-08/tBTCv2-rewards-details/1701388800-1701993600.json"
const rewardsDetails = JSON.parse(fs.readFileSync(rewardsDetailsPath))

console.log("======= REWARDS DETAILS BEFORE LEGACY REMOVE =======")
console.log(JSON.stringify(rewardsDetails, null, 4))

const rewardsDetailsFiltered = rewardsDetails.filter((rewardDetail) => {
const rewardStakingProvider = Object.keys(rewardDetail)[0].toLowerCase()
const legacyStakesStakingProviders = Object.keys(legacyStakes)
Expand All @@ -170,9 +120,6 @@ async function main() {
JSON.stringify(rewardsDetailsFiltered, null, 4)
)

console.log("======= REWARDS DETAILS AFTER LEGACY REMOVE =======")
console.log(JSON.stringify(rewardsDetailsFiltered, null, 4))

// Add rewards earned to cumulative totals
try {
bonusRewards = JSON.parse(
Expand Down
51 changes: 49 additions & 2 deletions src/scripts/pre-rewards/subgraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require("isomorphic-unfetch")
const { createClient, gql } = require("@urql/core")
const { ethers } = require("ethers")
const BigNumber = require("bignumber.js")
const Taco = require("../taco-rewards/taco-rewards.js")

// The Graph limits GraphQL queries to 1000 results max
const RESULTS_PER_QUERY = 1000
Expand Down Expand Up @@ -270,12 +271,58 @@ exports.getPreStakes = async function (gqlUrl, startTimestamp, endTimestamp) {
const currentTime = parseInt(Date.now() / 1000)
const gqlClient = createClient({ url: gqlUrl })

// Get the list of operators confirmed between dates
const opsConfirmed = await getOperatorsConfirmedBeforeDate(
// Get the list of TACo operators confirmed
const tacoOpsConfirmed = await Taco.getOperatorsConfirmed(endTimestamp)

// Get the list of PRE operators confirmed between dates
const preOpsConfirmed = await getOperatorsConfirmedBeforeDate(
gqlClient,
endTimestamp
)

// Special case Jan 1st distribution: during this rewards' period TACo nodes
// have been released. So, during this transition period, both PRE and TACo
// are valid to be rewards-eligible. So:
// - PRE operators which were confirmed before Jan 1st, are eligible for
// rewards.
// - TACo operators which were confirmed before Jan 1st, are elibigle for
// rewards.
// - Stakes that have confirmed both, PRE operator and TACo operator, will be
// considered as eligible since the date in which the first operator
// (either PRE or TACo) was confirmed.

const opsConfirmed = preOpsConfirmed.map((stake) => {
const op = {
id: stake.id,
operator: stake.operator,
confirmedTimestamp: stake.confirmedTimestamp,
}
const tacoOp = Object.keys(tacoOpsConfirmed).find(
(stProv) => stProv.toLowerCase() === stake.id
)
// If TACo operator was confirmed before PRE operator...
if (
tacoOp &&
tacoOpsConfirmed[tacoOp].confirmedTimestamp < stake.confirmedTimestamp
) {
op.operator = tacoOpsConfirmed[tacoOp].operator.toLowerCase()
op.confirmedTimestamp = tacoOpsConfirmed[tacoOp].confirmedTimestamp
}
return op
})

// Look for stakes that have a confirmed TACo operator but not a PRE operator
Object.keys(tacoOpsConfirmed).map((tacoOp) => {
if (!preOpsConfirmed.find((preOp) => preOp.id === tacoOp.toLowerCase())) {
const op = {
id: tacoOp.toLowerCase(),
operator: tacoOpsConfirmed[tacoOp].operator.toLowerCase(),
confirmedTimestamp: tacoOpsConfirmed[tacoOp].confirmedTimestamp,
}
opsConfirmed.push(op)
}
})

// Get the stakes information
const stakeDatas = await getStakeDatasInfo(gqlClient)

Expand Down
100 changes: 100 additions & 0 deletions src/scripts/taco-rewards/taco-rewards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const { ethers } = require("ethers")

const TACoChildApplicationAdd = "0xFa07aaB78062Fac4C36995bF28F6D677667973F5"
const eventSignature = "OperatorConfirmed(address,address)"
const eventTopic = ethers.utils.id(eventSignature)
const eventAbi =
// eslint-disable-next-line quotes
'[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakingProvider","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"}],"name":"OperatorConfirmed","type":"event"}]'

// Return the operator confirmed event for the provided staking provider
async function getOperatorConfirmedEvent(stakingProvider) {
const eventIntrfc = new ethers.utils.Interface(eventAbi)
const provider = new ethers.providers.JsonRpcProvider(
process.env.POLYGON_RPC_URL
)

const rawLogs = await provider.getLogs({
address: TACoChildApplicationAdd,
topics: [
eventTopic,
ethers.utils.hexZeroPad(stakingProvider.toLowerCase(), 32),
],
fromBlock: 50223997,
})

if (rawLogs.length === 0) {
return undefined
}

// Take the most recent Operator confirmed event
const log = rawLogs.reduce((acc, val) => {
return acc > val ? acc : val
})
const parsedLog = eventIntrfc.parseLog(log)

const operatorInfo = {
stakingProvider: stakingProvider,
operator: parsedLog.args.operator,
blockNumber: log.blockNumber,
}

return operatorInfo
}

// Return the TACo operators confirmed before provided timestamp
// Note: each returned operator object will contain the timestamp of the oldest
// operator confirmed but the address of the most recent operator.
async function getOperatorsConfirmed(timestamp) {
if (!timestamp) {
timestamp = Math.floor(Date.now() / 1000)
}

const eventIntrfc = new ethers.utils.Interface(eventAbi)
const provider = new ethers.providers.JsonRpcProvider(
process.env.POLYGON_RPC_URL
)

// TODO: as stated in ethers.js docs, many backends will discard old events.
// Use subgraph instead
const rawLogs = await provider.getLogs({
address: TACoChildApplicationAdd,
topics: [eventTopic],
fromBlock: 50223997,
})

// Get the timestamps for each operator confirmed event
const logsTimestamps = {}
const promises = rawLogs.map((log) => {
return provider.getBlock(log.blockHash).then((block) => {
logsTimestamps[block.number] = block.timestamp
})
})
await Promise.all(promises)

// Check if events were confirmed before provided timestamp
const filtRawLogs = rawLogs.filter((log) => {
return logsTimestamps[log.blockNumber] <= timestamp
})

const opsConfirmed = {}
filtRawLogs.map((filtRawLog) => {
const stProvLogs = filtRawLogs.filter(
(log) => log.topics[1] === filtRawLog.topics[1]
)
const firstStProvLog = stProvLogs[0]
const latestStProvLog = stProvLogs[stProvLogs.length - 1]

const latestStProvLogParsed = eventIntrfc.parseLog(latestStProvLog)
const stProv = latestStProvLogParsed.args.stakingProvider

opsConfirmed[stProv] = {
confirmedTimestamp: logsTimestamps[firstStProvLog.blockNumber],
operator: latestStProvLogParsed.args.operator,
}
})

return opsConfirmed
}

module.exports = { getOperatorConfirmedEvent, getOperatorsConfirmed }