From 8a9c44a69ec50eb3c197a825ace9ec8a706f9bec Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Thu, 28 Dec 2023 11:05:06 +0100 Subject: [PATCH 1/5] Add func to get TACo operator confirmed events --- src/scripts/taco-rewards/taco-rewards.js | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/scripts/taco-rewards/taco-rewards.js diff --git a/src/scripts/taco-rewards/taco-rewards.js b/src/scripts/taco-rewards/taco-rewards.js new file mode 100644 index 00000000..040c8a27 --- /dev/null +++ b/src/scripts/taco-rewards/taco-rewards.js @@ -0,0 +1,43 @@ +const { ethers } = require("ethers") + +async function getOperatorConfirmedEvent(stakingProvider) { + const TACoChildApplicationAdd = "0xFa07aaB78062Fac4C36995bF28F6D677667973F5" + const provider = new ethers.providers.JsonRpcProvider( + process.env.POLYGON_RPC_URL + ) + 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"}]' + const eventIntrfc = new ethers.utils.Interface(eventAbi) + + 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 +} + +module.exports = { getOperatorConfirmedEvent } From 1529bf367298635078c58f593a0a605480bcfd88 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Sun, 31 Dec 2023 13:03:22 +0100 Subject: [PATCH 2/5] Add getOperatorsConfirmed function to TACo --- src/scripts/taco-rewards/taco-rewards.js | 74 +++++++++++++++++++++--- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/src/scripts/taco-rewards/taco-rewards.js b/src/scripts/taco-rewards/taco-rewards.js index 040c8a27..b6737401 100644 --- a/src/scripts/taco-rewards/taco-rewards.js +++ b/src/scripts/taco-rewards/taco-rewards.js @@ -1,16 +1,18 @@ 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 TACoChildApplicationAdd = "0xFa07aaB78062Fac4C36995bF28F6D677667973F5" + const eventIntrfc = new ethers.utils.Interface(eventAbi) const provider = new ethers.providers.JsonRpcProvider( process.env.POLYGON_RPC_URL ) - 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"}]' - const eventIntrfc = new ethers.utils.Interface(eventAbi) const rawLogs = await provider.getLogs({ address: TACoChildApplicationAdd, @@ -40,4 +42,60 @@ async function getOperatorConfirmedEvent(stakingProvider) { return operatorInfo } -module.exports = { getOperatorConfirmedEvent } +// Return the TACo operators confirmed before provided timestamp +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, + }) + + // Take the most recent confirmed operator event for each staking provider + const filtRawLogs = [] + rawLogs.forEach((log) => { + const lastLog = rawLogs.findLast((elem) => elem.topics[1] === log.topics[1]) + if (lastLog.transactionHash === log.transactionHash) { + filtRawLogs.push(log) + } + }) + + const parsedLogs = filtRawLogs.map((log) => eventIntrfc.parseLog(log)) + + const logsTimestamps = {} + const promises = filtRawLogs.map((log, logIndex) => { + return provider.getBlock(log.blockHash).then((block) => { + const stProv = parsedLogs[logIndex].args.stakingProvider + logsTimestamps[stProv] = block.timestamp + }) + }) + await Promise.all(promises) + + // Check if events were confirmed before provided timestamp + const filtParsedLogs = parsedLogs.filter( + (log) => logsTimestamps[log.args.stakingProvider] <= timestamp + ) + + const opsConfirmed = {} + filtParsedLogs.map((log) => { + const stProv = log.args.stakingProvider + opsConfirmed[stProv] = { + confirmedTimestamp: logsTimestamps[stProv], + operator: log.args.operator, + } + }) + + return opsConfirmed +} + +module.exports = { getOperatorConfirmedEvent, getOperatorsConfirmed } From d21bcf1c5d6c08ec72a7ad3911ca9ca45801d490 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Sun, 31 Dec 2023 14:34:46 +0100 Subject: [PATCH 3/5] Fix getOperatorsConfirmed function Now, this function returns the timestamp of the oldest operator confirmed for each staking provider but also returns the address of the most recent operator. --- src/scripts/taco-rewards/taco-rewards.js | 41 ++++++++++++------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/scripts/taco-rewards/taco-rewards.js b/src/scripts/taco-rewards/taco-rewards.js index b6737401..a8537445 100644 --- a/src/scripts/taco-rewards/taco-rewards.js +++ b/src/scripts/taco-rewards/taco-rewards.js @@ -43,6 +43,8 @@ async function getOperatorConfirmedEvent(stakingProvider) { } // 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) @@ -61,37 +63,34 @@ async function getOperatorsConfirmed(timestamp) { fromBlock: 50223997, }) - // Take the most recent confirmed operator event for each staking provider - const filtRawLogs = [] - rawLogs.forEach((log) => { - const lastLog = rawLogs.findLast((elem) => elem.topics[1] === log.topics[1]) - if (lastLog.transactionHash === log.transactionHash) { - filtRawLogs.push(log) - } - }) - - const parsedLogs = filtRawLogs.map((log) => eventIntrfc.parseLog(log)) - + // Get the timestamps for each operator confirmed event const logsTimestamps = {} - const promises = filtRawLogs.map((log, logIndex) => { + const promises = rawLogs.map((log) => { return provider.getBlock(log.blockHash).then((block) => { - const stProv = parsedLogs[logIndex].args.stakingProvider - logsTimestamps[stProv] = block.timestamp + logsTimestamps[block.number] = block.timestamp }) }) await Promise.all(promises) // Check if events were confirmed before provided timestamp - const filtParsedLogs = parsedLogs.filter( - (log) => logsTimestamps[log.args.stakingProvider] <= timestamp - ) + const filtRawLogs = rawLogs.filter((log) => { + return logsTimestamps[log.blockNumber] <= timestamp + }) const opsConfirmed = {} - filtParsedLogs.map((log) => { - const stProv = log.args.stakingProvider + 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[stProv], - operator: log.args.operator, + confirmedTimestamp: logsTimestamps[firstStProvLog.blockNumber], + operator: latestStProvLogParsed.args.operator, } }) From c121680b7e5fce446d5f8bdfdd2b9b332a74d0eb Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Tue, 2 Jan 2024 07:07:27 +0100 Subject: [PATCH 4/5] Add TACo operators support for Jan 1 2024 dist --- src/scripts/pre-rewards/subgraph.js | 51 +++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/scripts/pre-rewards/subgraph.js b/src/scripts/pre-rewards/subgraph.js index 68fd4313..e4854c78 100644 --- a/src/scripts/pre-rewards/subgraph.js +++ b/src/scripts/pre-rewards/subgraph.js @@ -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 @@ -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) From be1c1ac4939392b8f2f27359415ea32501fc1238 Mon Sep 17 00:00:00 2001 From: Manuel Montenegro Date: Tue, 2 Jan 2024 07:09:24 +0100 Subject: [PATCH 5/5] Delete ad-hoc calculations for Legacy Nu --- src/scripts/gen_rewards_dist.js | 53 --------------------------------- 1 file changed, 53 deletions(-) diff --git a/src/scripts/gen_rewards_dist.js b/src/scripts/gen_rewards_dist.js index b8387e1a..62cd7060 100644 --- a/src/scripts/gen_rewards_dist.js +++ b/src/scripts/gen_rewards_dist.js @@ -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 @@ -77,31 +76,8 @@ 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( @@ -109,24 +85,6 @@ async function main() { 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...") @@ -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 @@ -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) @@ -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(