Skip to content

Commit

Permalink
feat: add redemption rights script, update reward per p token
Browse files Browse the repository at this point in the history
  • Loading branch information
jparklev committed Oct 16, 2024
1 parent ab12c49 commit 8f98784
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 2 deletions.
Binary file added bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions contracts/script/PointTokenVault.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ contract PointTokenVaultScripts is BatchScript {

bytes32 POINTS_ID_ETHENA_SATS_S2 = LibString.packTwo("Rumpel kPt: Ethena S2", "kpSATS-2");
ERC20 ENA = ERC20(0x57e114B691Db790C35207b2e685D4A43181e6061);
uint256 EXCHANGE_RATE = 2e18;
uint256 REWARDS_PER_P_TOKEN = 63381137368827226;
bool REDEMPTION_RIGHTS = true;

pointTokenVault.setRedemption(POINTS_ID_ETHENA_SATS_S2, ENA, EXCHANGE_RATE, REDEMPTION_RIGHTS);
pointTokenVault.setRedemption(POINTS_ID_ETHENA_SATS_S2, ENA, REWARDS_PER_P_TOKEN, REDEMPTION_RIGHTS);

// bytes32 MERKLE_ROOT_WIT_REDEMPTION_RIGHTS = 0x882aaf07b6b16e5f021a498e1a8c5de540e6ffe9345fdc48b51dd79dc894a059;

Expand Down
171 changes: 171 additions & 0 deletions js-scripts/generateRedemptionRights.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import * as fs from "fs";
import * as dotenv from "dotenv";
import {
keccak256,
encodePacked,
createPublicClient,
http,
parseAbiItem,
Address,
zeroAddress,
} from "viem";
import { mainnet } from "viem/chains";
import { MerkleTree } from "merkletreejs";

function solidityKeccak256(types: string[], values: any[]) {
return keccak256(encodePacked(types, values));
}

dotenv.config({ path: "./scripts/.env" });

const MAINNET_RPC_URL = process.env.MAINNET_RPC_URL;
const PTOKEN_ADDRESSES = (process.env.PTOKEN_ADDRESSES as string).split(
","
) as Address[];
const POINTS_IDS = (process.env.POINTS_IDS as string).split(
","
) as `0x${string}`[];

const REDEMPTION_RIGHTS_PREFIX = solidityKeccak256(
["string"],
["REDEMPTION_RIGHTS"]
);

if (
!MAINNET_RPC_URL ||
PTOKEN_ADDRESSES.length === 0 ||
POINTS_IDS.length === 0
) {
console.error(
"Please set MAINNET_RPC_URL, PTOKEN_ADDRESSES, and POINTS_IDS in your .env file"
);
process.exit(1);
}

if (PTOKEN_ADDRESSES.length !== POINTS_IDS.length) {
console.error(
"The number of PTOKEN_ADDRESSES must match the number of POINTS_IDS"
);
process.exit(1);
}

async function generateMerkleTree() {
const publicClient = createPublicClient({
chain: mainnet,
transport: http(MAINNET_RPC_URL),
});

const latestBlock = await publicClient.getBlockNumber();
console.log(`latest block #: ${latestBlock}`);

const allHolders = new Map<string, Map<string, bigint>>();

for (let i = 0; i < PTOKEN_ADDRESSES.length; i++) {
const pTokenAddress = PTOKEN_ADDRESSES[i];
const pointsId = POINTS_IDS[i];

console.log(
`Processing pToken: ${pTokenAddress} with Points ID: ${pointsId}`
);

const logs = await publicClient.getLogs({
address: pTokenAddress,
event: parseAbiItem(
"event Transfer(address indexed from, address indexed to, uint256)"
),
fromBlock: 0n,
toBlock: "latest",
});

const holders = new Map<string, bigint>();

for (const log of logs) {
const [from, to, value] = log.args as [Address, Address, bigint];
if (!from || !to || value === undefined) {
throw new Error("Transfer event args are undefined");
}

if (from !== zeroAddress) {
const fromBalance = (holders.get(from) || 0n) - value;
holders.set(from, fromBalance);
}

if (to !== zeroAddress) {
const toBalance = (holders.get(to) || 0n) + value;
holders.set(to, toBalance);
}
}

for (const [address, balance] of holders.entries()) {
if (balance > 0n) {
if (!allHolders.has(address)) {
allHolders.set(address, new Map());
}
allHolders.get(address)!.set(pointsId, balance);
}
}
}

// TODO: for ptokens being LP'd in Uni, give the redemption rights to the LPs themselves rather than the pool address

console.log("Generating Merkle tree...");

const leaves = Array.from(allHolders.entries()).flatMap(
([address, balances]) =>
Array.from(balances.entries()).map(([pointsId, balance]) =>
solidityKeccak256(
["bytes32", "address", "bytes32", "uint256"],
[REDEMPTION_RIGHTS_PREFIX, address, pointsId, balance.toString()]
)
)
);

const sortedLeaves = leaves.sort((a, b) => a.localeCompare(b));
const tree = new MerkleTree(sortedLeaves, keccak256, { sortPairs: true });
const root = tree.getHexRoot();

const merklizedPointsData = {
wallets: Object.fromEntries(
Array.from(allHolders.entries()).map(([userAddress, balances]) => [
userAddress,
Object.fromEntries(
Array.from(balances.entries()).map(([pointsId, balance]) => {
const leaf = solidityKeccak256(
["bytes32", "address", "bytes32", "uint256"],
[
REDEMPTION_RIGHTS_PREFIX,
userAddress,
pointsId,
balance.toString(),
]
);
return [
pointsId,
{
amount: balance.toString(),
proof: tree.getHexProof(leaf),
},
];
})
),
])
),
root,
};

console.log("Merkle tree generated successfully.");
console.log("Root:", root);
console.log(
"Total number of holders:",
Object.keys(merklizedPointsData.wallets).length
);

// Save the merklizedPointsData to a JSON file
fs.writeFileSync(
"merklizedPointsData.json",
JSON.stringify(merklizedPointsData, null, 2)
);
console.log("Merkle data saved to merklizedPointsData.json");
}

generateMerkleTree().catch(console.error);
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "point-tokenization-vault-scripts",
"module": "index.ts",
"type": "module",
"scripts": {
"generate-merkle-tree": "bun run js-scripts/generateRedemptionRights.ts"
},
"devDependencies": {
"bun-types": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"dotenv": "^16.3.1",
"merkletreejs": "^0.4.0",
"viem": "^2.21.25"
}
}

0 comments on commit 8f98784

Please sign in to comment.