diff --git a/config/chains.json b/config/chains.json index 61802ee..f604ca2 100644 --- a/config/chains.json +++ b/config/chains.json @@ -52,22 +52,24 @@ "contracts": { "nonfungiblePositionManager": "0x84715977598247125C3D6E2e85370d1F6fDA1eaF", "factory": "0xC5396866754799B9720125B104AE01d935Ab9C7b", - "poolDeployer": "0xE08026Fd8537d67C501199610c42D08bB34eAa75" + "poolDeployer": "0xE08026Fd8537d67C501199610c42D08bB34eAa75", + "syrupFactory": "0x4880c9ff216ae69Cb1Fc717575d824314Ed862a9" }, "modules": { "walletQuick": true, - "syrupStaking": false, + "syrupStaking": true, "algebraIntegralV4": true, "liquidityManagers": true, "v2LPStaking": true }, "deployed": { - "_updatedAt": "2025-12-24T02:34:39.737Z", - "aggregator": "0xda13ca59c0806c6093cf07e6f29b379e31adc181", + "_updatedAt": "2026-01-13T12:41:42.267Z", + "aggregator": "0x9975bc2fa6590620aeee87bd7e7e4bec0001095c", "walletQuick": "0xc52f200f29ac8f37d23ee00a31e6cdafdbd71506", "algebraIntegralV4": "0xacd0d266d504ebbdf4ccd141673a8bd9630b97c8", "liquidityManagers": "0x22729e385310f035b54866c270d9f0b2cf0b6d64", - "v2LPStaking": "0x559781f2b6d8b9499626317d68858f99e45437be" + "v2LPStaking": "0x559781f2b6d8b9499626317d68858f99e45437be", + "syrupStaking": "0x4e0acd3980a601821b9724586cd3da81cdc7f33a" } }, "ethereum": { diff --git a/contracts/modules/SyrupStakingModule.sol b/contracts/modules/SyrupStakingModule.sol index aeec3e8..97240ad 100644 --- a/contracts/modules/SyrupStakingModule.sol +++ b/contracts/modules/SyrupStakingModule.sol @@ -7,9 +7,8 @@ import "./Ownable.sol"; /** * @title SyrupStakingModule * @notice Counts QUICK staked in syrup pools - * @dev Uses unbounded factory enumeration (like Voting10) + legacy allowlist + * @dev Uses factory enumeration (like Voting10) + legacy allowlist for pools not in factory * - * Compatible chains: Polygon */ interface IStakingRewardsFactory { @@ -21,10 +20,10 @@ interface IStakingRewardsFactory { contract SyrupStakingModule is IVotingModule, Ownable { /// @notice Max factory pools to enumerate (gas protection + alert threshold) - uint256 public constant MAX_FACTORY_POOLS = 100; + uint256 public constant MAX_FACTORY_POOLS = 50; - /// @notice Max legacy pools allowed - uint256 public constant MAX_LEGACY_POOLS = 50; + /// @notice Max legacy pools allowed (pools not deployed via Factory) + uint256 public constant MAX_LEGACY_POOLS = 20; /// @notice Syrup factory for dynamic enumeration address public factory; diff --git a/scripts/check-syrup-module-status.ts b/scripts/check-syrup-module-status.ts new file mode 100644 index 0000000..4c7eb03 --- /dev/null +++ b/scripts/check-syrup-module-status.ts @@ -0,0 +1,286 @@ +/** + * Check SyrupStakingModule status for any chain + * Reads configuration from chains.json or accepts custom addresses + * + * Usage: + * pnpm exec tsx scripts/check-syrup-module-status.ts # Uses Base from chains.json + * pnpm exec tsx scripts/check-syrup-module-status.ts --chain polygon # Uses Polygon from chains.json + * pnpm exec tsx scripts/check-syrup-module-status.ts --module 0x... # Custom module address + * pnpm exec tsx scripts/check-syrup-module-status.ts --factory 0x... # Custom factory address + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { createPublicClient, http, parseAbi, formatEther, Address, Chain } from "viem"; +import { base, polygon, mainnet } from "viem/chains"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const CHAINS_CONFIG_PATH = path.join(__dirname, "..", "config", "chains.json"); + +interface ChainConfig { + chainId: number; + name: string; + rpcEnvVar: string; + defaultRpc: string; + contracts: { + syrupFactory?: string; + }; + deployed: { + syrupStaking?: string; + }; +} + +interface ChainsConfig { + chains: Record; +} + +const CHAIN_MAP: Record = { + 137: polygon, + 8453: base, + 1: mainnet, +}; + +const moduleAbi = parseAbi([ + "function balanceOf(address account) view returns (uint256)", + "function factory() view returns (address)", + "function legacyPoolsLength() view returns (uint256)", + "function getFactoryStatus() view returns (uint256 currentCount, uint256 maxCount, bool nearLimit)", + "function MAX_FACTORY_POOLS() view returns (uint256)", + "function owner() view returns (address)", +]); + +const factoryAbi = parseAbi([ + "function rewardTokens(uint256 index) view returns (address)", + "function stakingRewardsInfoByRewardToken(address rewardToken) view returns (address stakingRewards, uint256 rewardAmount, uint256 duration)", +]); + +function parseArgs(): { chain: string; module?: string; factory?: string; user?: string } { + const args = process.argv.slice(2); + const result: { chain: string; module?: string; factory?: string; user?: string } = { + chain: "base", // default + }; + + for (let i = 0; i < args.length; i++) { + if (args[i] === "--chain" && args[i + 1]) { + result.chain = args[i + 1]; + i++; + } else if (args[i] === "--module" && args[i + 1]) { + result.module = args[i + 1]; + i++; + } else if (args[i] === "--factory" && args[i + 1]) { + result.factory = args[i + 1]; + i++; + } else if (args[i] === "--user" && args[i + 1]) { + result.user = args[i + 1]; + i++; + } else if (args[i] === "--help" || args[i] === "-h") { + console.log(` +Usage: pnpm exec tsx scripts/check-syrup-module-status.ts [options] + +Options: + --chain Chain name from chains.json (default: base) + --module Override SyrupStakingModule address + --factory Override Factory address + --user Test user address for balanceOf checks + --help Show this help + +Examples: + pnpm exec tsx scripts/check-syrup-module-status.ts + pnpm exec tsx scripts/check-syrup-module-status.ts --chain polygon + pnpm exec tsx scripts/check-syrup-module-status.ts --module 0x1234... +`); + process.exit(0); + } + } + + return result; +} + +async function main() { + const args = parseArgs(); + + // Load chains config + const chainsConfig: ChainsConfig = JSON.parse(fs.readFileSync(CHAINS_CONFIG_PATH, "utf8")); + const chainConfig = chainsConfig.chains[args.chain]; + + if (!chainConfig) { + console.error(`❌ Chain "${args.chain}" not found in chains.json`); + console.error(` Available chains: ${Object.keys(chainsConfig.chains).join(", ")}`); + process.exit(1); + } + + // Determine addresses + const moduleAddress = (args.module || chainConfig.deployed.syrupStaking) as Address | undefined; + const factoryAddress = (args.factory || chainConfig.contracts.syrupFactory) as Address | undefined; + const testUser = args.user as Address | undefined; + + if (!moduleAddress) { + console.error(`❌ No SyrupStakingModule address found for ${args.chain}`); + console.error(` Either deploy one or use --module
`); + process.exit(1); + } + + console.log("╔═══════════════════════════════════════════════════════════════╗"); + console.log(`║ SyrupStakingModule Status - ${chainConfig.name.padEnd(23)} ║`); + console.log("╚═══════════════════════════════════════════════════════════════╝\n"); + + // Create client + const viemChain = CHAIN_MAP[chainConfig.chainId]; + const rpcUrl = process.env[chainConfig.rpcEnvVar] || chainConfig.defaultRpc; + + const client = createPublicClient({ + chain: viemChain, + transport: http(rpcUrl), + }); + + // ======= Module Info ======= + console.log("📦 Module Information"); + console.log("─".repeat(50)); + console.log(" Address:", moduleAddress); + + let moduleFactory: Address | undefined; + try { + moduleFactory = await client.readContract({ + address: moduleAddress, + abi: moduleAbi, + functionName: "factory", + }); + console.log(" Factory:", moduleFactory); + } catch { + console.log(" Factory: (not available)"); + } + + let maxPools: bigint = 100n; + try { + maxPools = await client.readContract({ + address: moduleAddress, + abi: moduleAbi, + functionName: "MAX_FACTORY_POOLS", + }); + console.log(" MAX_FACTORY_POOLS:", maxPools.toString()); + } catch { + console.log(" MAX_FACTORY_POOLS: (no getter, likely 100)"); + } + + try { + const legacyLen = await client.readContract({ + address: moduleAddress, + abi: moduleAbi, + functionName: "legacyPoolsLength", + }); + console.log(" Legacy Pools:", legacyLen.toString()); + } catch { + console.log(" Legacy Pools: (not available)"); + } + + try { + const owner = await client.readContract({ + address: moduleAddress, + abi: moduleAbi, + functionName: "owner", + }); + console.log(" Owner:", owner); + } catch { + // Skip if no owner function + } + console.log(); + + // ======= Factory Status ======= + const effectiveFactory = moduleFactory || factoryAddress; + + if (effectiveFactory && effectiveFactory !== "0x0000000000000000000000000000000000000000") { + console.log("🏭 Factory Pool Analysis"); + console.log("─".repeat(50)); + console.log(" Factory Address:", effectiveFactory); + console.log(); + + // Count actual pools in factory + let poolCount = 0; + const pools: { index: number; rewardToken: Address; stakingRewards: Address }[] = []; + + for (let i = 0; i < Number(maxPools) + 10; i++) { + try { + const rewardToken = await client.readContract({ + address: effectiveFactory, + abi: factoryAbi, + functionName: "rewardTokens", + args: [BigInt(i)], + }); + + const [stakingRewards] = await client.readContract({ + address: effectiveFactory, + abi: factoryAbi, + functionName: "stakingRewardsInfoByRewardToken", + args: [rewardToken], + }); + + pools.push({ index: i, rewardToken, stakingRewards }); + poolCount++; + } catch { + // Array out of bounds - no more pools + break; + } + } + + console.log(" ✅ Total Pools in Factory:", poolCount); + console.log(); + + if (pools.length > 0 && pools.length <= 10) { + console.log(" Pool Details:"); + for (const pool of pools) { + console.log(` [${pool.index}] Reward: ${pool.rewardToken.slice(0, 10)}... → Pool: ${pool.stakingRewards}`); + } + console.log(); + } else if (pools.length > 10) { + console.log(` (${pools.length} pools - showing first 5)`); + for (const pool of pools.slice(0, 5)) { + console.log(` [${pool.index}] Reward: ${pool.rewardToken.slice(0, 10)}... → Pool: ${pool.stakingRewards}`); + } + console.log(); + } + } + + // ======= getFactoryStatus ======= + console.log("📊 Module's getFactoryStatus()"); + console.log("─".repeat(50)); + + try { + const [currentCount, maxCount, nearLimit] = await client.readContract({ + address: moduleAddress, + abi: moduleAbi, + functionName: "getFactoryStatus", + }); + console.log(" Current Pool Count:", currentCount.toString()); + console.log(" Max Pool Limit:", maxCount.toString()); + console.log(" Near Limit:", nearLimit ? "⚠️ YES" : "No"); + } catch { + console.log(" (getFactoryStatus not available)"); + } + console.log(); + + // ======= Test balanceOf ======= + if (testUser) { + console.log("👤 Balance Check"); + console.log("─".repeat(50)); + console.log(" User:", testUser); + + try { + const balance = await client.readContract({ + address: moduleAddress, + abi: moduleAbi, + functionName: "balanceOf", + args: [testUser], + }); + console.log(` Balance: ✅ ${formatEther(balance)} QUICK`); + } catch (e: any) { + console.log(` Balance: ❌ FAILED - ${e.shortMessage || e.message?.slice(0, 100)}`); + } + console.log(); + } + + // ======= Summary ======= + console.log("═".repeat(60)); + console.log("✅ Status check complete"); +} + +main().catch(console.error); diff --git a/scripts/deploy/base-syrup-module.ts b/scripts/deploy/base-syrup-module.ts new file mode 100644 index 0000000..1f861ab --- /dev/null +++ b/scripts/deploy/base-syrup-module.ts @@ -0,0 +1,241 @@ +/** + * Deploy SyrupStakingModule on Base and configure it in the existing aggregator + * + * Usage: + * pnpm exec hardhat run scripts/deploy/base-syrup-module.ts --network base + * FACTORY=0x... pnpm exec hardhat run scripts/deploy/base-syrup-module.ts --network base + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import readline from "readline"; +import { type Address, createWalletClient, formatEther, http, isAddress } from "viem"; +import { base } from "viem/chains"; +import hre from "hardhat"; +import type { DeployContractConfig, WalletClient } from "@nomicfoundation/hardhat-viem/types"; +import { deploySyrupStakingModule, ZERO_ADDRESS } from "./deployers.js"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const CHAINS_CONFIG_PATH = path.join(__dirname, "..", "..", "config", "chains.json"); +const CHAINS_CONFIG = JSON.parse(fs.readFileSync(CHAINS_CONFIG_PATH, "utf8")); + +const BASE_CONFIG = CHAINS_CONFIG.chains.base; +const OWNER_ADDRESS = (process.env.OWNER_ADDRESS || CHAINS_CONFIG.owner) as Address; + +async function promptFactoryAddress(): Promise
{ + // 1. Check if factory is in chains.json + const configFactory = BASE_CONFIG.contracts?.syrupFactory; + + // 2. Check if FACTORY env var is set + const envFactory = process.env.FACTORY; + + if (envFactory) { + const addr = envFactory.toLowerCase(); + if (addr === "0x0" || addr === "0x0000000000000000000000000000000000000000") { + console.log(" Using address(0) from FACTORY env var\n"); + return ZERO_ADDRESS; + } + if (!isAddress(envFactory)) { + throw new Error(`Invalid FACTORY address: ${envFactory}`); + } + console.log(` Using factory from FACTORY env var: ${envFactory}\n`); + return envFactory as Address; + } + + // 3. Interactive prompt + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + const prompt = (query: string): Promise => { + return new Promise(resolve => rl.question(query, resolve)); + }; + + if (configFactory && configFactory !== ZERO_ADDRESS) { + console.log(` Factory in chains.json: ${configFactory}`); + const answer = await prompt(" Press Enter to use it, or paste different address (or '0x0' for none): "); + rl.close(); + + if (!answer || answer.trim() === "") { + console.log(` Using: ${configFactory}\n`); + return configFactory as Address; + } + + const addr = answer.trim().toLowerCase(); + if (addr === "0x0" || addr === "0x0000000000000000000000000000000000000000") { + console.log(" Using: address(0)\n"); + return ZERO_ADDRESS; + } + + if (!isAddress(answer.trim())) { + throw new Error(`Invalid address: ${answer}`); + } + + console.log(` Using: ${answer.trim()}\n`); + return answer.trim() as Address; + } else { + console.log(" No factory configured in chains.json"); + const answer = await prompt(" Enter factory address (or '0x0' for none): "); + rl.close(); + + const addr = answer.trim().toLowerCase(); + if (addr === "0x0" || addr === "0x0000000000000000000000000000000000000000") { + console.log(" Using: address(0)\n"); + return ZERO_ADDRESS; + } + + if (!isAddress(answer.trim())) { + throw new Error(`Invalid address: ${answer}`); + } + + console.log(` Using: ${answer.trim()}\n`); + return answer.trim() as Address; + } +} + +async function main() { + console.log("🚀 Base SyrupStaking Module Deployment"); + console.log("=".repeat(60)); + + // Load deployer account from keystore + const { getAccount } = await import("../utils/keystore.js"); + const deployerAccount = await getAccount(); + + // Create network connection + const connection = await hre.network.connect(); + const hhViem = connection.viem; + + // Setup clients + const rpcUrl = (process.env.BASE_RPC || BASE_CONFIG.defaultRpc) as string; + const publicClient = await hhViem.getPublicClient({ chain: base, transport: http(rpcUrl) }); + const walletClient = createWalletClient({ + account: deployerAccount, + chain: base, + transport: http(rpcUrl) + }) as unknown as WalletClient; + + const deployConfig: DeployContractConfig = { + client: { wallet: walletClient, public: publicClient } + }; + + console.log(` Chain: ${BASE_CONFIG.name} (${BASE_CONFIG.chainId})`); + console.log(` Deployer: ${deployerAccount.address}`); + console.log(` Owner: ${OWNER_ADDRESS}`); + + const balance = await publicClient.getBalance({ address: deployerAccount.address }); + console.log(` Balance: ${formatEther(balance)} ETH\n`); + + // Get factory address (from config, env, or prompt) + const factoryAddress = await promptFactoryAddress(); + + // Load legacy pools from allowlist (should be empty for Base) + const allowlistPath = path.join(__dirname, "..", "..", "deployments", "allowlists", "base.json"); + const allowlist = JSON.parse(fs.readFileSync(allowlistPath, "utf8")); + const legacyPools = allowlist.syrupLegacyPools?.addresses || []; + + if (legacyPools.length > 0) { + console.log(` Legacy pools: ${legacyPools.length}`); + } + + // Deploy SyrupStakingModule + console.log("📦 Deploying SyrupStakingModule...\n"); + const syrupModule = await deploySyrupStakingModule( + hhViem, + OWNER_ADDRESS, + factoryAddress, + legacyPools, + deployConfig + ); + + console.log(` ✅ SyrupStakingModule deployed: ${syrupModule.address}\n`); + + // Update chains.json + if (factoryAddress !== ZERO_ADDRESS) { + CHAINS_CONFIG.chains.base.contracts.syrupFactory = factoryAddress; + } + CHAINS_CONFIG.chains.base.modules.syrupStaking = true; + CHAINS_CONFIG.chains.base.deployed.syrupStaking = syrupModule.address; + CHAINS_CONFIG.chains.base.deployed._updatedAt = new Date().toISOString(); + + fs.writeFileSync(CHAINS_CONFIG_PATH, JSON.stringify(CHAINS_CONFIG, null, 2) + "\n"); + console.log("✅ Updated: config/chains.json\n"); + + // Save deployment backup + const outputDir = path.join(__dirname, "..", "..", "deployments"); + const backupFile = path.join(outputDir, `base-syrup-${Date.now()}.json`); + + fs.writeFileSync(backupFile, JSON.stringify({ + chain: "base", + chainId: BASE_CONFIG.chainId, + deployer: deployerAccount.address, + owner: OWNER_ADDRESS, + deployedAt: new Date().toISOString(), + contracts: { + syrupStaking: { + address: syrupModule.address, + name: syrupModule.name, + args: syrupModule.args, + } + }, + factory: factoryAddress, + aggregator: BASE_CONFIG.deployed.aggregator, + }, null, 2)); + + console.log(`📄 Backup: ${backupFile}\n`); + + console.log("=".repeat(60)); + console.log("🎯 NEXT STEPS:"); + console.log("=".repeat(60)); + + const aggregatorAddress = BASE_CONFIG.deployed.aggregator; + + // Generate verify-args file + const verifyArgsDir = path.join(__dirname, "..", "..", "verify-args"); + fs.mkdirSync(verifyArgsDir, { recursive: true }); + const verifyArgsFile = path.join(verifyArgsDir, "base-syrup-module.js"); + fs.writeFileSync(verifyArgsFile, `/** + * Constructor arguments for SyrupStakingModule on Base + * Generated: ${new Date().toISOString()} + * Contract: ${syrupModule.address} + */ +module.exports = [ + "${OWNER_ADDRESS}", // owner + "${factoryAddress}", // factory + [] // legacyPools +]; +`); + console.log(`📄 Created: verify-args/base-syrup-module.js\n`); + + console.log("1️⃣ Verify the contract on Basescan:\n"); + console.log(` pnpm exec hardhat verify --network base \\`); + console.log(` --constructor-args-path verify-args/base-syrup-module.js \\`); + console.log(` ${syrupModule.address}\n`); + + console.log("2️⃣ Update the BaseAggregator to use this module:\n"); + console.log(` Aggregator Address: ${aggregatorAddress}`); + console.log(` Function: setSyrupStakingModule(address module)`); + console.log(` Argument: ${syrupModule.address}\n`); + + console.log(" 📋 Via Safe Multisig:"); + console.log(` - Target: ${aggregatorAddress}`); + console.log(` - Function: setSyrupStakingModule`); + console.log(` - Module: ${syrupModule.address}\n`); + + console.log(" 🔧 Or directly (if you're the owner):"); + console.log(` pnpm exec hardhat run scripts/update-aggregator-module.ts --network base\n`); + + console.log("3️⃣ Test the aggregator in Snapshot playground:\n"); + console.log(` Strategy: erc20-balance-of`); + console.log(` Network: 8453`); + console.log(` Address: ${aggregatorAddress}\n`); + + console.log("=".repeat(60)); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); + diff --git a/scripts/deploy/redeploy-aggregator.ts b/scripts/deploy/redeploy-aggregator.ts index 86d5346..042cbba 100644 --- a/scripts/deploy/redeploy-aggregator.ts +++ b/scripts/deploy/redeploy-aggregator.ts @@ -171,12 +171,35 @@ async function main() { }, null, 2)); console.log(`📄 Backup: ${backupFile}`); + // Generate verify-args file + const verifyArgsDir = path.join(__dirname, "..", "..", "verify-args"); + fs.mkdirSync(verifyArgsDir, { recursive: true }); + const verifyArgsFile = path.join(verifyArgsDir, `${chainKey}-aggregator.js`); + + fs.writeFileSync(verifyArgsFile, `/** + * Constructor arguments for ${chainKey === "polygon" ? "Polygon" : "Base"}Aggregator + * Generated: ${new Date().toISOString()} + * Contract: ${aggregator.address} + */ +module.exports = [ + "${OWNER}", // owner +${Object.entries(modules).map(([name, addr]) => ` "${addr}", // ${name}`).join("\n")} +]; +`); + console.log(`📄 Created: verify-args/${chainKey}-aggregator.js`); + console.log(""); console.log("=".repeat(60)); console.log("🎯 NEXT STEPS:"); - console.log(` 1. Verify contract on explorer`); - console.log(` 2. Test in Snapshot playground: ${aggregator.address}`); - console.log(` 3. Update Snapshot strategy`); + console.log(""); + console.log("1️⃣ Verify contract:"); + console.log(` pnpm exec hardhat verify --network ${chainKey} \\`); + console.log(` --constructor-args-path verify-args/${chainKey}-aggregator.js \\`); + console.log(` ${aggregator.address}`); + console.log(""); + console.log(`2️⃣ Test in Snapshot playground: ${aggregator.address}`); + console.log(""); + console.log(`3️⃣ Update Snapshot strategy`); } main().catch((e) => { diff --git a/scripts/update-aggregator-module.ts b/scripts/update-aggregator-module.ts new file mode 100644 index 0000000..fdc4124 --- /dev/null +++ b/scripts/update-aggregator-module.ts @@ -0,0 +1,167 @@ +/** + * Update BaseAggregator to configure the SyrupStakingModule + * + * Usage: + * pnpm exec hardhat run scripts/update-aggregator-module.ts --network base + * + * Prerequisites: + * - SyrupStakingModule must be deployed + * - You must be the owner of the aggregator (or use a Safe multisig) + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { type Address, createWalletClient, http, parseAbi } from "viem"; +import { base } from "viem/chains"; +import hre from "hardhat"; +import type { WalletClient } from "@nomicfoundation/hardhat-viem/types"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const CHAINS_CONFIG_PATH = path.join(__dirname, "..", "config", "chains.json"); +const CHAINS_CONFIG = JSON.parse(fs.readFileSync(CHAINS_CONFIG_PATH, "utf8")); +const BASE_CONFIG = CHAINS_CONFIG.chains.base; + +// ABI for the aggregator function we need +const AGGREGATOR_ABI = parseAbi([ + 'function setSyrupStakingModule(address module) external', + 'function getModuleAddresses() external view returns (address walletQuick, address syrupStaking, address algebraIntegral, address liquidityManagers, address v2LPStaking)', + 'function owner() external view returns (address)', +]); + +async function main() { + console.log("🔧 Update BaseAggregator SyrupStaking Module"); + console.log("=".repeat(60)); + + const aggregatorAddress = BASE_CONFIG.deployed.aggregator as Address; + const syrupModuleAddress = BASE_CONFIG.deployed.syrupStaking as Address; + + if (!aggregatorAddress) { + throw new Error("BaseAggregator not deployed. Check config/chains.json"); + } + + if (!syrupModuleAddress) { + throw new Error("SyrupStakingModule not deployed. Run: pnpm exec hardhat run scripts/deploy/base-syrup-module.ts --network base"); + } + + console.log(` Aggregator: ${aggregatorAddress}`); + console.log(` SyrupModule: ${syrupModuleAddress}\n`); + + // Load deployer account from keystore + const { getAccount } = await import("./utils/keystore.js"); + const deployerAccount = await getAccount(); + + // Create network connection + const connection = await hre.network.connect(); + const hhViem = connection.viem; + + const rpcUrl = (process.env.BASE_RPC || BASE_CONFIG.defaultRpc) as string; + const publicClient = await hhViem.getPublicClient({ chain: base, transport: http(rpcUrl) }); + const walletClient = createWalletClient({ + account: deployerAccount, + chain: base, + transport: http(rpcUrl) + }) as unknown as WalletClient; + + console.log(` Signer: ${deployerAccount.address}\n`); + + // Check current configuration + console.log("📋 Checking current aggregator configuration...\n"); + + const [currentOwner, moduleAddresses] = await Promise.all([ + publicClient.readContract({ + address: aggregatorAddress, + abi: AGGREGATOR_ABI, + functionName: 'owner', + }), + publicClient.readContract({ + address: aggregatorAddress, + abi: AGGREGATOR_ABI, + functionName: 'getModuleAddresses', + }), + ]); + + console.log(` Current Owner: ${currentOwner}`); + console.log(` Current WalletQuick: ${moduleAddresses[0]}`); + console.log(` Current SyrupStaking: ${moduleAddresses[1]}`); + console.log(` Current AlgebraV4: ${moduleAddresses[2]}`); + console.log(` Current LiqManagers: ${moduleAddresses[3]}`); + console.log(` Current V2LPStaking: ${moduleAddresses[4]}\n`); + + // Check if signer is the owner + if (currentOwner.toLowerCase() !== deployerAccount.address.toLowerCase()) { + console.log("⚠️ WARNING: You are not the owner of the aggregator!"); + console.log(` Owner: ${currentOwner}`); + console.log(` Signer: ${deployerAccount.address}\n`); + console.log(" This transaction will fail unless you use the owner account."); + console.log(" If the owner is a Safe multisig, use the Safe UI instead:\n"); + console.log(" 📋 Safe Transaction:"); + console.log(` - Target: ${aggregatorAddress}`); + console.log(` - Function: setSyrupStakingModule(address)`); + console.log(` - Module: ${syrupModuleAddress}\n`); + return; + } + + // Check if already configured + if (moduleAddresses[1].toLowerCase() === syrupModuleAddress.toLowerCase()) { + console.log("✅ SyrupStakingModule is already configured in the aggregator!"); + console.log(" No action needed.\n"); + return; + } + + // Update the aggregator + console.log("📝 Updating aggregator configuration...\n"); + + const hash = await walletClient.writeContract({ + address: aggregatorAddress, + abi: AGGREGATOR_ABI, + functionName: 'setSyrupStakingModule', + args: [syrupModuleAddress], + }); + + console.log(` 📤 Transaction sent: ${hash}\n`); + console.log(" ⏳ Waiting for confirmation...\n"); + + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + + if (receipt.status === 'success') { + console.log(" ✅ Transaction confirmed!\n"); + } else { + console.log(" ❌ Transaction failed!\n"); + throw new Error("Transaction reverted"); + } + + // Verify the update + console.log("🔍 Verifying update...\n"); + + const updatedModules = await publicClient.readContract({ + address: aggregatorAddress, + abi: AGGREGATOR_ABI, + functionName: 'getModuleAddresses', + }); + + console.log(` Updated SyrupStaking: ${updatedModules[1]}\n`); + + if (updatedModules[1].toLowerCase() === syrupModuleAddress.toLowerCase()) { + console.log("✅ SUCCESS! SyrupStaking module configured in aggregator.\n"); + console.log("=".repeat(60)); + console.log("🎯 NEXT STEPS:"); + console.log("=".repeat(60)); + console.log("\n1️⃣ Test the aggregator in Snapshot playground:"); + console.log(` Strategy: erc20-balance-of`); + console.log(` Network: 8453`); + console.log(` Address: ${aggregatorAddress}\n`); + console.log("2️⃣ Compare voting power before/after for test addresses\n"); + console.log("3️⃣ Update Snapshot space configuration if needed\n"); + } else { + console.log("❌ ERROR: Module address mismatch after update!"); + console.log(` Expected: ${syrupModuleAddress}`); + console.log(` Got: ${updatedModules[1]}\n`); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); + diff --git a/snapshot-messages/base-aggregator-update.json b/snapshot-messages/base-aggregator-update.json new file mode 100644 index 0000000..b8bbc19 --- /dev/null +++ b/snapshot-messages/base-aggregator-update.json @@ -0,0 +1,21 @@ +{ + "domain": { + "name": "snapshot", + "version": "0.1.4" + }, + "primaryType": "Space", + "types": { + "Space": [ + { "name": "from", "type": "address" }, + { "name": "space", "type": "string" }, + { "name": "timestamp", "type": "uint64" }, + { "name": "settings", "type": "string" } + ] + }, + "message": { + "from": "", + "space": "", + "timestamp": 1768308837, + "settings": "" + } +} diff --git a/verify-args/base-aggregator.cjs b/verify-args/base-aggregator.cjs new file mode 100644 index 0000000..4c88244 --- /dev/null +++ b/verify-args/base-aggregator.cjs @@ -0,0 +1,13 @@ +/** + * Constructor arguments for BaseAggregator + * Generated: 2026-01-13T12:41:42.268Z + * Contract: 0x9975bc2fa6590620aeee87bd7e7e4bec0001095c + */ +module.exports = [ + "0xDA1077c4b0dd6da1BDF166F30aa4BDbF517d637b", // owner + "0xc52f200f29ac8f37d23ee00a31e6cdafdbd71506", // walletQuick + "0x4e0acd3980a601821b9724586cd3da81cdc7f33a", // syrupStaking + "0xacd0d266d504ebbdf4ccd141673a8bd9630b97c8", // algebraIntegralV4 + "0x22729e385310f035b54866c270d9f0b2cf0b6d64", // liquidityManagers + "0x559781f2b6d8b9499626317d68858f99e45437be", // v2LPStaking +]; diff --git a/verify-args/base-syrup-module.cjs b/verify-args/base-syrup-module.cjs new file mode 100644 index 0000000..acffb0e --- /dev/null +++ b/verify-args/base-syrup-module.cjs @@ -0,0 +1,16 @@ +/** + * Constructor arguments for SyrupStakingModule on Base + * ISSUE with "[]" empty list + * + * Usage: + * pnpm exec hardhat verify --network base \ + * --constructor-args-path verify-args/base-syrup-module.js \ + * + * + * Current deployed: 0x4e0acd3980a601821b9724586cd3da81cdc7f33a + */ +module.exports = [ + "0xDA1077c4b0dd6da1BDF166F30aa4BDbF517d637b", // owner (Governance Safe) + "0x4880c9ff216ae69Cb1Fc717575d824314Ed862a9", // factory (StakingRewardsFactory) + [] // legacyPools (empty array for Base) +];