Skip to content

Commit

Permalink
Merge pull request #117 from manumonti/taco
Browse files Browse the repository at this point in the history
Add TACo operators' support
  • Loading branch information
cygnusv authored Jan 2, 2024
2 parents 72154f4 + be1c1ac commit 0566a73
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 55 deletions.
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 }

0 comments on commit 0566a73

Please sign in to comment.