diff --git a/README.md b/README.md index d6f29583..b88886e0 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,10 @@ function totalAssets() public view virtual returns (uint256); function asset() external view virtual returns (address); ``` +## Lending Platforms + +The ARM token can be used as collateral on lending platforms to borrow other assets. The ARM token can not be borrowed on lending platforms as the ARM's price, or assets per share, can be increased by donating assets to the ARM. eg donating WETH or stETH to the Lido ARM will increase the ARM's token price. Increasing the price of a borrowed asset increases the borrower's loan to value (LTV) ratio which can lead to a liquidation. + ## Development ### Install diff --git a/build/deployments-1.json b/build/deployments-1.json index ff808a29..51454778 100644 --- a/build/deployments-1.json +++ b/build/deployments-1.json @@ -1,44 +1,44 @@ { - "executions": { - "001_CoreMainnet": 1723685111, - "002_UpgradeMainnet": 1726812322, - "003_UpgradeLidoARMScript": 1729073099, - "004_UpdateCrossPriceScript": 1739872139, - "005_RegisterLidoWithdrawalsScript": 1743500783, - "006_ChangeFeeCollector": 1751894483, - "007_UpgradeLidoARMMorphoScript": 1754407511, - "008_DeployPendleAdaptor": 1755770279, - "009_UpgradeLidoARMSetBufferScript": 1755692363, - "010_UpgradeLidoARMAssetScript": 1764755147, - "011_DeployEtherFiARMScript": 1761812927, - "012_UpgradeEtherFiARMScript": 1763557643, - "013_UpgradeOETHARMScript": 1765353347, - "014_DeployEthenaARMScript": 1764664655 - }, - "contracts": { - "ARM_ZAPPER": "0xE11EDbd5AE4Fa434Af7f8D7F03Da1742996e7Ab2", - "ETHENA_ARM": "0xCEDa2d856238aA0D12f6329de20B9115f07C366d", - "ETHENA_ARM_CAP_IMPL": "0x7073F39ae371962C2469D72f01f907375bB11E08", - "ETHENA_ARM_CAP_MAN": "0x687AFB5A52A15122fD5FC54A8B52cfd58346fb0C", - "ETHENA_ARM_IMPL": "0x6c181f2CA4224e42Cee8Fc7199c060261AD55d7d", - "ETHERFI_ARM_IMPL": "0x69b98667134EeE3eBF75799dacBCd604E28709ab", - "ETHER_FI_ARM": "0xfB0A3CF9B019BFd8827443d131b235B3E0FC58d2", - "ETHER_FI_ARM_CAP_IMPL": "0xe27720Fc3f3707D47015e274D81a99e5B0800472", - "ETHER_FI_ARM_CAP_MAN": "0xf2A18F7330141Ec737EB73A0A5Ea8E4d7e9bE7ec", - "ETHER_FI_ARM_IMPL": "0x00B53CEE151c043CBe4bbFe4cfD2938Cb4fabCEB", - "LIDO_ARM": "0x85B78AcA6Deae198fBF201c82DAF6Ca21942acc6", - "LIDO_ARM_CAP_IMPL": "0x8506486813d025C5935dF481E450e27D2e483dc9", - "LIDO_ARM_CAP_MAN": "0xf54ebff575f699d281645c6F14Fe427dFFE629CF", - "LIDO_ARM_IMPL": "0xC0297a0E39031F09406F0987C9D9D41c5dfbc3df", - "LIDO_ARM_ZAPPER": "0x01F30B7358Ba51f637d1aa05D9b4A60f76DAD680", - "MORPHO_MARKET_ETHERFI": "0x8Cf42b82fFFa3E7714D62a2cA223acBeC1Eef095", - "MORPHO_MARKET_ETHERFI_IMPL": "0x2a1b59870f7806E60dF58415B0C220C096f57658", - "MORPHO_MARKET_MEVCAPITAL": "0x29c4Bb7B1eBcc53e8CBd16480B5bAe52C69806D3", - "MORPHO_MARKET_MEVCAPITAL_IMP": "0x90c7ABC962f96de171ee395A242D2Ff794D0a04c", - "MORPHO_MARKET_ORIGIN": "0x0ad39D67404aE36Fe476eFDDE1306a5414383544", - "MORPHO_MARKET_ORIGIN_IMPL": "0x2ea9D1827973813D77aA8f35BD23cb1F1311A648", - "OETH_ARM": "0x6bac785889A4127dB0e0CeFEE88E0a9F1Aaf3cC7", - "OETH_ARM_IMPL": "0x9A2be51E45Eec98F75b3e6e1b334246c94663641", - "PENDLE_ORIGIN_ARM_SY": "0xbcae2Eb1cc47F137D8B2D351B0E0ea8DdA4C6184" - } -} \ No newline at end of file + "executions": { + "001_CoreMainnet": 1723685111, + "002_UpgradeMainnet": 1726812322, + "003_UpgradeLidoARMScript": 1729073099, + "004_UpdateCrossPriceScript": 1739872139, + "005_RegisterLidoWithdrawalsScript": 1743500783, + "006_ChangeFeeCollector": 1751894483, + "007_UpgradeLidoARMMorphoScript": 1754407511, + "008_DeployPendleAdaptor": 1755770279, + "009_UpgradeLidoARMSetBufferScript": 1755692363, + "010_UpgradeLidoARMAssetScript": 1764755147, + "011_DeployEtherFiARMScript": 1761812927, + "012_UpgradeEtherFiARMScript": 1763557643, + "013_UpgradeOETHARMScript": 1765353347, + "014_DeployEthenaARMScript": 1764664655 + }, + "contracts": { + "ARM_ZAPPER": "0xE11EDbd5AE4Fa434Af7f8D7F03Da1742996e7Ab2", + "ETHENA_ARM": "0xCEDa2d856238aA0D12f6329de20B9115f07C366d", + "ETHENA_ARM_CAP_IMPL": "0x7073F39ae371962C2469D72f01f907375bB11E08", + "ETHENA_ARM_CAP_MAN": "0x687AFB5A52A15122fD5FC54A8B52cfd58346fb0C", + "ETHENA_ARM_IMPL": "0x6c181f2CA4224e42Cee8Fc7199c060261AD55d7d", + "ETHERFI_ARM_IMPL": "0x69b98667134EeE3eBF75799dacBCd604E28709ab", + "ETHER_FI_ARM": "0xfB0A3CF9B019BFd8827443d131b235B3E0FC58d2", + "ETHER_FI_ARM_CAP_IMPL": "0xe27720Fc3f3707D47015e274D81a99e5B0800472", + "ETHER_FI_ARM_CAP_MAN": "0xf2A18F7330141Ec737EB73A0A5Ea8E4d7e9bE7ec", + "ETHER_FI_ARM_IMPL": "0x00B53CEE151c043CBe4bbFe4cfD2938Cb4fabCEB", + "LIDO_ARM": "0x85B78AcA6Deae198fBF201c82DAF6Ca21942acc6", + "LIDO_ARM_CAP_IMPL": "0x8506486813d025C5935dF481E450e27D2e483dc9", + "LIDO_ARM_CAP_MAN": "0xf54ebff575f699d281645c6F14Fe427dFFE629CF", + "LIDO_ARM_IMPL": "0xC0297a0E39031F09406F0987C9D9D41c5dfbc3df", + "LIDO_ARM_ZAPPER": "0x01F30B7358Ba51f637d1aa05D9b4A60f76DAD680", + "MORPHO_MARKET_ETHERFI": "0x8Cf42b82fFFa3E7714D62a2cA223acBeC1Eef095", + "MORPHO_MARKET_ETHERFI_IMPL": "0x2a1b59870f7806E60dF58415B0C220C096f57658", + "MORPHO_MARKET_MEVCAPITAL": "0x29c4Bb7B1eBcc53e8CBd16480B5bAe52C69806D3", + "MORPHO_MARKET_MEVCAPITAL_IMP": "0x90c7ABC962f96de171ee395A242D2Ff794D0a04c", + "MORPHO_MARKET_ORIGIN": "0x0ad39D67404aE36Fe476eFDDE1306a5414383544", + "MORPHO_MARKET_ORIGIN_IMPL": "0x2ea9D1827973813D77aA8f35BD23cb1F1311A648", + "ORIGIN_ARM": "0x6bac785889A4127dB0e0CeFEE88E0a9F1Aaf3cC7", + "ORIGIN_ARM_IMPL": "0x9A2be51E45Eec98F75b3e6e1b334246c94663641", + "PENDLE_ORIGIN_ARM_SY": "0xbcae2Eb1cc47F137D8B2D351B0E0ea8DdA4C6184" + } +} diff --git a/src/js/actions/allocateOETH.js b/src/js/actions/allocateOETH.js new file mode 100644 index 00000000..3a97466a --- /dev/null +++ b/src/js/actions/allocateOETH.js @@ -0,0 +1,33 @@ +const { Defender } = require("@openzeppelin/defender-sdk"); +const { ethers } = require("ethers"); + +const { allocate } = require("../tasks/admin"); +const { mainnet } = require("../utils/addresses"); +const armAbi = require("../../abis/OriginARM.json"); + +// Entrypoint for the Defender Action +const handler = async (event) => { + // Initialize defender relayer provider and signer + const client = new Defender(event); + const provider = client.relaySigner.getProvider({ ethersVersion: "v6" }); + const signer = await client.relaySigner.getSigner(provider, { + speed: "fastest", + ethersVersion: "v6", + }); + + console.log( + `DEBUG env var in handler before being set: "${process.env.DEBUG}"`, + ); + + // References to contracts + const arm = new ethers.Contract(mainnet.OethARM, armAbi, signer); + + await allocate({ + signer, + arm, + threshold: 10000, + maxGasPrice: 500, + }); +}; + +module.exports = { handler }; diff --git a/src/js/actions/autoClaimWithdraw.js b/src/js/actions/autoClaimWithdraw.js index 6f587f2e..ead83d29 100644 --- a/src/js/actions/autoClaimWithdraw.js +++ b/src/js/actions/autoClaimWithdraw.js @@ -1,7 +1,7 @@ const { Defender } = require("@openzeppelin/defender-sdk"); const { ethers } = require("ethers"); -const { autoClaimWithdraw } = require("../tasks/liquidity"); +const { autoClaimWithdraw } = require("../tasks/liquidityAutomation"); const { mainnet } = require("../utils/addresses"); const erc20Abi = require("../../abis/ERC20.json"); const oethARMAbi = require("../../abis/OethARM.json"); diff --git a/src/js/actions/autoRequestWithdraw.js b/src/js/actions/autoRequestWithdraw.js index 9adb72a2..056521c3 100644 --- a/src/js/actions/autoRequestWithdraw.js +++ b/src/js/actions/autoRequestWithdraw.js @@ -1,7 +1,7 @@ const { Defender } = require("@openzeppelin/defender-sdk"); const { ethers } = require("ethers"); -const { autoRequestWithdraw } = require("../tasks/liquidity"); +const { autoRequestWithdraw } = require("../tasks/liquidityAutomation"); const { mainnet } = require("../utils/addresses"); const erc20Abi = require("../../abis/ERC20.json"); const oethARMAbi = require("../../abis/OethARM.json"); @@ -21,14 +21,14 @@ const handler = async (event) => { ); // References to contracts - const asset = new ethers.Contract(mainnet.OETHProxy, erc20Abi, signer); + const baseAsset = new ethers.Contract(mainnet.OETHProxy, erc20Abi, signer); const arm = new ethers.Contract(mainnet.OethARM, oethARMAbi, signer); await autoRequestWithdraw({ signer, - asset, + baseAsset, arm, - minAmount: 1, + minAmount: 5, confirm: true, }); }; diff --git a/src/js/actions/collectEtherFiFees.js b/src/js/actions/collectEtherFiFees.js index 8c172930..e1883c50 100644 --- a/src/js/actions/collectEtherFiFees.js +++ b/src/js/actions/collectEtherFiFees.js @@ -3,7 +3,7 @@ const { ethers } = require("ethers"); const { collectFees } = require("../tasks/admin"); const { mainnet } = require("../utils/addresses"); -const etherFiARMAbi = require("../../abis/EtherFiARM.json"); +const armAbi = require("../../abis/EtherFiARM.json"); // Entrypoint for the Defender Action const handler = async (event) => { @@ -20,7 +20,7 @@ const handler = async (event) => { ); // References to contracts - const arm = new ethers.Contract(mainnet.etherfiARM, etherFiARMAbi, signer); + const arm = new ethers.Contract(mainnet.etherfiARM, armAbi, signer); await collectFees({ signer, diff --git a/src/js/actions/collectOETHFees.js b/src/js/actions/collectOETHFees.js new file mode 100644 index 00000000..200d8938 --- /dev/null +++ b/src/js/actions/collectOETHFees.js @@ -0,0 +1,31 @@ +const { Defender } = require("@openzeppelin/defender-sdk"); +const { ethers } = require("ethers"); + +const { collectFees } = require("../tasks/admin"); +const { mainnet } = require("../utils/addresses"); +const armAbi = require("../../abis/OriginARM.json"); + +// Entrypoint for the Defender Action +const handler = async (event) => { + // Initialize defender relayer provider and signer + const client = new Defender(event); + const provider = client.relaySigner.getProvider({ ethersVersion: "v6" }); + const signer = await client.relaySigner.getSigner(provider, { + speed: "fastest", + ethersVersion: "v6", + }); + + console.log( + `DEBUG env var in handler before being set: "${process.env.DEBUG}"`, + ); + + // References to contracts + const arm = new ethers.Contract(mainnet.OethARM, armAbi, signer); + + await collectFees({ + signer, + arm, + }); +}; + +module.exports = { handler }; diff --git a/src/js/actions/rollup.config.cjs b/src/js/actions/rollup.config.cjs index 95df6802..2911d3b0 100644 --- a/src/js/actions/rollup.config.cjs +++ b/src/js/actions/rollup.config.cjs @@ -49,14 +49,17 @@ const actions = [ "collectFeesSonic", "collectEtherFiFees", "collectEthenaFees", - "allocateSonic", + "collectOETHFees", "collectRewardsSonic", - "setPrices", - "setPricesEtherFi", - "setOSSiloPriceAction", "allocateLido", "allocateEtherFi", "allocateEthena", + "allocateOETH", + "allocateSonic", + "setOSSiloPriceAction", + "setPrices", + "setPricesEtherFi", + "setPricesOETH", ]; module.exports = actions.map((action) => ({ diff --git a/src/js/actions/setPricesOETH.js b/src/js/actions/setPricesOETH.js new file mode 100644 index 00000000..1d1296b9 --- /dev/null +++ b/src/js/actions/setPricesOETH.js @@ -0,0 +1,50 @@ +const { Defender } = require("@openzeppelin/defender-sdk"); +const { ethers } = require("ethers"); + +const { setPrices } = require("../tasks/lidoMorphoPrices"); +const { mainnet } = require("../utils/addresses"); +const armAbi = require("../../abis/OriginARM.json"); + +// Entrypoint for the Defender Action +const handler = async (event) => { + // Initialize defender relayer provider and signer + const client = new Defender(event); + const provider = client.relaySigner.getProvider({ ethersVersion: "v6" }); + const signer = await client.relaySigner.getSigner(provider, { + speed: "fastest", + ethersVersion: "v6", + }); + + console.log( + `DEBUG env var in handler before being set: "${process.env.DEBUG}"`, + ); + + // References to contracts + const arm = new ethers.Contract(mainnet.etherfiARM, armAbi, signer); + + try { + await setPrices({ + signer, + arm, + // sellPrice: 0.9998, + // buyPrice: 0.9997, + maxSellPrice: 0.9999, + minSellPrice: 0.9995, + maxBuyPrice: 0.9995, + minBuyPrice: 0.996, + // inch: true, + // curve: true, + kyber: true, + amount: 10, + tolerance: 0.3, + fee: 5, + offset: 1.0, + priceOffset: true, + blockTag: "latest", + }); + } catch (error) { + console.error(error); + } +}; + +module.exports = { handler }; diff --git a/src/js/tasks/admin.js b/src/js/tasks/admin.js index 124495a4..f70c4b7f 100644 --- a/src/js/tasks/admin.js +++ b/src/js/tasks/admin.js @@ -37,7 +37,7 @@ async function allocate({ } // 1. Call the allocate static call to get the return values - // Returned value can be either a single int256 or a tuple of two int256 values + // Returned value is a tuple of two int256 values let liquidityDelta; [, liquidityDelta] = await arm.allocate.staticCall(); @@ -60,21 +60,14 @@ async function allocate({ if (activeMarketAddress !== ethers.ZeroAddress) { const activeMarket = new ethers.Contract( activeMarketAddress, - ["function market() external view returns (address)"], - signer, - ); - - // Get the underlying ERC-4626 vault. eg Silo or Morpho Vault - const underlyingVaultAddress = await activeMarket.market(); - const underlyingVault = new ethers.Contract( - underlyingVaultAddress, ["function maxWithdraw(address) external view returns (uint256)"], signer, ); // Check there is liquidity available to withdraw from the lending market - const availableAssets = - await underlyingVault.maxWithdraw(activeMarketAddress); + const availableAssets = await activeMarket.maxWithdraw( + await arm.getAddress(), + ); if (availableAssets < parseUnits("0.01", 18)) { log( `Only ${formatUnits(availableAssets)} liquidity available in the active lending market, skipping allocation`, @@ -84,16 +77,17 @@ async function allocate({ } } - // 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`, ); + if (execute) { + // Add 10% buffer to gas limit + let gasLimit = await arm.connect(signer).allocate.estimateGas(); + gasLimit = (gasLimit * 11n) / 10n; + const tx = await arm.connect(signer).allocate({ gasLimit }); await logTxDetails(tx, "allocate"); } diff --git a/src/js/tasks/lido.js b/src/js/tasks/lido.js index b098c20d..6a36a448 100644 --- a/src/js/tasks/lido.js +++ b/src/js/tasks/lido.js @@ -11,6 +11,7 @@ const { } = require("./markets"); const { getBlock } = require("../utils/block"); const { getLidoQueueData } = require("../utils/lido"); +const { getMerklRewards } = require("../utils/merkl"); const { getSigner } = require("../utils/signers"); const { logTxDetails } = require("../utils/txLogger"); const { @@ -226,7 +227,7 @@ const logAssets = async (arm, blockTag) => { let lendingMarketBalance = 0n; // Get the lending market from the active market // Atm we use a hardcoded address, but this should be replaced with a call to the active market once the ARM is upgraded - let marketAddress = "0x29c4Bb7B1eBcc53e8CBd16480B5bAe52C69806D3"; //await arm.activeMarket({ blockTag }); + let marketAddress = await arm.activeMarket({ blockTag }); if (marketAddress != addresses.zero) { const marketContract = await ethers.getContractAt( "Abstract4626MarketWrapper", @@ -269,6 +270,10 @@ const logAssets = async (arm, blockTag) => { const buffer = await arm.armBuffer({ blockTag }); const bufferPercent = (buffer * 10000n) / parseUnits("1"); + const { amount: morphoRewards } = await getMerklRewards({ + userAddress: marketAddress, + }); + console.log(`Assets`); console.log(`liquidity buffer ${formatUnits(bufferPercent, 2)}%`); console.log( @@ -303,6 +308,7 @@ const logAssets = async (arm, blockTag) => { console.log( `${formatUnits(wethInStrategist, 18).padEnd(24)} WETH in Strategist (fees)`, ); + console.log(`${formatUnits(morphoRewards, 18)} MORPHO rewards claimable`); return { totalAssets, totalSupply, liquidityWeth }; }; diff --git a/src/js/tasks/liquidity.js b/src/js/tasks/liquidity.js index 33029885..e2864bf1 100644 --- a/src/js/tasks/liquidity.js +++ b/src/js/tasks/liquidity.js @@ -13,6 +13,7 @@ const { logKyberPrices, logWrappedEtherFiPrices, } = require("./markets"); +const { getMerklRewards } = require("../utils/merkl"); const { logTxDetails } = require("../utils/txLogger"); const log = require("../utils/logger")("task:liquidity"); @@ -20,27 +21,23 @@ const log = require("../utils/logger")("task:liquidity"); // Extend Day.js with the UTC plugin dayjs.extend(utc); -const requestWithdraw = async ({ amount, signer, armName, arm }) => { +const requestWithdraw = async ({ amount, signer, arm }) => { const amountBI = parseUnits(amount.toString(), 18); - log(`About to request ${amount} OETH withdrawal`); + log(`About to request ${amount} oToken withdrawal`); - const functionName = - armName == "Origin" ? "requestOriginWithdrawal" : "requestWithdrawal"; - const tx = await arm.connect(signer)[functionName](amountBI); + const tx = await arm.connect(signer).requestOriginWithdrawal(amountBI); - await logTxDetails(tx, functionName); + await logTxDetails(tx, "requestOriginWithdrawal"); // TODO parse the request id from the WithdrawalRequested event on the OETH Vault }; -const claimWithdraw = async ({ id, signer, armName, arm }) => { - const functionName = - armName == "Origin" ? "claimOriginWithdrawals" : "claimWithdrawals"; - const tx = await arm.connect(signer)[functionName]([id]); +const claimWithdraw = async ({ id, signer, arm }) => { + const tx = await arm.connect(signer).claimOriginWithdrawals([id]); - log(`About to claim withdrawal request ${id} calling ${functionName}`); - await logTxDetails(tx, functionName); + log(`About to claim withdrawal request ${id}`); + await logTxDetails(tx, "claimOriginWithdrawals"); }; const withdrawRequestStatus = async ({ id, arm, vault }) => { @@ -61,6 +58,8 @@ const withdrawRequestStatus = async ({ id, arm, vault }) => { const snap = async ({ arm, block, days, gas, amount, oneInch, kyber }) => { const armContract = await resolveArmContract(arm); + const { chainId } = await ethers.provider.getNetwork(); + const blockTag = await getBlock(block); const { liquidityBalance } = await logLiquidity({ arm, block }); @@ -69,50 +68,58 @@ const snap = async ({ arm, block, days, gas, amount, oneInch, kyber }) => { await logWithdrawalRequests({ blockTag }); } - // This can be removed after OETH is upgraded - if (arm !== "Oeth") { - await logWithdrawalQueue(armContract, blockTag, liquidityBalance); + await logWithdrawalQueue(armContract, blockTag, liquidityBalance); - const armPrices = await logArmPrices({ block, gas, days }, armContract); + const armPrices = await logArmPrices({ block, gas, days }, armContract); - const pair = - arm === "Lido" - ? "stETH/WETH" - : arm === "EtherFi" - ? "eETH/WETH" - : arm == "Origin" + const pair = + arm === "Lido" + ? "stETH/WETH" + : arm === "EtherFi" + ? "eETH/WETH" + : arm === "Ethena" + ? "sUSDe/USDe" + : arm == "Origin" && chainId === 146 ? "OS/wS" - : arm === "Ethena" - ? "sUSDe/USDe" - : null; - const assets = { - liquid: await armContract.liquidityAsset(), - base: await armContract.baseAsset(), - }; - - let wrapPrice; - if (arm === "Ethena") { - wrapPrice = await convertToAsset(assets.base, amount); - } + : "OETH/WETH"; + const assets = { + liquid: await armContract.liquidityAsset(), + base: await armContract.baseAsset(), + }; + + let wrapPrice; + if (arm === "Ethena") { + wrapPrice = await convertToAsset(assets.base, amount); + const actualArmSellPrice = + (armPrices.sellPrice * wrapPrice) / parseUnits("1", 18); + const actualArmBuyPrice = + (armPrices.buyPrice * wrapPrice) / parseUnits("1", 18); + + console.log(`\nEthena : ${formatUnits(wrapPrice, 18)} sUSDe/USDe`); + console.log( + `Sell : ${formatUnits(actualArmSellPrice, 18).padEnd(20)} sUSDe/USDe`, + ); + console.log( + `Buy : ${formatUnits(actualArmBuyPrice, 18).padEnd(20)} sUSDe/USDe`, + ); + } - if (oneInch) { - const fee = arm === "Lido" ? 10n : 30n; + if (oneInch) { + const fee = arm === "Lido" ? 10n : 30n; - const chainId = await (await ethers.provider.getNetwork()).chainId; - await log1InchPrices( - { amount, assets, fee, pair, chainId, wrapPrice }, - armPrices, - ); + await log1InchPrices( + { amount, assets, fee, pair, chainId, wrapPrice }, + armPrices, + ); - if (arm === "EtherFi") { - await logWrappedEtherFiPrices({ amount, armPrices }); - } + if (arm === "EtherFi") { + await logWrappedEtherFiPrices({ amount, armPrices }); } + } - if (kyber && arm !== "Origin") { - // Kyber does not support Sonic - await logKyberPrices({ amount, assets, pair, wrapPrice }, armPrices); - } + if (kyber && chainId !== 146) { + // Kyber does not support Sonic + await logKyberPrices({ amount, assets, pair, wrapPrice }, armPrices); } }; @@ -163,6 +170,7 @@ const logLiquidity = async ({ block, arm }) => { } let lendingMarketBalance = 0n; + let morphoRewards = 0n; // TODO this can be removed after OETH is upgraded if (arm !== "Oeth") { // Get the lending market from the active SiloMarket @@ -175,6 +183,13 @@ const logLiquidity = async ({ block, arm }) => { lendingMarketBalance = await market.convertToAssets(armShares, { blockTag, }); + + if (arm !== "Ethena") { + const { amount } = await getMerklRewards({ + userAddress: marketAddress, + }); + morphoRewards = amount; + } } const total = @@ -226,6 +241,7 @@ const logLiquidity = async ({ block, arm }) => { console.log(`${formatUnits(accruedFees, 18)} accrued fees`); console.log(`${formatUnits(totalAssets, 18)} total assets`); console.log(`liquidity buffer ${formatUnits(bufferPercent, 2)}%`); + console.log(`${formatUnits(morphoRewards, 18)} MORPHO rewards claimable`); return { total, liquidityBalance }; }; diff --git a/src/js/tasks/liquidityAutomation.js b/src/js/tasks/liquidityAutomation.js index 23bb3993..1583f999 100644 --- a/src/js/tasks/liquidityAutomation.js +++ b/src/js/tasks/liquidityAutomation.js @@ -12,13 +12,13 @@ dayjs.extend(utc); const autoRequestWithdraw = async ({ signer, - asset, + baseAsset, arm, minAmount, confirm, }) => { - const symbol = await asset.symbol(); - const assetBalance = await asset.balanceOf(await arm.getAddress()); + const symbol = await baseAsset.symbol(); + const assetBalance = await baseAsset.balanceOf(await arm.getAddress()); log(`${formatUnits(assetBalance)} ${symbol} in ARM`); const minAmountBI = parseUnits(minAmount.toString(), 18); @@ -34,10 +34,8 @@ const autoRequestWithdraw = async ({ log(`About to request ${formatUnits(assetBalance)} ${symbol} withdrawal`); - const functionName = - symbol == "OS" ? "requestOriginWithdrawal" : "requestWithdrawal"; - const tx = await arm.connect(signer)[functionName](assetBalance); - await logTxDetails(tx, "requestWithdrawal", confirm); + const tx = await arm.connect(signer).requestOriginWithdrawal(assetBalance); + await logTxDetails(tx, "requestOriginWithdrawal", confirm); }; const autoClaimWithdraw = async ({ @@ -85,10 +83,8 @@ const autoClaimWithdraw = async ({ log(`About to claim requests: ${requestIds} `); - const functionName = - liquiditySymbol == "wS" ? "claimOriginWithdrawals" : "claimWithdrawals"; - const tx = await arm.connect(signer)[functionName](requestIds); - await logTxDetails(tx, "claimWithdrawals", confirm); + const tx = await arm.connect(signer).claimOriginWithdrawals(requestIds); + await logTxDetails(tx, "claimOriginWithdrawals", confirm); return requestIds; }; diff --git a/src/js/tasks/tasks.js b/src/js/tasks/tasks.js index ca2520f3..5436ebaa 100644 --- a/src/js/tasks/tasks.js +++ b/src/js/tasks/tasks.js @@ -84,7 +84,7 @@ subtask( ) .addParam( "arm", - "Name of the ARM. eg Lido, Origin, Oeth, EtherFi or Ethena", + "Name of the ARM. eg Lido, Origin, EtherFi or Ethena", "Lido", types.string, ) @@ -138,36 +138,32 @@ task("swapLido").setAction(async (_, __, runSuper) => { return runSuper(); }); -// OETH ARM Liquidity management +// Origin ARM Liquidity management subtask( "autoRequestWithdraw", "Request withdrawal of base asset (WETH/OS) from the Origin Vault", ) - .addOptionalParam( - "arm", - "The name of the ARM. eg Oeth or Origin", - "Oeth", - types.string, - ) .addOptionalParam( "minAmount", - "Minimum amount of OETH that will be withdrawn", + "Minimum amount of oTokens that will be withdrawn", 2, types.float, ) .setAction(async (taskArgs) => { - const arm = taskArgs.arm; const signer = await getSigner(); - const assetSymbol = arm === "Oeth" ? "OETH" : "OS"; - const asset = await resolveAsset(assetSymbol); - const armContract = await resolveArmContract(arm); + const armContract = await resolveArmContract("Origin"); + const baseAssetAddress = await armContract.baseAsset(); + const baseAsset = await ethers.getContractAt( + "IERC20Metadata", + baseAssetAddress, + ); await autoRequestWithdraw({ ...taskArgs, signer, - asset, + baseAsset, arm: armContract, }); }); @@ -175,33 +171,29 @@ task("autoRequestWithdraw").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask("autoClaimWithdraw", "Claim withdrawal requests from the OETH Vault") - .addOptionalParam( - "arm", - "The name of the ARM. eg Oeth or Origin", - "Oeth", - types.string, - ) - .setAction(async (taskArgs) => { - const arm = taskArgs.arm; - const signer = await getSigner(); - const liquiditySymbol = arm === "Oeth" ? "WETH" : "WS"; - const liquidityAsset = await resolveAsset(liquiditySymbol); - - const armContract = await resolveArmContract(arm); +subtask( + "autoClaimWithdraw", + "Claim withdrawal requests from an Origin Vault", +).setAction(async (taskArgs) => { + const signer = await getSigner(); - const vaultName = arm === "Oeth" ? "OETH" : "OS"; - const vaultAddress = await parseAddress(`${vaultName}_VAULT`); - const vault = await ethers.getContractAt("IOriginVault", vaultAddress); + const armContract = await resolveArmContract("Origin"); + const vaultAddress = await armContract.vault(); + const vault = await ethers.getContractAt("IOriginVault", vaultAddress); + const assetAddress = await armContract.asset(); + const liquidityAsset = await ethers.getContractAt( + "IERC20Metadata", + assetAddress, + ); - await autoClaimWithdraw({ - ...taskArgs, - signer, - liquidityAsset, - arm: armContract, - vault, - }); + await autoClaimWithdraw({ + ...taskArgs, + signer, + liquidityAsset, + arm: armContract, + vault, }); +}); task("autoClaimWithdraw").setAction(async (_, __, runSuper) => { return runSuper(); }); @@ -210,22 +202,15 @@ subtask( "requestWithdraw", "Request a specific amount of oTokens to withdraw from the Vault", ) - .addOptionalParam( - "arm", - "The name of the ARM. eg Oeth or Origin", - "Oeth", - types.string, - ) - .addParam("amount", "OETH withdraw amount", 50, types.float) + .addParam("amount", "oToken withdraw amount", 50, types.float) .setAction(async (taskArgs) => { const signer = await getSigner(); - const armContract = await resolveArmContract(taskArgs.arm); + const armContract = await resolveArmContract("Origin"); await requestWithdraw({ ...taskArgs, signer, - armName: taskArgs.arm, arm: armContract, }); }); @@ -234,22 +219,15 @@ task("requestWithdraw").setAction(async (_, __, runSuper) => { }); subtask("claimWithdraw", "Claim a requested oToken withdrawal from the Vault") - .addOptionalParam( - "arm", - "The name of the ARM. eg Oeth or Origin", - "Oeth", - types.string, - ) .addParam("id", "Request identifier", undefined, types.string) .setAction(async (taskArgs) => { const signer = await getSigner(); - const armContract = await resolveArmContract(taskArgs.arm); + const armContract = await resolveArmContract("Origin"); await claimWithdraw({ ...taskArgs, signer, - armName: taskArgs.arm, arm: armContract, }); }); @@ -257,20 +235,13 @@ task("claimWithdraw").setAction(async (_, __, runSuper) => { return runSuper(); }); -subtask("withdrawStatus", "Get the status of a OETH withdrawal request") - .addOptionalParam( - "arm", - "The name of the ARM. eg Oeth or Origin", - "Oeth", - types.string, - ) +subtask("withdrawStatus", "Get the status of a oToken withdrawal request") .addParam("id", "Request number", undefined, types.string) .setAction(async (taskArgs) => { const signer = await getSigner(); - const armContract = await resolveArmContract(taskArgs.arm); - const vaultName = taskArgs.arm === "Oeth" ? "OETH_VAULT" : "OS_VAULT"; - const vaultAddress = await parseAddress(vaultName); + const armContract = await resolveArmContract("Origin"); + const vaultAddress = await armContract.vault(); const vault = await ethers.getContractAt("IOriginVault", vaultAddress); await withdrawRequestStatus({ @@ -284,7 +255,8 @@ task("withdrawStatus").setAction(async (_, __, runSuper) => { return runSuper(); }); -// Token tasks. +// Token tasks + subtask("allowance", "Get the token allowance an owner has given to a spender") .addParam( "symbol", @@ -392,6 +364,7 @@ task("transferFrom").setAction(async (_, __, runSuper) => { }); // WETH tasks + subtask("depositWETH", "Deposit ETH into WETH") .addParam("amount", "Amount of ETH to deposit", undefined, types.float) .setAction(async (taskArgs) => { @@ -429,7 +402,7 @@ task("submitLido").setAction(async (_, __, runSuper) => { return runSuper(); }); -// Vault tasks. +// Origin Vault tasks task( "queueLiquidity", @@ -971,7 +944,7 @@ task("setHarvester").setAction(async (_, __, runSuper) => { subtask("allocate", "Allocate to/from the active lending market") .addOptionalParam( "arm", - "The name of the ARM. eg Lido, OETH, Origin, EtherFi or Ethena", + "The name of the ARM. eg Lido, Origin, EtherFi or Ethena", "Origin", types.string, ) @@ -1008,7 +981,7 @@ task("allocate").setAction(async (_, __, runSuper) => { subtask("setARMBuffer", "Set the ARM buffer percentage") .addOptionalParam( "arm", - "The name of the ARM. eg Lido, OETH, Origin, EtherFi or Ethena", + "The name of the ARM. eg Lido, Origin, EtherFi or Ethena", "Origin", types.string, ) @@ -1202,7 +1175,7 @@ task("snapMarket").setAction(async (_, __, runSuper) => { subtask("snap", "Take a snapshot of the an ARM") .addOptionalParam( "arm", - "The name of the ARM. eg Lido, Oeth, Origin, EtherFi or Ethena", + "The name of the ARM. eg Lido, Origin, EtherFi or Ethena", "Lido", types.string, ) @@ -1352,7 +1325,7 @@ subtask( ) .addOptionalParam( "arm", - "Name of the ARM. eg Lido, Origin or Oeth", + "Name of the ARM. currently only Origin", "Origin", types.string, ) @@ -1459,7 +1432,7 @@ task("setOSSiloPrice").setAction(async (_, __, runSuper) => { subtask("claimMerklRewards", "Claim Merkl rewards for Morpho markets") .addOptionalParam( "arm", - "Name of the ARM. eg Lido, Ether.fi or Oeth", + "Name of the ARM. eg Lido, Ether.fi or Origin", "Lido", types.string, ) diff --git a/src/js/utils/merkl.js b/src/js/utils/merkl.js index 0a28bbbd..92cbb18a 100644 --- a/src/js/utils/merkl.js +++ b/src/js/utils/merkl.js @@ -1,14 +1,25 @@ const axios = require("axios"); +const log = require("../utils/logger")("task:merkl"); + const MERKL_API_ENDPOINT = "https://api.merkl.xyz/v4"; const getMerklRewards = async ({ userAddress, chainId = 1 }) => { const url = `${MERKL_API_ENDPOINT}/users/${userAddress}/rewards?chainId=${chainId}`; try { + log(`Fetching Merkl rewards from ${url}`); const response = await axios.get(url); + if (response.data.length === 0 || response.data[0].rewards.length === 0) { + return { + amount: 0n, + token: null, + proofs: [], + }; + } + return { - amount: response.data[0].rewards[0].amount, + amount: response.data[0].rewards[0].pending, token: response.data[0].rewards[0].token.address, proofs: response.data[0].rewards[0].proofs, }; diff --git a/test/smoke/OethARMSmokeTest.t.sol b/test/smoke/OethARMSmokeTest.t.sol index 30d8b571..eca2ef7b 100644 --- a/test/smoke/OethARMSmokeTest.t.sol +++ b/test/smoke/OethARMSmokeTest.t.sol @@ -28,8 +28,8 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { vm.label(address(oeth), "OETH"); vm.label(address(operator), "OPERATOR"); - proxy = Proxy(payable(deployManager.getDeployment("OETH_ARM"))); - originARM = OriginARM(deployManager.getDeployment("OETH_ARM")); + proxy = Proxy(payable(deployManager.getDeployment("ORIGIN_ARM"))); + originARM = OriginARM(deployManager.getDeployment("ORIGIN_ARM")); _dealWETH(address(originARM), 100 ether); _dealOETH(address(originARM), 100 ether); @@ -100,8 +100,8 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { vm.startPrank(address(originARM)); oeth.transfer(address(this), oeth.balanceOf(address(originARM))); vm.stopPrank(); - vm.prank(Mainnet.TIMELOCK); - originARM.setCrossPrice(0.9999e36); + //vm.prank(Mainnet.TIMELOCK); + //originARM.setCrossPrice(0.9995e36); // trader buys OETH and sells WETH, the ARM sells OETH at a // 0.5 bps discount @@ -121,7 +121,7 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { expectedOut = amountIn * 1e36 / price; vm.prank(Mainnet.ARM_RELAYER); - originARM.setPrices(price - 2e32, price); + originARM.setPrices(0.99e36, price); } else { // Trader is selling stETH and buying WETH // the ARM is buying stETH and selling WETH @@ -161,8 +161,8 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { vm.startPrank(address(originARM)); oeth.transfer(address(this), oeth.balanceOf(address(originARM))); vm.stopPrank(); - vm.prank(Mainnet.TIMELOCK); - originARM.setCrossPrice(0.9999e36); + //vm.prank(Mainnet.TIMELOCK); + //originARM.setCrossPrice(0.9999e36); // trader buys OETH and sells WETH, the ARM sells OETH at a // 0.5 bps discount @@ -182,7 +182,7 @@ contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { expectedIn = amountOut * price / 1e36; vm.prank(Mainnet.ARM_RELAYER); - originARM.setPrices(price - 2e32, price); + originARM.setPrices(0.99e36, price); } else { // Trader is selling stETH and buying WETH // the ARM is buying stETH and selling WETH