Skip to content

Commit

Permalink
Merge pull request #105 from paraswap/feat/aura-rewards
Browse files Browse the repository at this point in the history
Feat/aura rewards
  • Loading branch information
alexshchur authored May 21, 2024
2 parents b2526fb + d0f96d2 commit 92ec440
Show file tree
Hide file tree
Showing 20 changed files with 964 additions and 86 deletions.
10 changes: 10 additions & 0 deletions .sequelizerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// .sequelizerc

const path = require('path');

module.exports = {
config: path.resolve('sequelize', 'database.js'),
// 'models-path': path.resolve('db', 'models'),
// 'seeders-path': path.resolve('db', 'seeders'),
'migrations-path': path.resolve('sequelize', 'migrations'),
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"gas-refund:dev:compute-gas-refund-save-db": "patch-package && NODE_ENV=development ts-node scripts/gas-refund-program/computeGasRefund",
"gas-refund:prod:compute-gas-refund-save-db": "node scripts/gas-refund-program/computeGasRefund.js",
"gas-refund:computeDistributionDataAndPersistDB": "patch-package && NODE_ENV=development ts-node scripts/gas-refund-program/distribution/computeDistributionDataAndPersistDB",
"gas-refund:computeDistributionDataAndPersistDB-epoch-47": "DISTRIBUTED_EPOCH=47 yarn gas-refund:computeDistributionDataAndPersistDB",
"gas-refund:computeDistributionFilesAndPersistIPFS": "patch-package && NODE_ENV=development ts-node scripts/gas-refund-program/distribution/computeDistributionFilesAndPersistIPFS",
"migrate:up": "source .env && DATABASE_URL=$DATABASE_URL npx sequelize-cli db:migrate # <- executes any new migrations that are not in sequalize meta table yet, sorted alphabetically",
"migrate:undo": "source .env && DATABASE_URL=$DATABASE_URL npx sequelize-cli db:migrate:undo # <- undoes the last migration from sequalize meta table, sorted alphabetically",
"test": "jest"
},
"husky": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ import { persistDirectoryToPinata } from './utils/pinata';
import { GasRefundGenesisEpoch } from '../../../src/lib/gas-refund/gas-refund';
import { computeDistributionSimulation } from './lib/computeDistributionSimulation';

assert(
process.env.DISTRIBUTED_EPOCH,
'DISTRIBUTED_EPOCH env variable is required',
);
assert(process.env.TENDERLY_TOKEN, 'TENDERLY_TOKEN env variable is required');
assert(
process.env.TENDERLY_PROJECT,
'TENDERLY_PROJECT env variable is required',
);
assert(process.env.PINATA_API_KEY, 'PINATA_API_KEY env variable is required');
assert(
process.env.PINATE_API_SECRET,
'PINATE_API_SECRET env variable is required',
);

const constructBasePath = (epoch: number) =>
path.join(__dirname, `data/grp-distribution-epoch-${epoch}`);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,133 @@ import { StakeV2Resolver } from '../../staking/2.0/StakeV2Resolver';
import {
AddressChainRewardsMapping,
AddressRewards,
AddressRewardsMapping,
AddressRewardsMappingWithMaybeGRP,
ChainRewardsMapping,
MerkleTreeAndChain,
} from './types';
import {
ProgramAgnosticAddressRewards,
AddressRewardsWithAmountsByProgramVariation,
isGRPItem,
AddressRewardsGRP,
} from '../../../../src/types';
import { composeAuraRewards } from '../../../../src/lib/utils/aura-rewards';

function combinePrograms(
input: { programName: string; rewards: ProgramAgnosticAddressRewards[] }[],
): AddressRewardsWithAmountsByProgramVariation[] {
// TODO
// return input[0].rewards.map(reward => ({
// ...reward,
// amountsByProgram: {
// [input[0].programName]: reward.amount.toFixed(),
// },
// }));
// return [];

const mergedByChainByUserByProgram: {
[chainId: number]: {
[user: string]: {
srcItems: {
programName: string;
item: ProgramAgnosticAddressRewards;
}[];
totalAmount: BigNumber;
};
};
} = {};

input.forEach(({ programName, rewards }) => {
rewards.forEach(reward => {
if (!mergedByChainByUserByProgram[reward.chainId])
mergedByChainByUserByProgram[reward.chainId] = {};

if (!mergedByChainByUserByProgram[reward.chainId][reward.account])
mergedByChainByUserByProgram[reward.chainId][reward.account] = {
srcItems: [],
totalAmount: new BigNumber(0),
};

mergedByChainByUserByProgram[reward.chainId][
reward.account
].srcItems.push({
programName,
item: reward,
});

mergedByChainByUserByProgram[reward.chainId][reward.account].totalAmount =
mergedByChainByUserByProgram[reward.chainId][
reward.account
].totalAmount.plus(reward.amount);
});
});

const result: AddressRewardsWithAmountsByProgramVariation[] = [];

Object.entries(mergedByChainByUserByProgram).forEach(
([chainId, usersData]) => {
Object.entries(usersData).forEach(([user, { srcItems, totalAmount }]) => {
const amountsByProgram: { [program: string]: string } = {};

let breakDownGRP: AddressRewardsGRP['breakDownGRP'] | null = null;
srcItems.forEach(srcItem => {
amountsByProgram[srcItem.programName] = srcItem.item.amount.toFixed();

const itm = srcItem.item;
if (isGRPItem(itm)) {
breakDownGRP = itm.breakDownGRP;
}
});

input.forEach(({ programName }) => {
if (!amountsByProgram[programName]) {
amountsByProgram[programName] = '0';
}
});

result.push({
account: user,
chainId: +chainId,
amount: totalAmount,
amountsByProgram,
debugInfo: srcItems.map(({ item }) => item.debugInfo).filter(Boolean),
breakDownGRP: breakDownGRP || {},
});
});
},
);

// console.log(result);
// TODO: cleanup this
require('fs').writeFileSync(
'tmp.json',
JSON.stringify(result, null, 2),
'utf-8',
);

// debugger;
return result;
}

// accepts list of distribution entries
// returns [extended] list of distribution entries, the items of which are extended with "amount by program" and the amount adjusted respectively
export async function composeWithAmountsByProgram(
epoch: number,
originalGasRefundProgramItems: ProgramAgnosticAddressRewards[],
): Promise<AddressRewardsWithAmountsByProgramVariation[]> {
// 1. compute AddressRewards[] rows for Aura Program
// 2. combined AddressRewardsWithAmountsByProgram[] = Aura + GasRefund
return combinePrograms([
{
programName: 'paraswapGasRefund',
rewards: originalGasRefundProgramItems,
},
{
programName: 'auraRewards',
rewards: await composeAuraRewards(epoch), // TODO: compute Aura rewards
},
]);
}

function asserted<T>(val: T) {
assert(val !== null && val !== undefined, 'val should not be null or undef');
Expand Down Expand Up @@ -220,16 +343,19 @@ export async function computeDistributionMerkleData(
!entry.amount.isNaN(),
);

const allChainsRefunds = composeRefundWithPIP38Refunds(
epoch,
_allChainsRefunds,
);
const withPIP38 = composeRefundWithPIP38Refunds(epoch, _allChainsRefunds);

const allChainsRefunds = await composeWithAmountsByProgram(epoch, withPIP38);

const userGRPChainsBreakDowns = allChainsRefunds.reduce<{
[stakeChainId: number]: AddressRewardsMapping;
[stakeChainId: number]: AddressRewardsMappingWithMaybeGRP;
}>((acc, curr) => {
if (!acc[curr.chainId]) acc[curr.chainId] = {};
acc[curr.chainId][curr.account] = curr.breakDownGRP;
acc[curr.chainId][curr.account] = {
byChain: 'breakDownGRP' in curr ? curr.breakDownGRP : null,
amountsByProgram: curr.amountsByProgram,
// debugInfo: curr.debugInfo,
};

return acc;
}, {});
Expand All @@ -243,12 +369,15 @@ export async function computeDistributionMerkleData(

merkleTreeData.forEach(({ chainId, merkleTree }) => {
merkleTree.merkleProofs.forEach(l => {
const GRPChainBreakDown = userGRPChainsBreakDowns[+chainId][l.address];
const GRPChainBreakDown =
userGRPChainsBreakDowns[+chainId][l.address].byChain;
if (GRPChainBreakDown) {
l.GRPChainBreakDown = stringifyGRPChainBreakDown(GRPChainBreakDown);
l.amountsByProgram =
userGRPChainsBreakDowns[+chainId][l.address].amountsByProgram;
}
l.debugInfo = userGRPChainsBreakDowns[+chainId][l.address].debugInfo;
});
});

return merkleTreeData;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MerkleRedeemAddressSePSP1 } from '../../../../src/lib/gas-refund/gas-re
import { Contract } from 'ethers';
import { Provider } from '../../../../src/lib/provider';
import { ERC20Interface, MerkleRedeemIface, SePSPIface } from './abis';
import { CHAIN_ID_OPTIMISM } from '../../../../src/lib/constants';

export async function computeDistributionSafeProposal(
merkleDistributionData: MerkleTreeAndChain,
Expand Down Expand Up @@ -38,19 +39,27 @@ export async function computeDistributionSafeProposal(
const PSPAddress = await sePSP1.callStatic.asset();

const txs = [
{
to: PSPAddress,
data: ERC20Interface.encodeFunctionData('approve', [
sePSP1Address,
totalAmountRefunded,
]),
value: '0',
},
{
to: sePSP1Address,
data: SePSPIface.encodeFunctionData('deposit', [totalAmountRefunded]),
value: '0',
},
// on optimism no need to obtain sePSP1, as we already have enough from aura rewards
// (was true for EPOCH #016 (47))
...(+chainId === CHAIN_ID_OPTIMISM
? []
: [
{
to: PSPAddress,
data: ERC20Interface.encodeFunctionData('approve', [
sePSP1Address,
totalAmountRefunded,
]),
value: '0',
},
{
to: sePSP1Address,
data: SePSPIface.encodeFunctionData('deposit', [
totalAmountRefunded,
]),
value: '0',
},
]),
{
to: sePSP1Address,
data: ERC20Interface.encodeFunctionData('approve', [
Expand Down
12 changes: 9 additions & 3 deletions scripts/gas-refund-program/distribution/lib/merkle-tree.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Claimable, GasRefundMerkleProof } from './types';
import { Claimable, RewardMerkleProof } from './types';
import { utils, logger } from 'ethers';
import { MerkleTree } from 'merkletreejs';
import { GasRefundTransaction } from '../../../../src/models/GasRefundTransaction';
import BigNumber from 'bignumber.js';
import { MerkleTreeAndChain } from './types';
import { assert } from 'ts-essentials';

export type MinGasRefundTransaction = Pick<
GasRefundTransaction,
Expand All @@ -29,7 +30,11 @@ export async function computeMerkleData({
}[];
}>((acc, curr) => {
if (!acc[curr.chainId]) acc[curr.chainId] = [];
acc[curr.chainId].push(curr);
assert(
curr.amount.isGreaterThanOrEqualTo(0),
`Negative amount for ${curr.account} on chain ${curr.chainId}`,
);
if (!curr.amount.isZero()) acc[curr.chainId].push(curr);

return acc;
}, {});
Expand Down Expand Up @@ -82,7 +87,7 @@ function computeMerkleDataForChain({

const merkleRoot = merkleTree.getHexRoot();

const merkleLeaves: GasRefundMerkleProof[] = allLeaves.map(leaf => {
const merkleLeaves: RewardMerkleProof[] = allLeaves.map(leaf => {
const { address, amount } = hashedClaimabled[leaf];
const proofs = merkleTree.getHexProof(leaf);
return {
Expand All @@ -91,6 +96,7 @@ function computeMerkleDataForChain({
epoch,
proof: proofs,
GRPChainBreakDown: {},
amountsByProgram: {},
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@ import { sliceCalls } from '../../../../src/lib/utils/helpers';
import { GasRefundDistribution } from '../../../../src/models/GasRefundDistribution';
import { GasRefundParticipation } from '../../../../src/models/GasRefundParticipation';
import { GasRefundParticipantData } from '../../../../src/lib/gas-refund/gas-refund';
import { GasRefundMerkleProof, GasRefundMerkleTree } from './types';
import { RewardMerkleProof, RewardMerkleTree } from './types';

export async function storeDistributionDataInDB(
chainId: number,
merkleTree: GasRefundMerkleTree,
merkleTree: RewardMerkleTree,
) {
const {
root: { epoch, merkleRoot, totalAmount },
merkleProofs,
} = merkleTree;

await database.sequelize?.transaction(async t => {
// TODO: revisit
await database.sequelize.query(
`
DELETE FROM "GasRefundDistributions" WHERE epoch = ${epoch} and "chainId"=${chainId};
DELETE FROM "GasRefundParticipations" WHERE epoch = ${epoch} and "chainId"=${chainId};
`,
{
transaction: t,
},
);
await GasRefundDistribution.create(
{
epoch,
Expand All @@ -30,8 +41,15 @@ export async function storeDistributionDataInDB(
);

const epochDataToUpdate: GasRefundParticipantData[] = merkleProofs.map(
(leaf: GasRefundMerkleProof) => {
const { address: account, proof, amount, GRPChainBreakDown } = leaf;
(leaf: RewardMerkleProof) => {
const {
address: account,
proof: merkleProofs,
amount,
GRPChainBreakDown,
amountsByProgram,
debugInfo,
} = leaf;
assert(
account == account.toLowerCase(),
`LOGIC ERROR: ${account} should be lowercased`,
Expand All @@ -40,10 +58,12 @@ export async function storeDistributionDataInDB(
epoch,
address: account,
chainId: chainId,
merkleProofs: proof,
merkleProofs,
isCompleted: true,
amount,
GRPChainBreakDown,
amountsByProgram,
debugInfo,
};
},
);
Expand Down
Loading

0 comments on commit 92ec440

Please sign in to comment.