From 8af08d02f57b1d43c7d91a42dac56f3fb7cdb60b Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Fri, 16 Aug 2024 20:35:02 +1000 Subject: [PATCH] Defender Actions setup (#12) * WIP outstanding requests * Got autoClaimWithdraw Hardhat task working * Added setActionVars Hardhat task Got Defender Actions working * Add await for contract.getAddress() * Downgrade to ethers v5 --- README.md | 28 ++- hardhat.config.js | 2 +- package.json | 9 +- src/abis/vault.json | 236 ++++++++++++++++++ src/contracts/Interfaces.sol | 8 +- src/js/actions/autoClaimWithdraw.js | 41 +++ .../autoRequestWithdraw.js | 21 +- .../{autotasks => actions}/rollup.config.cjs | 4 +- src/js/autotasks/autoClaimWithdraws.js | 43 ---- src/js/tasks/defender.js | 25 ++ src/js/tasks/liquidity.js | 79 ++++-- src/js/tasks/swap.js | 2 +- src/js/tasks/tasks.js | 44 +++- src/js/tasks/tokens.js | 2 +- src/js/tasks/vault.js | 6 +- src/js/tasks/weth.js | 2 +- src/js/utils/queue.js | 126 ++++++++++ src/js/utils/txLogger.js | 2 +- 18 files changed, 577 insertions(+), 103 deletions(-) create mode 100644 src/abis/vault.json create mode 100644 src/js/actions/autoClaimWithdraw.js rename src/js/{autotasks => actions}/autoRequestWithdraw.js (71%) rename src/js/{autotasks => actions}/rollup.config.cjs (91%) delete mode 100644 src/js/autotasks/autoClaimWithdraws.js create mode 100644 src/js/tasks/defender.js create mode 100644 src/js/utils/queue.js diff --git a/README.md b/README.md index d212e0a..9c492bc 100644 --- a/README.md +++ b/README.md @@ -204,19 +204,19 @@ forge verify-contract 0x6bac785889A4127dB0e0CeFEE88E0a9F1Aaf3cC7 Proxy [Open Zeppelin Defender v2](https://docs.openzeppelin.com/defender/v2/) is used to manage the Operations account and automate AMM operational jobs like managing liquidity. -### Deploying Defender Autotasks +### Deploying Defender Actions -Autotasks are used to run operational jobs are specific times or intervals. +Defender Actions are used to run operational jobs are specific times or intervals. -[rollup](https://rollupjs.org/) is used to bundle the Autotask source code in [/src/js/autotasks](./src/js/autotasks) into a single file that can be uploaded to Defender. The implementation was based off [Defender Autotask example using Rollup](https://github.com/OpenZeppelin/defender-autotask-examples/tree/master/rollup). The rollup config is in [/src/js/autotasks/rollup.config.cjs](./src/js/autotasks/rollup.config.cjs). The outputs are written to task-specific folders under [/src/js/autotasks/dist](./src/js/autotasks/dist/). +[rollup](https://rollupjs.org/) is used to bundle the Actions source code in [/src/js/actions](./src/js/actions) into a single file that can be uploaded to Defender. The implementation was based off [Defender Action example using Rollup](https://github.com/OpenZeppelin/defender-autotask-examples/tree/master/rollup). The rollup config is in [/src/js/actions/rollup.config.cjs](./src/js/actions/rollup.config.cjs). The outputs are written to task-specific folders under [/src/js/actions/dist](./src/js/actions/dist/). The [defender-autotask CLI](https://www.npmjs.com/package/@openzeppelin/defender-autotask-client) is used to upload the Action code to Defender. For this to work, a Defender Team API key with `Manage Actions` capabilities is needed. This can be generated by a Defender team admin under the `Manage` tab on the top right of the UI and then `API Keys` on the left menu. -The following will set the Defender Team API key and bundle the Autotask code ready for upload. +The following will set the Defender Team API key and bundle the Actions code ready for upload. -``` -cd ./src/js/autotasks +```bash +cd ./src/js/actions # Export the Defender Team API key. This is different to the Defender Relayer API key. export API_KEY= @@ -225,13 +225,21 @@ export API_SECRET= npx rollup -c ``` -The following will upload the different Autotask bundles to Defender. +The following will upload the different Action bundles to Defender. -``` -# autoRequestWithdraw +```bash -# autoClaimWithdraws +# Set the DEBUG environment variable to oeth* for the Defender Action +npx hardhat setActionVars --id 93c010f9-05b5-460f-bd10-1205dd80a7c9 +npx hardhat setActionVars --id 563d8d0c-17dc-46d3-8955-e4824864869f + +# The Defender autotask client uses generic env var names so we'll set them first from the values in the .env file +export API_KEY= +export API_SECRET= +# Mainnet +npx defender-autotask update-code 93c010f9-05b5-460f-bd10-1205dd80a7c9 ./dist/autoRequestWithdraw +npx defender-autotask update-code 563d8d0c-17dc-46d3-8955-e4824864869f ./dist/autoClaimWithdraw ``` `rollup` and `defender-autotask` can be installed globally to avoid the `npx` prefix. diff --git a/hardhat.config.js b/hardhat.config.js index 3f6508a..1b52b1a 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -2,7 +2,7 @@ require("dotenv").config(); -require("@nomicfoundation/hardhat-ethers"); +require("@nomiclabs/hardhat-ethers"); require("@nomicfoundation/hardhat-foundry"); require("./src/js/tasks/tasks"); diff --git a/package.json b/package.json index 39f2fba..798dc4e 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,12 @@ }, "license": "MIT", "devDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.5", + "@apollo/client": "^3.11.4", + "@nomiclabs/hardhat-ethers": "2.0.2", "@nomicfoundation/hardhat-foundry": "^1.1.1", "@nomicfoundation/hardhat-network-helpers": "^1.0.10", "@openzeppelin/defender-autotask-client": "^1.54.1", - "@openzeppelin/defender-sdk": "^1.11.0", + "@openzeppelin/defender-sdk": "1.13.1", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", @@ -24,9 +25,11 @@ "axios": "^1.6.2", "builtin-modules": "^3.3.0", "chai": "^4.3.10", + "dayjs": "^1.11.12", "dotenv": "^16.4.5", "eslint": "^8.55.0", - "ethers": "^6.11.1", + "ethers": "5.7.2", + "graphql": "^16.9.0", "hardhat": "^2.19.2", "rollup": "^4.9.1" } diff --git a/src/abis/vault.json b/src/abis/vault.json new file mode 100644 index 0000000..3cb1461 --- /dev/null +++ b/src/abis/vault.json @@ -0,0 +1,236 @@ +[ + { + "inputs": [], + "name": "CLAIM_DELAY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "addWithdrawalQueueLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "claimWithdrawal", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "requestIds", + "type": "uint256[]" + } + ], + "name": "claimWithdrawals", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "totalAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dripper", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "governor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minimumOusdAmount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minimumUnitAmount", + "type": "uint256" + } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "requestWithdrawal", + "outputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "queued", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_maxSupplyDiff", + "type": "uint256" + } + ], + "name": "setMaxSupplyDiff", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalQueueMetadata", + "outputs": [ + { + "internalType": "uint128", + "name": "queued", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "claimable", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "claimed", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "nextWithdrawalIndex", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "name": "withdrawalRequests", + "outputs": [ + { + "internalType": "address", + "name": "withdrawer", + "type": "address" + }, + { + "internalType": "bool", + "name": "claimed", + "type": "bool" + }, + { + "internalType": "uint40", + "name": "timestamp", + "type": "uint40" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "queued", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/contracts/Interfaces.sol b/src/contracts/Interfaces.sol index 106a45f..0dab405 100644 --- a/src/contracts/Interfaces.sol +++ b/src/contracts/Interfaces.sol @@ -130,19 +130,21 @@ interface IOETHVault { function setMaxSupplyDiff(uint256 _maxSupplyDiff) external; - function governor() external returns (address); + function governor() external view returns (address); - function dripper() external returns (address); + function dripper() external view returns (address); function withdrawalQueueMetadata() external + view returns (uint128 queued, uint128 claimable, uint128 claimed, uint128 nextWithdrawalIndex); function withdrawalRequests(uint256 requestId) external + view returns (address withdrawer, bool claimed, uint40 timestamp, uint128 amount, uint128 queued); - function CLAIM_DELAY() external returns (uint256); + function CLAIM_DELAY() external view returns (uint256); } interface IGovernance { diff --git a/src/js/actions/autoClaimWithdraw.js b/src/js/actions/autoClaimWithdraw.js new file mode 100644 index 0000000..d5df381 --- /dev/null +++ b/src/js/actions/autoClaimWithdraw.js @@ -0,0 +1,41 @@ +const { + DefenderRelaySigner, + DefenderRelayProvider, +} = require("defender-relay-client/lib/ethers"); +const { ethers } = require("ethers"); + +const { autoClaimWithdraw } = require("../tasks/liquidity"); +const { mainnet } = require("../utils/addresses"); +const erc20Abi = require("../../abis/ERC20.json"); +const oethARMAbi = require("../../abis/OethARM.json"); +const vaultAbi = require("../../abis/vault.json"); + +// Entrypoint for the Autotask +const handler = async (event) => { + // Initialize defender relayer provider and signer + const provider = new DefenderRelayProvider(event); + const signer = new DefenderRelaySigner(event, provider, { speed: "fastest" }); + + console.log( + `DEBUG env var in handler before being set: "${process.env.DEBUG}"` + ); + + // References to contracts + const weth = new ethers.Contract(mainnet.WETH, erc20Abi, signer); + const vault = new ethers.Contract(mainnet.OETHVaultProxy, vaultAbi, signer); + const oethARM = new ethers.Contract(mainnet.OethARM, oethARMAbi, signer); + + try { + await autoClaimWithdraw({ + signer, + weth, + oethARM, + vault, + confirm: false, + }); + } catch (error) { + console.error(error); + } +}; + +module.exports = { handler }; diff --git a/src/js/autotasks/autoRequestWithdraw.js b/src/js/actions/autoRequestWithdraw.js similarity index 71% rename from src/js/autotasks/autoRequestWithdraw.js rename to src/js/actions/autoRequestWithdraw.js index d0a9a96..03f26ab 100644 --- a/src/js/autotasks/autoRequestWithdraw.js +++ b/src/js/actions/autoRequestWithdraw.js @@ -4,7 +4,7 @@ const { } = require("defender-relay-client/lib/ethers"); const { ethers } = require("ethers"); -const { autoWithdraw } = require("../tasks/liquidity"); +const { autoRequestWithdraw } = require("../tasks/liquidity"); const { mainnet } = require("../utils/addresses"); const erc20Abi = require("../../abis/ERC20.json"); const oethARMAbi = require("../../abis/OethARM.json"); @@ -20,15 +20,20 @@ const handler = async (event) => { ); // References to contracts - const oeth = new ethers.Contract(mainnet.OETH, erc20Abi, signer); + const oeth = new ethers.Contract(mainnet.OETHProxy, erc20Abi, signer); const oethARM = new ethers.Contract(mainnet.OethARM, oethARMAbi, signer); - await autoWithdraw({ - signer, - oethARM, - oeth, - minAmount: 2, - }); + try { + await autoRequestWithdraw({ + signer, + oeth, + oethARM, + minAmount: 0.001, + confirm: false, + }); + } catch (error) { + console.error(error); + } }; module.exports = { handler }; diff --git a/src/js/autotasks/rollup.config.cjs b/src/js/actions/rollup.config.cjs similarity index 91% rename from src/js/autotasks/rollup.config.cjs rename to src/js/actions/rollup.config.cjs index 1e40697..ba83f15 100644 --- a/src/js/autotasks/rollup.config.cjs +++ b/src/js/actions/rollup.config.cjs @@ -31,9 +31,9 @@ module.exports = [ ...commonConfig, }, { - input: "autoClaimWithdraws.js", + input: "autoClaimWithdraw.js", output: { - file: "dist/autoClaimWithdraws/index.js", + file: "dist/autoClaimWithdraw/index.js", format: "cjs", }, ...commonConfig, diff --git a/src/js/autotasks/autoClaimWithdraws.js b/src/js/autotasks/autoClaimWithdraws.js deleted file mode 100644 index 0d8f840..0000000 --- a/src/js/autotasks/autoClaimWithdraws.js +++ /dev/null @@ -1,43 +0,0 @@ -const { - DefenderRelaySigner, - DefenderRelayProvider, -} = require("defender-relay-client/lib/ethers"); -const { ethers } = require("ethers"); - -const { autoClaimStEth } = require("../tasks/liquidity"); -const addresses = require("../utils/addresses"); -const oethARMAbi = require("../../abis/OEthARM.json"); -const LidoStEthWithdrawalQueue = require("../../abis/LidoWithdrawQueue.json"); - -// Entrypoint for the Autotask -const handler = async (event) => { - // Initialize defender relayer provider and signer - const provider = new DefenderRelayProvider(event); - const signer = new DefenderRelaySigner(event, provider, { speed: "fastest" }); - - console.log( - `DEBUG env var in handler before being set: "${process.env.DEBUG}"` - ); - - // References to contracts - const oethARM = new ethers.Contract( - addresses.mainnet.OEthARM, - oethARMAbi, - signer - ); - const withdrawalQueue = new ethers.Contract( - addresses.mainnet.stETHWithdrawalQueue, - LidoStEthWithdrawalQueue, - signer - ); - - await autoClaimStEth({ - asset: "WETH", - signer, - oethARM, - withdrawalQueue, - confirm: false, - }); -}; - -module.exports = { handler }; diff --git a/src/js/tasks/defender.js b/src/js/tasks/defender.js new file mode 100644 index 0000000..4565d79 --- /dev/null +++ b/src/js/tasks/defender.js @@ -0,0 +1,25 @@ +const { AutotaskClient } = require("@openzeppelin/defender-autotask-client"); +const path = require("path"); + +require("dotenv").config({ path: path.resolve(__dirname, "../../../.env") }); + +const log = require("../utils/logger")("task:defender"); + +const setActionVars = async (options) => { + log(`Used DEFENDER_TEAM_KEY ${process.env.DEFENDER_TEAM_KEY}`); + const creds = { + apiKey: process.env.DEFENDER_TEAM_KEY, + apiSecret: process.env.DEFENDER_TEAM_SECRET, + }; + const client = new AutotaskClient(creds); + + // Update Variables + const variables = await client.updateEnvironmentVariables(options.id, { + DEBUG: "origin*", + }); + console.log("updated Autotask environment variables", variables); +}; + +module.exports = { + setActionVars, +}; diff --git a/src/js/tasks/liquidity.js b/src/js/tasks/liquidity.js index 1598d0a..96f336c 100644 --- a/src/js/tasks/liquidity.js +++ b/src/js/tasks/liquidity.js @@ -1,14 +1,16 @@ -const { formatUnits, parseUnits } = require("ethers"); +const { formatUnits, parseUnits } = require("ethers").utils; const { parseAddress } = require("../utils/addressParser"); const { resolveAsset } = require("../utils/assets"); +const { + claimableRequests, + outstandingWithdrawalAmount, +} = require("../utils/queue"); const { logTxDetails } = require("../utils/txLogger"); const log = require("../utils/logger")("task:liquidity"); -const requestWithdraw = async (options) => { - const { amount, signer, oethARM } = options; - +const requestWithdraw = async ({ amount, signer, oethARM }) => { const amountBI = parseUnits(amount.toString(), 18); log(`About to request ${amount} OETH withdrawal`); @@ -28,11 +30,14 @@ const claimWithdraw = async ({ id, signer, oethARM }) => { await logTxDetails(tx, "claimWithdrawal"); }; -const autoWithdraw = async (options) => { - const { signer, oeth, oethARM, minAmount } = options; - - const oethArmAddr = oethARM.getAddress(); - const oethBalance = await oeth.balanceOf(oethArmAddr); +const autoRequestWithdraw = async ({ + signer, + oeth, + oethARM, + minAmount, + confirm, +}) => { + const oethBalance = await oeth.balanceOf(oethARM.address); log(`${formatUnits(oethBalance)} OETH in ARM`); const minAmountBI = parseUnits(minAmount.toString(), 18); @@ -49,14 +54,39 @@ const autoWithdraw = async (options) => { log(`About to request ${formatUnits(oethBalance)} OETH withdrawal`); const tx = await oethARM.connect(signer).requestWithdrawal(oethBalance); - - await logTxDetails(tx, "requestWithdrawal", options.confirm); + await logTxDetails(tx, "requestWithdrawal", confirm); }; -const withdrawRequestStatus = async (options) => { - const { id, oethARM } = options; +const autoClaimWithdraw = async ({ signer, weth, oethARM, vault, confirm }) => { + // Get amount of requests that have already been claimed + const { claimed } = await vault.withdrawalQueueMetadata(); + + // Get WETH balance in OETH Vault + const wethBalance = await weth.balanceOf(oethARM.address); + + const queuedAmountClaimable = claimed.add(wethBalance); + log( + `Claimable queued amount is ${formatUnits(claimed)} claimed + ${formatUnits( + wethBalance + )} WETH in vault = ${formatUnits(queuedAmountClaimable)}` + ); + + // get claimable withdrawal requests + let requestIds = await claimableRequests({ + withdrawer: oethARM.address, + queuedAmountClaimable, + }); + + log(`About to claim requests: ${requestIds} `); + + if (requestIds.length > 0) { + const tx = await oethARM.connect(signer).claimWithdrawals(requestIds); + await logTxDetails(tx, "claimWithdrawals", confirm); + } +}; - const queue = await oethARM.withdrawalQueueMetadata(); +const withdrawRequestStatus = async ({ id, oethARM, vault }) => { + const queue = await vault.withdrawalQueueMetadata(); const request = await oethARM.withdrawalRequests(id); if (request.queued <= queue.claimable) { @@ -77,18 +107,19 @@ const logLiquidity = async () => { const oethARM = await ethers.getContractAt("OethARM", oethArmAddress); const weth = await resolveAsset("WETH"); - const liquidityWeth = await weth.balanceOf(oethARM.getAddress()); + const liquidityWeth = await weth.balanceOf(oethARM.address); const oeth = await resolveAsset("OETH"); - const liquidityOeth = await oeth.balanceOf(oethARM.getAddress()); - // TODO need to get from indexer - const liquidityOethWithdraws = 0n; + const liquidityOeth = await oeth.balanceOf(oethARM.address); + const liquidityOethWithdraws = await outstandingWithdrawalAmount({ + withdrawer: oethARM.address, + }); - const total = liquidityWeth + liquidityOeth + liquidityOethWithdraws; - const wethPercent = total == 0 ? 0 : (liquidityWeth * 10000n) / total; + const total = liquidityWeth.add(liquidityOeth).add(liquidityOethWithdraws); + const wethPercent = total == 0 ? 0 : liquidityWeth.mul(10000).div(total); const oethWithdrawsPercent = - total == 0 ? 0 : (liquidityOethWithdraws * 10000n) / total; - const oethPercent = total == 0 ? 0 : (liquidityOeth * 10000n) / total; + total == 0 ? 0 : liquidityOethWithdraws.mul(10000).div(total); + const oethPercent = total == 0 ? 0 : liquidityOeth.mul(10000).div(total); console.log( `${formatUnits(liquidityWeth, 18)} WETH ${formatUnits(wethPercent, 2)}%` @@ -102,10 +133,12 @@ const logLiquidity = async () => { 18 )} OETH in withdrawal requests ${formatUnits(oethWithdrawsPercent, 2)}%` ); + console.log(`${formatUnits(total, 18)} total WETH and OETH`); }; module.exports = { - autoWithdraw, + autoRequestWithdraw, + autoClaimWithdraw, logLiquidity, requestWithdraw, claimWithdraw, diff --git a/src/js/tasks/swap.js b/src/js/tasks/swap.js index 4e64178..3056b26 100644 --- a/src/js/tasks/swap.js +++ b/src/js/tasks/swap.js @@ -1,4 +1,4 @@ -const { parseUnits, MaxInt256 } = require("ethers"); +const { parseUnits, MaxInt256 } = require("ethers").utils; const { resolveAddress } = require("../utils/assets"); const { getSigner } = require("../utils/signers"); diff --git a/src/js/tasks/tasks.js b/src/js/tasks/tasks.js index 17f5699..122b575 100644 --- a/src/js/tasks/tasks.js +++ b/src/js/tasks/tasks.js @@ -2,8 +2,10 @@ const { subtask, task, types } = require("hardhat/config"); const { parseAddress } = require("../utils/addressParser"); const { setAutotaskVars } = require("./autotask"); +const { setActionVars } = require("./defender"); const { - autoWithdraw, + autoRequestWithdraw, + autoClaimWithdraw, requestWithdraw, claimWithdraw, logLiquidity, @@ -68,7 +70,7 @@ subtask("autoRequestWithdraw", "Request withdrawal of WETH from the OETH Vault") const weth = await resolveAsset("WETH"); const oethArmAddress = await parseAddress("OETH_ARM"); const oethARM = await ethers.getContractAt("OethARM", oethArmAddress); - await autoWithdraw({ + await autoRequestWithdraw({ ...taskArgs, signer, oeth, @@ -80,6 +82,29 @@ task("autoRequestWithdraw").setAction(async (_, __, runSuper) => { return runSuper(); }); +subtask( + "autoClaimWithdraw", + "Claim withdrawal requests from the OETH Vault" +).setAction(async (taskArgs) => { + const signer = await getSigner(); + const weth = await resolveAsset("WETH"); + const oethArmAddress = await parseAddress("OETH_ARM"); + const oethARM = await ethers.getContractAt("OethARM", oethArmAddress); + const vaultAddress = await parseAddress("OETH_VAULT"); + const vault = await ethers.getContractAt("IOETHVault", vaultAddress); + + await autoClaimWithdraw({ + ...taskArgs, + signer, + weth, + oethARM, + vault, + }); +}); +task("autoClaimWithdraw").setAction(async (_, __, runSuper) => { + return runSuper(); +}); + subtask( "requestWithdraw", "Request a specific amount of OETH to withdraw from the OETH Vault" @@ -118,8 +143,10 @@ subtask("withdrawStatus", "Get the status of a OETH withdrawal request") const oethArmAddress = await parseAddress("OETH_ARM"); const oethARM = await ethers.getContractAt("OethARM", oethArmAddress); + const vaultAddress = await parseAddress("OETH_VAULT"); + const vault = await ethers.getContractAt("IOETHVault", vaultAddress); - await withdrawRequestStatus({ ...taskArgs, signer, oethARM }); + await withdrawRequestStatus({ ...taskArgs, signer, oethARM, vault }); }); task("withdrawStatus").setAction(async (_, __, runSuper) => { return runSuper(); @@ -409,3 +436,14 @@ subtask("upgradeProxy", "Upgrade a proxy contract to a new implementation") task("upgradeProxy").setAction(async (_, __, runSuper) => { return runSuper(); }); + +// Defender +subtask( + "setActionVars", + "Set environment variables on a Defender Actions. eg DEBUG=origin*" +) + .addParam("id", "Identifier of the Defender Actions", undefined, types.string) + .setAction(setActionVars); +task("setActionVars").setAction(async (_, __, runSuper) => { + return runSuper(); +}); diff --git a/src/js/tasks/tokens.js b/src/js/tasks/tokens.js index acdfe93..373260f 100644 --- a/src/js/tasks/tokens.js +++ b/src/js/tasks/tokens.js @@ -1,4 +1,4 @@ -const { parseUnits, formatUnits } = require("ethers"); +const { parseUnits, formatUnits } = require("ethers").utils; const { resolveAsset } = require("../utils/assets"); const { getSigner } = require("../utils/signers"); diff --git a/src/js/tasks/vault.js b/src/js/tasks/vault.js index ac50461..1f4103d 100644 --- a/src/js/tasks/vault.js +++ b/src/js/tasks/vault.js @@ -1,4 +1,4 @@ -const { parseUnits } = require("ethers"); +const { parseUnits } = require("ethers").utils; const { resolveAsset } = require("../utils/assets"); const { getSigner } = require("../utils/signers"); @@ -73,14 +73,14 @@ async function mint({ amount, asset, min, approve }) { if (approve) { const approveTx = await cAsset .connect(signer) - .approve(vault.getAddress(), assetUnits); + .approve(vault.address, assetUnits); await logTxDetails(approveTx, "approve"); } log(`About to mint OETH from ${amount} ${asset}`); const tx = await vault .connect(signer) - .mint(cAsset.getAddress(), assetUnits, minUnits); + .mint(cAsset.address, assetUnits, minUnits); await logTxDetails(tx, "mint"); } diff --git a/src/js/tasks/weth.js b/src/js/tasks/weth.js index ca94c65..b9f3539 100644 --- a/src/js/tasks/weth.js +++ b/src/js/tasks/weth.js @@ -1,4 +1,4 @@ -const { parseUnits } = require("ethers"); +const { parseUnits } = require("ethers").utils; const { logTxDetails } = require("../utils/txLogger"); diff --git a/src/js/utils/queue.js b/src/js/utils/queue.js new file mode 100644 index 0000000..81697b2 --- /dev/null +++ b/src/js/utils/queue.js @@ -0,0 +1,126 @@ +const { ApolloClient, InMemoryCache, gql } = require("@apollo/client/core"); +const dayjs = require("dayjs"); +const utc = require("dayjs/plugin/utc"); +const { BigNumber } = require("ethers"); +const { formatUnits } = require("ethers").utils; + +// Extend Day.js with the UTC plugin +dayjs.extend(utc); + +const log = require("../utils/logger")("task:queue"); + +const uri = "https://origin.squids.live/origin-squid/graphql"; + +const outstandingWithdrawalAmount = async ({ withdrawer }) => { + const client = new ApolloClient({ + uri, + cache: new InMemoryCache(), + }); + + log(`About to get outstanding withdrawal requests for ${withdrawer}`); + + const query = gql` + query OutstandingRequestsQuery($withdrawer: String!) { + oethWithdrawalRequests( + where: { withdrawer_eq: $withdrawer, claimed_eq: false } + ) { + id + amount + queued + claimed + requestId + timestamp + txHash + } + } + `; + + try { + const { data } = await client.query({ + query, + variables: { withdrawer: withdrawer.toLowerCase() }, + }); + + log( + `Found ${data.oethWithdrawalRequests.length} outstanding withdrawal requests` + ); + + const amount = data.oethWithdrawalRequests.reduce( + (acc, request) => acc.add(request.amount), + BigNumber.from(0) + ); + + return amount; + } catch (error) { + const msg = `Failed to get outstanding OETH withdrawals for ${withdrawer}`; + console.error(msg); + throw Error(msg, { cause: error }); + } +}; + +const claimableRequests = async ({ withdrawer, queuedAmountClaimable }) => { + const client = new ApolloClient({ + uri, + cache: new InMemoryCache(), + }); + + log( + `About to get claimable withdrawal requests for ${withdrawer} up to ${formatUnits( + queuedAmountClaimable + )} WETH` + ); + + const query = gql` + query ClaimableRequestsQuery( + $withdrawer: String! + $liquidity: BigInt! + $tenMinutesAgo: DateTime! + ) { + oethWithdrawalRequests( + where: { + withdrawer_eq: $withdrawer + claimed_eq: false + queued_lte: $liquidity + timestamp_lt: $tenMinutesAgo + } + ) { + id + amount + queued + claimed + requestId + timestamp + txHash + } + } + `; + + try { + // Get the Date time of 10 minutes ago + const now = dayjs(); + const tenMinutesAgo = now.subtract(10, "minute"); + + log(`Ten minutes ago: ${tenMinutesAgo}`); + + const { data } = await client.query({ + query, + variables: { + withdrawer: withdrawer.toLowerCase(), + liquidity: queuedAmountClaimable.toString(), + tenMinutesAgo, + }, + }); + + log( + `Found ${data.oethWithdrawalRequests.length} claimable withdrawal requests` + ); + + return data.oethWithdrawalRequests.map((request) => request.requestId); + } catch (error) { + const msg = `Failed to get claimable OETH withdrawals for ${withdrawer}`; + console.error(msg); + throw Error(msg, { cause: error }); + } +}; + +module.exports = { claimableRequests, outstandingWithdrawalAmount }; diff --git a/src/js/utils/txLogger.js b/src/js/utils/txLogger.js index 54aa2ac..d15e265 100644 --- a/src/js/utils/txLogger.js +++ b/src/js/utils/txLogger.js @@ -1,4 +1,4 @@ -const { formatUnits } = require("ethers"); +const { formatUnits } = require("ethers").utils; const log = require("./logger")("utils:txLogger");