|
| 1 | +/** |
| 2 | + * Fund OltinStaking.rewardPool to a target balance. |
| 3 | + * |
| 4 | + * Idempotent: ensures rewardPool >= FUND_AMOUNT_OLTIN. Mints OLTIN to admin |
| 5 | + * if balance is insufficient, approves the staking contract, then calls |
| 6 | + * fundRewardPool with the delta. Skips early if pool is already at target. |
| 7 | + * |
| 8 | + * Run: |
| 9 | + * PRIVATE_KEY=0x... npx hardhat run scripts/fundRewardPool.ts --network zkSyncSepolia |
| 10 | + * |
| 11 | + * Override amount (default 1000): |
| 12 | + * FUND_AMOUNT_OLTIN=2000 npx hardhat run scripts/fundRewardPool.ts --network zkSyncSepolia |
| 13 | + */ |
| 14 | +// Hardhat loads contracts/.env via hardhat.config.ts (dotenv.config()) before |
| 15 | +// running any script, so PRIVATE_KEY is already in process.env here. |
| 16 | +import { Wallet, Provider, Contract } from "zksync-ethers"; |
| 17 | + |
| 18 | +const OLTIN_ADDRESS = "0x4A56B78DBFc2E6c914f5413B580e86ee1A474347"; |
| 19 | +const STAKING_ADDRESS = "0x63e537A3a150d06035151E29904C1640181C8314"; |
| 20 | +const FUND_ORDER_ID = "fund-reward-pool"; |
| 21 | + |
| 22 | +const OLTIN_ABI = [ |
| 23 | + "function mint(address to, uint256 amount, string orderId)", |
| 24 | + "function balanceOf(address) view returns (uint256)", |
| 25 | + "function approve(address spender, uint256 amount) returns (bool)", |
| 26 | + "function allowance(address owner, address spender) view returns (uint256)", |
| 27 | +]; |
| 28 | + |
| 29 | +const STAKING_ABI = [ |
| 30 | + "function fundRewardPool(uint256 amount)", |
| 31 | + "function rewardPool() view returns (uint256)", |
| 32 | +]; |
| 33 | + |
| 34 | +function formatOltin(wei: bigint): string { |
| 35 | + return `${(Number(wei) / 1e18).toFixed(4)} OLTIN`; |
| 36 | +} |
| 37 | + |
| 38 | +async function main() { |
| 39 | + const privateKey = process.env.PRIVATE_KEY; |
| 40 | + if (!privateKey) { |
| 41 | + throw new Error("PRIVATE_KEY not set in contracts/.env"); |
| 42 | + } |
| 43 | + |
| 44 | + const fundAmountRaw = process.env.FUND_AMOUNT_OLTIN ?? "1000"; |
| 45 | + if (!/^\d+$/.test(fundAmountRaw)) { |
| 46 | + throw new Error( |
| 47 | + `FUND_AMOUNT_OLTIN must be a positive integer, got: ${fundAmountRaw}`, |
| 48 | + ); |
| 49 | + } |
| 50 | + const fundAmountOltin = BigInt(fundAmountRaw); |
| 51 | + const targetAmount = fundAmountOltin * 10n ** 18n; |
| 52 | + |
| 53 | + const provider = new Provider("https://sepolia.era.zksync.dev"); |
| 54 | + const wallet = new Wallet(privateKey, provider); |
| 55 | + |
| 56 | + console.log("=== Fund OltinStaking.rewardPool ==="); |
| 57 | + console.log(`Admin: ${wallet.address}`); |
| 58 | + console.log(`Target rewardPool: ${formatOltin(targetAmount)}`); |
| 59 | + |
| 60 | + const oltin = new Contract(OLTIN_ADDRESS, OLTIN_ABI, wallet); |
| 61 | + const staking = new Contract(STAKING_ADDRESS, STAKING_ABI, wallet); |
| 62 | + |
| 63 | + // 1. Check current rewardPool — skip if already funded. |
| 64 | + const rewardPoolBefore: bigint = await staking.rewardPool(); |
| 65 | + console.log(`\nCurrent rewardPool: ${formatOltin(rewardPoolBefore)}`); |
| 66 | + |
| 67 | + if (rewardPoolBefore >= targetAmount) { |
| 68 | + console.log("Already at or above target. Nothing to do."); |
| 69 | + return; |
| 70 | + } |
| 71 | + |
| 72 | + const toFund = targetAmount - rewardPoolBefore; |
| 73 | + console.log(`Need to add: ${formatOltin(toFund)}`); |
| 74 | + |
| 75 | + // 2. Read admin's OLTIN balance and current allowance in parallel. |
| 76 | + const [balance, currentAllowance]: [bigint, bigint] = await Promise.all([ |
| 77 | + oltin.balanceOf(wallet.address), |
| 78 | + oltin.allowance(wallet.address, STAKING_ADDRESS), |
| 79 | + ]); |
| 80 | + console.log(`Admin OLTIN balance: ${formatOltin(balance)}`); |
| 81 | + |
| 82 | + // 3. Mint OLTIN to admin if balance insufficient. |
| 83 | + if (balance < toFund) { |
| 84 | + const toMint = toFund - balance; |
| 85 | + console.log(`\nMinting ${formatOltin(toMint)} to admin...`); |
| 86 | + const mintTx = await oltin.mint(wallet.address, toMint, FUND_ORDER_ID); |
| 87 | + console.log(` tx: ${mintTx.hash}`); |
| 88 | + await mintTx.wait(); |
| 89 | + console.log(` ✓ Minted`); |
| 90 | + } |
| 91 | + |
| 92 | + // 4. Approve staking contract for the delta. |
| 93 | + if (currentAllowance < toFund) { |
| 94 | + console.log(`\nApproving ${formatOltin(toFund)} for staking contract...`); |
| 95 | + const approveTx = await oltin.approve(STAKING_ADDRESS, toFund); |
| 96 | + console.log(` tx: ${approveTx.hash}`); |
| 97 | + await approveTx.wait(); |
| 98 | + console.log(` ✓ Approved`); |
| 99 | + } |
| 100 | + |
| 101 | + // 5. Fund the reward pool. |
| 102 | + console.log(`\nCalling fundRewardPool(${formatOltin(toFund)})...`); |
| 103 | + const fundTx = await staking.fundRewardPool(toFund); |
| 104 | + console.log(` tx: ${fundTx.hash}`); |
| 105 | + await fundTx.wait(); |
| 106 | + console.log(` ✓ Funded`); |
| 107 | + |
| 108 | + // 6. Verify final state. |
| 109 | + const rewardPoolAfter: bigint = await staking.rewardPool(); |
| 110 | + console.log( |
| 111 | + `\nRewardPool: ${formatOltin(rewardPoolBefore)} → ${formatOltin(rewardPoolAfter)}`, |
| 112 | + ); |
| 113 | + |
| 114 | + console.log("\n=== Done ==="); |
| 115 | +} |
| 116 | + |
| 117 | +main().catch((e: unknown) => { |
| 118 | + console.error(e instanceof Error ? e.message : e); |
| 119 | + process.exit(1); |
| 120 | +}); |
0 commit comments