From 501d55a97223ddcac899d7f6b22c35c0015dfda1 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Tue, 3 Jun 2025 10:13:38 +1000 Subject: [PATCH 1/9] Changed allocate Action to have a threshold before sending the tx --- src/abis/OriginARM.json | 16 +++++++++++++++- src/js/actions/allocateSonic.js | 1 + src/js/tasks/admin.js | 24 ++++++++++++++++++++++-- src/js/tasks/tasks.js | 10 ++++++++-- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/abis/OriginARM.json b/src/abis/OriginARM.json index 39191d4..b6d5536 100644 --- a/src/abis/OriginARM.json +++ b/src/abis/OriginARM.json @@ -13,6 +13,11 @@ "internalType": "uint256", "name": "_minSharesToRedeem", "type": "uint256" + }, + { + "internalType": "int256", + "name": "_allocateThreshold", + "type": "int256" } ], "stateMutability": "nonpayable", @@ -515,10 +520,19 @@ { "inputs": [], "name": "allocate", - "outputs": [], + "outputs": [ + { "internalType": "int256", "name": "liquidityDelta", "type": "int256" } + ], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "allocateThreshold", + "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, diff --git a/src/js/actions/allocateSonic.js b/src/js/actions/allocateSonic.js index c56bce0..c8f324c 100644 --- a/src/js/actions/allocateSonic.js +++ b/src/js/actions/allocateSonic.js @@ -26,6 +26,7 @@ const handler = async (event) => { await allocate({ signer, arm, + threshold: 20, }); } catch (error) { console.error(error); diff --git a/src/js/tasks/admin.js b/src/js/tasks/admin.js index 5cd115b..e523dc3 100644 --- a/src/js/tasks/admin.js +++ b/src/js/tasks/admin.js @@ -1,9 +1,29 @@ +const { formatUnits, parseUnits } = require("ethers"); + const { logTxDetails } = require("../utils/txLogger"); const log = require("../utils/logger")("task:admin"); -async function allocate({ arm, signer }) { - log(`About to allocate to/from the active lending market`); +async function allocate({ arm, signer, threshold }) { + const liquidityDelta = await arm.allocate.staticCall(); + + const thresholdBN = parseUnits((threshold || "10").toString(), 18); + if (liquidityDelta < thresholdBN && liquidityDelta > -thresholdBN) { + log( + `Only ${formatUnits( + liquidityDelta + )} liquidity delta, skipping allocation as threshold is ${formatUnits( + thresholdBN + )}` + ); + return; + } + + log( + `About to allocate ${formatUnits( + liquidityDelta + )} to/from the active lending market` + ); // Fixing the gas limit as the tx was a lot of the txs were failing wth out of gas errors const tx = await arm.connect(signer).allocate({ gasLimit: 3000000n }); await logTxDetails(tx, "allocate"); diff --git a/src/js/tasks/tasks.js b/src/js/tasks/tasks.js index b13e792..80f6198 100644 --- a/src/js/tasks/tasks.js +++ b/src/js/tasks/tasks.js @@ -877,13 +877,19 @@ subtask("allocate", "Allocate to/from the active lending market") "Origin", types.string ) - .setAction(async ({ arm }) => { + .addOptionalParam( + "threshold", + "The liquidity delta before threshold before allocate is called", + undefined, + types.float + ) + .setAction(async ({ arm, threshold }) => { const signer = await getSigner(); const armAddress = await parseDeployedAddress(`${arm.toUpperCase()}_ARM`); const armContract = await ethers.getContractAt(`${arm}ARM`, armAddress); - await allocate({ signer, arm: armContract }); + await allocate({ signer, arm: armContract, threshold }); }); task("allocate").setAction(async (_, __, runSuper) => { return runSuper(); From 37a3ee2db06f5e942cda46b82b896b147928221c Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Tue, 3 Jun 2025 10:27:52 +1000 Subject: [PATCH 2/9] Added Origin ARM support to the cap manager hardhat tasks --- src/js/tasks/liquidityProvider.js | 16 ++++++++++------ src/js/tasks/tasks.js | 2 ++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/js/tasks/liquidityProvider.js b/src/js/tasks/liquidityProvider.js index a03c4eb..bd60521 100644 --- a/src/js/tasks/liquidityProvider.js +++ b/src/js/tasks/liquidityProvider.js @@ -73,18 +73,20 @@ async function claimRedeemARM({ arm, id }) { await logTxDetails(tx, "claimRedeem"); } -async function setLiquidityProviderCaps({ accounts, cap }) { +async function setLiquidityProviderCaps({ accounts, arm, cap }) { const signer = await getSigner(); const capBn = parseUnits(cap.toString()); const liquidityProviders = accounts.split(","); - const lpcAddress = await parseDeployedAddress("LIDO_ARM_CAP_MAN"); + const lpcAddress = await parseDeployedAddress( + `${arm.toUpperCase()}_ARM_CAP_MAN` + ); const capManager = await ethers.getContractAt("CapManager", lpcAddress); log( - `About to set deposit cap of ${cap} WETH for liquidity providers ${liquidityProviders}` + `About to set deposit cap of ${cap} WETH for liquidity providers ${liquidityProviders} for the ${arm} ARM` ); const tx = await capManager .connect(signer) @@ -92,15 +94,17 @@ async function setLiquidityProviderCaps({ accounts, cap }) { await logTxDetails(tx, "setLiquidityProviderCaps"); } -async function setTotalAssetsCap({ cap }) { +async function setTotalAssetsCap({ arm, cap }) { const signer = await getSigner(); const capBn = parseUnits(cap.toString()); - const lpcAddress = await parseDeployedAddress("LIDO_ARM_CAP_MAN"); + const lpcAddress = await parseDeployedAddress( + `${arm.toUpperCase()}_ARM_CAP_MAN` + ); const capManager = await ethers.getContractAt("CapManager", lpcAddress); - log(`About to set total asset cap of ${cap} WETH`); + log(`About to set total asset cap of ${cap} for the ${arm} ARM`); const tx = await capManager.connect(signer).setTotalAssetsCap(capBn); await logTxDetails(tx, "setTotalAssetsCap"); } diff --git a/src/js/tasks/tasks.js b/src/js/tasks/tasks.js index 80f6198..f82064e 100644 --- a/src/js/tasks/tasks.js +++ b/src/js/tasks/tasks.js @@ -593,6 +593,7 @@ task("claimRedeemARM").setAction(async (_, __, runSuper) => { // Capital Management subtask("setLiquidityProviderCaps", "Set deposit cap for liquidity providers") + .addParam("arm", "Name of the ARM. eg Lido or Origin", "Lido", types.string) .addParam( "cap", "Amount of WETH not scaled to 18 decimals", @@ -611,6 +612,7 @@ task("setLiquidityProviderCaps").setAction(async (_, __, runSuper) => { }); subtask("setTotalAssetsCap", "Set total assets cap") + .addParam("arm", "Name of the ARM. eg Lido or Origin", "Lido", types.string) .addParam( "cap", "Amount of WETH not scaled to 18 decimals", From e09a86bcc7545f59c44c67ab47f3079818403e3f Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Tue, 3 Jun 2025 10:50:18 +1000 Subject: [PATCH 3/9] Add 10% buffer to allocate tx --- src/js/tasks/admin.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/tasks/admin.js b/src/js/tasks/admin.js index e523dc3..dd4be8f 100644 --- a/src/js/tasks/admin.js +++ b/src/js/tasks/admin.js @@ -19,13 +19,16 @@ async function allocate({ arm, signer, threshold }) { return; } + // Add 10% buffer to gas limit + let gasLimit = await arm.connect(signer).allocate.estimateGas(); + gasLimit = (gasLimit * 11n) / 10n; + log( `About to allocate ${formatUnits( liquidityDelta )} to/from the active lending market` ); - // Fixing the gas limit as the tx was a lot of the txs were failing wth out of gas errors - const tx = await arm.connect(signer).allocate({ gasLimit: 3000000n }); + const tx = await arm.connect(signer).allocate({ gasLimit }); await logTxDetails(tx, "allocate"); } From 68baa70ffbe8b5a07005a6cce830891421242579 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Tue, 3 Jun 2025 10:52:18 +1000 Subject: [PATCH 4/9] Add 10% buffer to depositARM --- src/js/tasks/liquidityProvider.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/js/tasks/liquidityProvider.js b/src/js/tasks/liquidityProvider.js index bd60521..a946fb7 100644 --- a/src/js/tasks/liquidityProvider.js +++ b/src/js/tasks/liquidityProvider.js @@ -29,8 +29,16 @@ async function depositARM({ amount, asset, arm }) { const armAddress = await parseDeployedAddress(`${arm.toUpperCase()}_ARM`); const armContract = await ethers.getContractAt(`${arm}ARM`, armAddress); + // Add 10% buffer to gas limit + let gasLimit = await armContract + .connect(signer) + ["deposit(uint256)"].estimateGas(amountBn); + gasLimit = (gasLimit * 11n) / 10n; + log(`About to deposit ${amount} ${asset} to the ${arm} ARM`); - const tx = await armContract.connect(signer).deposit(amountBn); + const tx = await armContract + .connect(signer) + ["deposit(uint256)"](amountBn, { gasLimit }); await logTxDetails(tx, "deposit"); } else if (asset == "S") { const zapperAddress = await parseDeployedAddress( @@ -44,6 +52,10 @@ async function depositARM({ amount, asset, arm }) { .connect(signer) .deposit(armAddress, { value: amountBn }); await logTxDetails(tx, "zap deposit"); + } else { + throw new Error( + `Unsupported asset type: ${asset}. Supported types are WETH, ETH, WS, S.` + ); } } From f750fe2fafe258dfdfc9981785b0d170d8ef59c8 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Tue, 3 Jun 2025 10:53:59 +1000 Subject: [PATCH 5/9] Add 10% to gas limit for collectFees --- src/js/tasks/admin.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/js/tasks/admin.js b/src/js/tasks/admin.js index dd4be8f..19ac99c 100644 --- a/src/js/tasks/admin.js +++ b/src/js/tasks/admin.js @@ -33,8 +33,12 @@ async function allocate({ arm, signer, threshold }) { } async function collectFees({ arm, signer }) { + // Add 10% buffer to gas limit + let gasLimit = await arm.connect(signer).collectFees.estimateGas(); + gasLimit = (gasLimit * 11n) / 10n; + log(`About to collect ARM fees`); - const tx = await arm.connect(signer).collectFees(); + const tx = await arm.connect(signer).collectFees({ gasLimit }); await logTxDetails(tx, "collectFees"); } From 9aefba65990737a265edf8172adb14d688507c31 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Tue, 3 Jun 2025 13:47:33 +1000 Subject: [PATCH 6/9] Added prices to snap task --- src/js/tasks/tasks.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/js/tasks/tasks.js b/src/js/tasks/tasks.js index f82064e..a755046 100644 --- a/src/js/tasks/tasks.js +++ b/src/js/tasks/tasks.js @@ -26,6 +26,7 @@ const { logLiquidity, withdrawRequestStatus, } = require("./liquidity"); +const { logArmPrices } = require("./markets"); const { depositARM, requestRedeemARM, @@ -912,7 +913,18 @@ subtask("snap", "Take a snapshot of the an ARM") undefined, types.int ) - .setAction(logLiquidity); + .setAction(async (taskArgs) => { + await logLiquidity(taskArgs); + + if (taskArgs.arm.toUpperCase() === "OETH") return; + + const armAddress = await parseAddress(`${taskArgs.arm.toUpperCase()}_ARM`); + const armContract = await ethers.getContractAt( + `${taskArgs.arm}ARM`, + armAddress + ); + await logArmPrices(taskArgs, armContract); + }); task("snap").setAction(async (_, __, runSuper) => { return runSuper(); }); From 7936dd7737e4f13465fd15175b1252aaafb67a71 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 4 Jun 2025 12:04:27 +1000 Subject: [PATCH 7/9] Added setHarvester and setOperator HH tasks Changed collectRewards to call the SonicHarvester Changed the Silo token address --- src/abis/SonicHarvester.json | 660 ++++++++++++++++++++++++++ src/contracts/utils/Addresses.sol | 2 +- src/js/actions/collectRewardsSonic.js | 12 +- src/js/tasks/governance.js | 11 + src/js/tasks/sonicHarvest.js | 35 +- src/js/tasks/tasks.js | 82 +++- src/js/utils/addresses.js | 5 +- 7 files changed, 773 insertions(+), 34 deletions(-) create mode 100644 src/abis/SonicHarvester.json create mode 100644 src/js/tasks/governance.js diff --git a/src/abis/SonicHarvester.json b/src/abis/SonicHarvester.json new file mode 100644 index 0000000..536ea1d --- /dev/null +++ b/src/abis/SonicHarvester.json @@ -0,0 +1,660 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_liquidityAsset", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "actualBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minExpected", + "type": "uint256" + } + ], + "name": "BalanceMismatchAfterSwap", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyLiquidityAsset", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyMagpieRouter", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyRewardRecipient", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "allowedSlippageBps", + "type": "uint256" + } + ], + "name": "InvalidAllowedSlippage", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDecimals", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromAsset", + "type": "address" + } + ], + "name": "InvalidFromAsset", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fromAssetAmount", + "type": "uint256" + } + ], + "name": "InvalidFromAssetAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "enum SonicHarvester.SwapPlatform", + "name": "swapPlatform", + "type": "uint8" + } + ], + "name": "InvalidSwapPlatform", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "InvalidSwapRecipient", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "toAsset", + "type": "address" + } + ], + "name": "InvalidToAsset", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "actualBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minExpected", + "type": "uint256" + } + ], + "name": "SlippageError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "strategyAddress", + "type": "address" + } + ], + "name": "UnsupportedStrategy", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "allowedSlippageBps", + "type": "uint256" + } + ], + "name": "AllowedSlippageUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "MagpieRouterUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "OperatorChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "priceProvider", + "type": "address" + } + ], + "name": "PriceProviderUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "rewardRecipient", + "type": "address" + } + ], + "name": "RewardRecipientUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "rewardToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "swappedInto", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum SonicHarvester.SwapPlatform", + "name": "swapPlatform", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "RewardTokenSwapped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address[]", + "name": "strategy", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "address[][]", + "name": "rewardTokens", + "type": "address[][]" + }, + { + "indexed": false, + "internalType": "uint256[][]", + "name": "amounts", + "type": "uint256[][]" + } + ], + "name": "RewardsCollected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "strategy", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isSupported", + "type": "bool" + } + ], + "name": "SupportedStrategyUpdate", + "type": "event" + }, + { + "inputs": [], + "name": "allowedSlippageBps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_strategies", + "type": "address[]" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "address[][]", + "name": "rewardTokens", + "type": "address[][]" + }, + { + "internalType": "uint256[][]", + "name": "amounts", + "type": "uint256[][]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_priceProvider", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_allowedSlippageBps", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_rewardRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "_magpieRouter", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidityAsset", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "magpieRouter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "priceProvider", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_allowedSlippageBps", + "type": "uint256" + } + ], + "name": "setAllowedSlippage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_router", + "type": "address" + } + ], + "name": "setMagpieRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOperator", + "type": "address" + } + ], + "name": "setOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_priceProvider", + "type": "address" + } + ], + "name": "setPriceProvider", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_rewardRecipient", + "type": "address" + } + ], + "name": "setRewardRecipient", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_strategyAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isSupported", + "type": "bool" + } + ], + "name": "setSupportedStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "supportedStrategies", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum SonicHarvester.SwapPlatform", + "name": "swapPlatform", + "type": "uint8" + }, + { + "internalType": "address", + "name": "fromAsset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAssetAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "uint256", + "name": "toAssetAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/contracts/utils/Addresses.sol b/src/contracts/utils/Addresses.sol index 92e9d1c..3bac1df 100644 --- a/src/contracts/utils/Addresses.sol +++ b/src/contracts/utils/Addresses.sol @@ -70,7 +70,7 @@ library Sonic { address public constant OS = 0xb1e25689D55734FD3ffFc939c4C3Eb52DFf8A794; address public constant WS = 0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38; address public constant WOS = 0x9F0dF7799f6FDAd409300080cfF680f5A23df4b1; - address public constant SILO = 0x53f753E4B17F4075D6fa2c6909033d224b81e698; + address public constant SILO = 0xb098AFC30FCE67f1926e735Db6fDadFE433E61db; // Contracts address public constant OS_VAULT = 0xa3c0eCA00D2B76b4d1F170b0AB3FdeA16C180186; diff --git a/src/js/actions/collectRewardsSonic.js b/src/js/actions/collectRewardsSonic.js index 8d73a45..ee1b79d 100644 --- a/src/js/actions/collectRewardsSonic.js +++ b/src/js/actions/collectRewardsSonic.js @@ -3,7 +3,7 @@ const { ethers } = require("ethers"); const { collectRewards } = require("../tasks/sonicHarvest"); const { sonic } = require("../utils/addresses"); -const siloMarketAbi = require("../../abis/SiloMarket.json"); +const harvesterAbi = require("../../abis/SonicHarvester.json"); // Entrypoint for the Defender Action const handler = async (event) => { @@ -19,17 +19,13 @@ const handler = async (event) => { `DEBUG env var in handler before being set: "${process.env.DEBUG}"` ); - // There is only one market for now - const siloMarket = new ethers.Contract( - sonic.siloVarlamoreMarket, - siloMarketAbi, - signer - ); + const harvester = new ethers.Contract(sonic.harvester, harvesterAbi, signer); try { await collectRewards({ signer, - siloMarket, + harvester, + strategies: [sonic.siloVarlamoreMarket], }); } catch (error) { console.error(error); diff --git a/src/js/tasks/governance.js b/src/js/tasks/governance.js new file mode 100644 index 0000000..6e71acd --- /dev/null +++ b/src/js/tasks/governance.js @@ -0,0 +1,11 @@ +const { logTxDetails } = require("../utils/txLogger"); + +const log = require("../utils/logger")("task:governance"); + +async function setOperator({ contract, operator, signer }) { + log(`About to set the Operator to ${operator}`); + const tx = await contract.connect(signer).setOperator(operator); + await logTxDetails(tx, "setOperator"); +} + +module.exports = { setOperator }; diff --git a/src/js/tasks/sonicHarvest.js b/src/js/tasks/sonicHarvest.js index 3024498..3d15f6e 100644 --- a/src/js/tasks/sonicHarvest.js +++ b/src/js/tasks/sonicHarvest.js @@ -6,38 +6,47 @@ const { magpieQuote } = require("../utils/magpie"); const log = require("../utils/logger")("task:sonic:harvest"); -async function collectRewards({ siloMarket, signer }) { - log(`About to collect Silo Rewards`); - const tx = await siloMarket.connect(signer).collectRewards(); - await logTxDetails(tx, "collectRewards"); +async function collectRewards({ harvester, strategies, signer }) { + log(`About to collect rewards from the following strategies: ${strategies}`); + const tx = await harvester.connect(signer).collect(strategies); + await logTxDetails(tx, "collect"); } -async function harvestRewards({ harvester, signer }) { - const silo = await resolveAsset("SILO"); - const siloRewards = await silo.balanceOf(harvester.getAddress()); +async function harvestRewards({ harvester, signer, symbol }) { + const rewardToken = await resolveAsset(symbol.toUpperCase()); + const rewards = await rewardToken.balanceOf(harvester.getAddress()); - if (siloRewards == 0n) { - console.log("No Silo rewards to harvest"); + if (rewards == 0n) { + console.log("No token rewards to harvest"); return; } const { data: magpieData } = await magpieQuote({ - from: "SILO", + from: symbol.toUpperCase(), to: "WS", - amount: siloRewards, + amount: rewards, slippage: 0.5, swapper: await harvester.getAddress(), recipient: await harvester.getAddress(), }); - log(`About to harvest ${formatUnits(siloRewards)} Silo rewards using Magpie`); + log( + `About to harvest ${formatUnits(rewards)} ${symbol} rewards using Magpie` + ); const tx = await harvester .connect(signer) - .swap(0, silo.getAddress(), siloRewards, magpieData); + .swap(0, rewardToken.getAddress(), rewards, magpieData); await logTxDetails(tx, "swap rewards"); } +async function setHarvester({ siloMarket, harvester, signer }) { + log(`About to set the harvester to ${harvester}`); + const tx = await siloMarket.connect(signer).setHarvester(harvester); + await logTxDetails(tx, "setHarvester"); +} + module.exports = { collectRewards, harvestRewards, + setHarvester, }; diff --git a/src/js/tasks/tasks.js b/src/js/tasks/tasks.js index a755046..a2817dd 100644 --- a/src/js/tasks/tasks.js +++ b/src/js/tasks/tasks.js @@ -16,7 +16,11 @@ const { } = require("./lido"); const { setPrices } = require("./lidoPrices"); const { allocate, collectFees } = require("./admin"); -const { collectRewards, harvestRewards } = require("./sonicHarvest"); +const { + collectRewards, + harvestRewards, + setHarvester, +} = require("./sonicHarvest"); const { requestLidoWithdrawals, claimLidoWithdrawals } = require("./lidoQueue"); const { autoRequestWithdraw, @@ -56,6 +60,7 @@ const { } = require("./vault"); const { upgradeProxy } = require("./proxy"); const { magpieQuote, magpieTx } = require("../utils/magpie"); +const { setOperator } = require("./governance"); subtask( "swap", @@ -840,12 +845,17 @@ subtask("collectRewards", "Collect rewards") const siloMarketAddress = await parseDeployedAddress( "SILO_VARLAMORE_S_MARKET" ); - const siloMarket = await ethers.getContractAt( - "SiloMarket", - siloMarketAddress + const harvesterAddress = await parseDeployedAddress("HARVESTER"); + const harvester = await ethers.getContractAt( + "SonicHarvester", + harvesterAddress ); - await collectRewards({ signer, siloMarket }); + await collectRewards({ + signer, + harvester, + strategies: [siloMarketAddress], + }); }); task("collectRewards").setAction(async (_, __, runSuper) => { return runSuper(); @@ -858,7 +868,13 @@ subtask("harvestRewards", "harvest rewards") "Origin", types.string ) - .setAction(async () => { + .addOptionalParam( + "token", + "The symbol of the reward token. eg Silo, beS, OS", + "Silo", + types.string + ) + .setAction(async ({ token }) => { const signer = await getSigner(); const harvesterAddress = await parseDeployedAddress("HARVESTER"); @@ -867,12 +883,38 @@ subtask("harvestRewards", "harvest rewards") harvesterAddress ); - await harvestRewards({ signer, harvester }); + await harvestRewards({ signer, harvester, symbol: token }); }); task("harvestRewards").setAction(async (_, __, runSuper) => { return runSuper(); }); +subtask("setHarvester", "Set the harvester on a lending market") + .addOptionalParam( + "name", + "The name of the ARM. eg Lido or Origin", + "Origin", + types.string + ) + .setAction(async () => { + const signer = await getSigner(); + + const harvester = await parseDeployedAddress("HARVESTER"); + + const siloMarketAddress = await parseDeployedAddress( + "SILO_VARLAMORE_S_MARKET" + ); + const siloMarket = await ethers.getContractAt( + "SiloMarket", + siloMarketAddress + ); + + await setHarvester({ signer, siloMarket, harvester }); + }); +task("setHarvester").setAction(async (_, __, runSuper) => { + return runSuper(); +}); + subtask("allocate", "Allocate to/from the active lending market") .addOptionalParam( "arm", @@ -898,6 +940,26 @@ task("allocate").setAction(async (_, __, runSuper) => { return runSuper(); }); +// Governance + +subtask("setOperator", "Set the operator of a contract") + .addParam("contract", "Name of a proxy contract", undefined, types.string) + .addParam("operator", "Address of the Operator", undefined, types.string) + .setAction(async ({ contract: contractName, operator }) => { + const signer = await getSigner(); + + const contractAddress = await parseDeployedAddress(contractName); + const contract = await ethers.getContractAt( + "OwnableOperable", + contractAddress + ); + + await setOperator({ signer, contract, operator }); + }); +task("setOperator").setAction(async (_, __, runSuper) => { + return runSuper(); +}); + // ARM Snapshots subtask("snap", "Take a snapshot of the an ARM") @@ -990,8 +1052,8 @@ task("setActionVars").setAction(async (_, __, runSuper) => { return runSuper(); }); -// Magpie -subtask("magpieQuote", "Get a quote from Magpie for a swap") +// Magpie (now Fly) +subtask("magpieQuote", "Get a quote from Magpie (now Fly) for a swap") .addOptionalParam("from", "Token symbol to swap from.", "SILO", types.string) .addOptionalParam("to", "Token symbol to swap to.", "WS", types.string) .addOptionalParam("amount", "Amount of tokens to sell", 1, types.float) @@ -1018,7 +1080,7 @@ task("magpieQuote").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask("magpieTx", "Get a Magpie swap tx based on a previous quote") +subtask("magpieTx", "Get a Magpie (Fly) swap tx based on a previous quote") .addParam( "id", "Identifier returned from a previous quote.", diff --git a/src/js/utils/addresses.js b/src/js/utils/addresses.js index badc45f..4b7b645 100644 --- a/src/js/utils/addresses.js +++ b/src/js/utils/addresses.js @@ -42,13 +42,14 @@ addresses.mainnet.UniswapV3stETHWETHPool = // Sonic addresses.sonic = {}; -addresses.sonic.guardian = "0x63cdd3072F25664eeC6FAEFf6dAeB668Ea4de94a" +addresses.sonic.guardian = "0x63cdd3072F25664eeC6FAEFf6dAeB668Ea4de94a"; addresses.sonic.WS = "0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38"; addresses.sonic.OriginARM = "0x2F872623d1E1Af5835b08b0E49aAd2d81d649D30"; -addresses.sonic.SILO = "0x53f753E4B17F4075D6fa2c6909033d224b81e698"; +addresses.sonic.SILO = "0xb098AFC30FCE67f1926e735Db6fDadFE433E61db"; addresses.sonic.OSonicProxy = "0xb1e25689D55734FD3ffFc939c4C3Eb52DFf8A794"; addresses.sonic.OSonicVaultProxy = "0xa3c0eCA00D2B76b4d1F170b0AB3FdeA16C180186"; addresses.sonic.siloVarlamoreMarket = "0x248Dbbc31F2D7675775DB4A9308a98444DaBaECf"; +addresses.sonic.harvester = "0x08876C0F5a80c1a43A6396b13A881A26F4b6Adfe"; module.exports = addresses; From 68756f04e4d0c5c1f58b326fb9aa9328f8511cf6 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 4 Jun 2025 20:11:24 +1000 Subject: [PATCH 8/9] Fixed harvestRewards Hardhat task --- src/contracts/utils/Addresses.sol | 1 + src/js/utils/magpie.js | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/contracts/utils/Addresses.sol b/src/contracts/utils/Addresses.sol index 3bac1df..142def2 100644 --- a/src/contracts/utils/Addresses.sol +++ b/src/contracts/utils/Addresses.sol @@ -70,6 +70,7 @@ library Sonic { address public constant OS = 0xb1e25689D55734FD3ffFc939c4C3Eb52DFf8A794; address public constant WS = 0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38; address public constant WOS = 0x9F0dF7799f6FDAd409300080cfF680f5A23df4b1; + address public constant BES = 0x871A101Dcf22fE4fE37be7B654098c801CBA1c88; address public constant SILO = 0xb098AFC30FCE67f1926e735Db6fDadFE433E61db; // Contracts diff --git a/src/js/utils/magpie.js b/src/js/utils/magpie.js index 00d1586..266738c 100644 --- a/src/js/utils/magpie.js +++ b/src/js/utils/magpie.js @@ -1,5 +1,5 @@ const axios = require("axios"); -const { formatUnits, parseUnits } = require("ethers"); +const { formatUnits, parseUnits, Interface } = require("ethers"); const { resolveAddress } = require("../utils/assets"); const MagpieBaseURL = "https://api.magpiefi.xyz/aggregator"; @@ -38,7 +38,7 @@ const magpieQuote = async ({ const toAmount = parseUnits(response.data.amountOut, 18); const price = (amount * parseUnits("1", 22)) / toAmount; - log("Magpie quote response: ", response.data); + // log("Magpie quote response: ", response.data); const id = response.data.id; const minAmountOut = response.data.amountOutMin; @@ -47,8 +47,6 @@ const magpieQuote = async ({ const data = await magpieTx({ id }); - console.log(data); - return { price, fromAsset, toAsset, minAmountOut, data }; } catch (err) { if (err.response) { @@ -72,12 +70,20 @@ const magpieTx = async ({ id }) => { params, }); - log("Magpie transaction response: ", response.data); - log(`Transaction data: ${response.data.data}`); - const swapData = `0x${response.data.data.slice(10)}`; - log(`Swap data: ${swapData}`); + // log("Magpie transaction response: ", response.data); + // log(`Transaction data: ${response.data.data}`); + + const iface = new Interface([ + "function swapWithMagpieSignature(bytes) view returns (uint256)", + ]); + + const decodedData = iface.decodeFunctionData( + "swapWithMagpieSignature", + response.data.data + ); + log(`Decoded swap data: ${decodedData}`); - return swapData; + return decodedData[0]; } catch (err) { if (err.response) { console.error("Response data : ", err.response.data); From 2d49267e04885f591901a7f6bf5c3030e48c2172 Mon Sep 17 00:00:00 2001 From: Nicholas Addison Date: Wed, 4 Jun 2025 20:12:03 +1000 Subject: [PATCH 9/9] Added error signatures to help when debugging --- src/contracts/SonicHarvester.sol | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/contracts/SonicHarvester.sol b/src/contracts/SonicHarvester.sol index 871ab9f..7114a5b 100644 --- a/src/contracts/SonicHarvester.sol +++ b/src/contracts/SonicHarvester.sol @@ -50,19 +50,19 @@ contract SonicHarvester is Initializable, OwnableOperable { event PriceProviderUpdated(address priceProvider); event MagpieRouterUpdated(address router); - error SlippageError(uint256 actualBalance, uint256 minExpected); - error BalanceMismatchAfterSwap(uint256 actualBalance, uint256 minExpected); - error InvalidSwapPlatform(SwapPlatform swapPlatform); - error UnsupportedStrategy(address strategyAddress); - error InvalidSwapRecipient(address recipient); - error InvalidFromAsset(address fromAsset); - error InvalidFromAssetAmount(uint256 fromAssetAmount); - error InvalidToAsset(address toAsset); - error EmptyLiquidityAsset(); - error EmptyMagpieRouter(); - error EmptyRewardRecipient(); - error InvalidDecimals(); - error InvalidAllowedSlippage(uint256 allowedSlippageBps); + error SlippageError(uint256 actualBalance, uint256 minExpected); // 0x2d96fff0 + error BalanceMismatchAfterSwap(uint256 actualBalance, uint256 minExpected); // 0x62baa1be + error InvalidSwapPlatform(SwapPlatform swapPlatform); // 0x36cb1d21 + error UnsupportedStrategy(address strategyAddress); // 0x04228892 + error InvalidSwapRecipient(address recipient); // 0x1a7c14f3 + error InvalidFromAsset(address fromAsset); // 0xc3e1c198 + error InvalidFromAssetAmount(uint256 fromAssetAmount); // 0x51444e84 + error InvalidToAsset(address toAsset); // 0xdc851a18 + error EmptyLiquidityAsset(); // 0x0c82ef26 + error EmptyMagpieRouter(); // 0x24444713 + error EmptyRewardRecipient(); // 0x0c45e033 + error InvalidDecimals(); // 0xd25598a0 + error InvalidAllowedSlippage(uint256 allowedSlippageBps); // 0xfbdd3e50 constructor(address _liquidityAsset) { if (_liquidityAsset == address(0)) revert EmptyLiquidityAsset();