Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions contracts/docs/ACTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ Cron times are UTC. Enable state and operational caveats (e.g. "do not enable",

## OToken operations

| Action | Network | Cron | Description |
| ----------------------------------- | ------- | ---------------- | ----------------------------------------------------------------------- |
| `otokenOsCollectAndRelease` | sonic | `55 23 * * *` | Rebase the OS vault and harvest on Sonic |
| `otokenOusdAutoWithdrawal` | mainnet | `35 11,23 * * *` | Auto-process OUSD withdrawals via the AutoWithdrawalModule |
| `otokenAddWithdrawalQueueLiquidity` | mainnet | `20 0 * * *` | Call `addWithdrawalQueueLiquidity` on every OToken vault on the network |
| `otokenAddWithdrawalQueueLiquidity` | base | `30 0 * * *` | As above, on Base |
| `otokenAddWithdrawalQueueLiquidity` | sonic | `35 0 * * *` | As above, on Sonic |
| `otokenAddWithdrawalQueueLiquidity` | plume | `25 0 * * *` | As above, on Plume |
| `otokenOethbUpdateWoethPrice` | base | `30 21 * * *` | Update the wOETH oracle price on the Base BridgedWOETHStrategy |
| `otokenOethbHarvest` | base | `55 11 * * *` | Harvest strategies on Base OETHb |
| `otokenOsSonicRestakeRewards` | sonic | `52 22 * * *` | Restake rewards for Sonic validators |
| Action | Network | Cron | Description |
| ----------------------------------- | ------- | ---------------- | --------------------------------------------------------------------------------------------------- |
| `otokenOsCollectAndRelease` | sonic | `55 23 * * *` | Rebase the OS vault and harvest on Sonic |
| `otokenOusdAutoWithdrawal` | mainnet | `35 11,23 * * *` | Auto-process OUSD withdrawals via the AutoWithdrawalModule |
| `otokenAddWithdrawalQueueLiquidity` | mainnet | `*/10 * * * *` | Call `addWithdrawalQueueLiquidity` on each OToken vault, only when it would add claimable liquidity |
| `otokenAddWithdrawalQueueLiquidity` | base | `*/10 * * * *` | As above, on Base |
| `otokenAddWithdrawalQueueLiquidity` | sonic | `*/10 * * * *` | As above, on Sonic |
| `otokenAddWithdrawalQueueLiquidity` | plume | `*/10 * * * *` | As above, on Plume |
| `otokenOethbUpdateWoethPrice` | base | `30 21 * * *` | Update the wOETH oracle price on the Base BridgedWOETHStrategy |
| `otokenOethbHarvest` | base | `55 11 * * *` | Harvest strategies on Base OETHb |
| `otokenOsSonicRestakeRewards` | sonic | `52 22 * * *` | Restake rewards for Sonic validators |

## Native staking (Ethereum validators)

Expand Down
23 changes: 19 additions & 4 deletions contracts/migrations/seed_schedules.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ DELETE FROM schedules
WHERE product = 'origin-dollar'
AND name = 'otoken_oethp_addWithdrawalQueueLiquidity';

-- Move the addWithdrawalQueueLiquidity rows from daily to every 10 minutes
-- (the action now skips the tx when there is nothing to add). The INSERT below
-- is ON CONFLICT DO NOTHING, so existing rows need this UPDATE. Guarded on the
-- old daily crons so it never overrides a cron later changed in the UI.
UPDATE schedules
SET cron_expr = '*/10 * * * *'
WHERE product = 'origin-dollar'
AND name IN (
'otoken_addWithdrawalQueueLiquidity_mainnet',
'otoken_addWithdrawalQueueLiquidity_base',
'otoken_addWithdrawalQueueLiquidity_sonic',
'otoken_addWithdrawalQueueLiquidity_plume'
)
AND cron_expr IN ('20 0 * * *', '30 0 * * *', '35 0 * * *', '25 0 * * *');

INSERT INTO schedules (product, name, command, cron_expr, timezone, enabled, note) VALUES
('origin-dollar', 'manage_merkle_morpho_bribe', 'cd /app && pnpm exec tsx tasks/run.ts manageMerklBribes --network mainnet', '30 13 * * 3', 'UTC', false, 'permissioned'),
('origin-dollar', 'manage_curve_pb_mainnet', 'cd /app && pnpm exec tsx tasks/run.ts manageBribes --network mainnet', '30 09 * * 5', 'UTC', false, 'permissioned'),
Expand All @@ -32,10 +47,10 @@ INSERT INTO schedules (product, name, command, cron_expr, timezone, enabled, not
('origin-dollar', 'otoken_os_collectAndRelease', 'cd /app && pnpm exec tsx tasks/run.ts otokenOsCollectAndRelease --network sonic', '55 23 * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_ousd_autoWithdrawal', 'cd /app && pnpm exec tsx tasks/run.ts otokenOusdAutoWithdrawal --network mainnet', '35 11,23 * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_oethb_updateWoethPrice', 'cd /app && pnpm exec tsx tasks/run.ts otokenOethbUpdateWoethPrice --network base', '30 21 * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_addWithdrawalQueueLiquidity_mainnet', 'cd /app && pnpm exec tsx tasks/run.ts otokenAddWithdrawalQueueLiquidity --network mainnet', '20 0 * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_addWithdrawalQueueLiquidity_base', 'cd /app && pnpm exec tsx tasks/run.ts otokenAddWithdrawalQueueLiquidity --network base', '30 0 * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_addWithdrawalQueueLiquidity_sonic', 'cd /app && pnpm exec tsx tasks/run.ts otokenAddWithdrawalQueueLiquidity --network sonic', '35 0 * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_addWithdrawalQueueLiquidity_plume', 'cd /app && pnpm exec tsx tasks/run.ts otokenAddWithdrawalQueueLiquidity --network plume', '25 0 * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_addWithdrawalQueueLiquidity_mainnet', 'cd /app && pnpm exec tsx tasks/run.ts otokenAddWithdrawalQueueLiquidity --network mainnet', '*/10 * * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_addWithdrawalQueueLiquidity_base', 'cd /app && pnpm exec tsx tasks/run.ts otokenAddWithdrawalQueueLiquidity --network base', '*/10 * * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_addWithdrawalQueueLiquidity_sonic', 'cd /app && pnpm exec tsx tasks/run.ts otokenAddWithdrawalQueueLiquidity --network sonic', '*/10 * * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_addWithdrawalQueueLiquidity_plume', 'cd /app && pnpm exec tsx tasks/run.ts otokenAddWithdrawalQueueLiquidity --network plume', '*/10 * * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_oethb_rebase', 'cd /app && pnpm exec tsx tasks/run.ts otokenOethbRebase --network base', '25 9,21 * * *', 'UTC', false, NULL),
('origin-dollar', 'otoken_os_sonicRestakeRewards', 'cd /app && pnpm exec tsx tasks/run.ts otokenOsSonicRestakeRewards --network sonic', '52 22 * * *', 'UTC', false, NULL),
('origin-dollar', 'cross_chain_balance_update_base', 'cd /app && pnpm exec tsx tasks/run.ts crossChainBalanceUpdateBase --network base', '40 7,15,23 * * *', 'UTC', false, 'permissioned'),
Expand Down
59 changes: 57 additions & 2 deletions contracts/tasks/actions/otokenAddWithdrawalQueueLiquidity.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { BigNumber, ethers } from "ethers";
import { formatUnits } from "ethers/lib/utils";
import { action } from "../lib/action";
import { getContract, getContractAt } from "../lib/contracts";
import { logTxDetails } from "../../utils/txLogger";
Expand All @@ -13,10 +15,47 @@ const VAULT_DEPLOYMENTS_BY_CHAIN_ID: Record<number, string[]> = {
98866: ["OETHPlumeVaultProxy"], // plume: Super OETH
};

// Mirrors VaultCore._addWithdrawalQueueLiquidity — keep in sync. Computed
// off-chain so the 10-minute schedule only spends gas when the call would
// actually increase the queue's claimable amount.
export function computeClaimableToAdd({
queued,
claimable,
claimed,
balance,
}: {
queued: BigNumber;
claimable: BigNumber;
claimed: BigNumber;
balance: BigNumber;
}) {
const shortfall = queued.sub(claimable);
const allocated = claimable.sub(claimed);
let added = BigNumber.from(0);
if (shortfall.gt(0) && balance.gt(allocated)) {
const unallocated = balance.sub(allocated);
added = shortfall.lt(unallocated) ? shortfall : unallocated;
}
return { added, shortfall, allocated };
}

async function claimableToAdd(vault: ethers.Contract) {
const { queued, claimable, claimed } = await vault.withdrawalQueueMetadata();
const asset = await getContractAt("IERC20", await vault.asset());
const balance: BigNumber = await asset.balanceOf(vault.address);
const decimals: number = await asset.decimals();

return {
...computeClaimableToAdd({ queued, claimable, claimed, balance }),
balance,
decimals,
};
}

action({
name: "otokenAddWithdrawalQueueLiquidity",
description:
"Call addWithdrawalQueueLiquidity on every OToken vault on the current network",
"Call addWithdrawalQueueLiquidity on every OToken vault on the current network when it would add claimable liquidity",
chains: Object.keys(VAULT_DEPLOYMENTS_BY_CHAIN_ID).map(Number),
run: async ({ signer, chainId, networkName, log }) => {
const deploymentNames = VAULT_DEPLOYMENTS_BY_CHAIN_ID[chainId];
Expand All @@ -25,8 +64,24 @@ action({
const vaultProxy = await getContract(deploymentName);
const vault = await getContractAt("IVault", vaultProxy.address);

const { added, shortfall, balance, allocated, decimals } =
await claimableToAdd(vault);
const fmt = (v: BigNumber) => formatUnits(v, decimals);

if (added.isZero()) {
log.info(
`Skipping ${deploymentName} at ${vault.address} (${networkName}): nothing to add ` +
`(shortfall ${fmt(shortfall)}, balance ${fmt(
balance
)}, allocated ${fmt(allocated)})`
);
continue;
}

log.info(
`Calling addWithdrawalQueueLiquidity on ${deploymentName} at ${vault.address} (${networkName})`
`Calling addWithdrawalQueueLiquidity on ${deploymentName} at ${
vault.address
} (${networkName}) to add ${fmt(added)} claimable`
);
const tx = await vault
.connect(signer)
Expand Down
75 changes: 75 additions & 0 deletions contracts/tasks/test/add-withdrawal-queue-liquidity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const { expect } = require("chai");
const { BigNumber } = require("ethers");

const {
computeClaimableToAdd,
} = require("../actions/otokenAddWithdrawalQueueLiquidity");

// The action runs every 10 minutes and only sends addWithdrawalQueueLiquidity
// when this off-chain mirror of VaultCore._addWithdrawalQueueLiquidity returns
// a non-zero amount. A wrong zero silently leaves claims unfunded, so pin the
// edge cases that decide skip vs. send.
describe("Unit: otokenAddWithdrawalQueueLiquidity computeClaimableToAdd", () => {
const compute = ({ queued, claimable, claimed, balance }) =>
computeClaimableToAdd({
queued: BigNumber.from(queued),
claimable: BigNumber.from(claimable),
claimed: BigNumber.from(claimed),
balance: BigNumber.from(balance),
});

it("adds nothing when the queue is fully funded", () => {
const { added, shortfall } = compute({
queued: 100,
claimable: 100,
claimed: 40,
balance: 1000,
});
expect(shortfall.toNumber()).to.equal(0);
expect(added.toNumber()).to.equal(0);
});

it("adds nothing when the balance only covers already-allocated funds", () => {
// allocated = claimable - claimed = 60
const { added, allocated } = compute({
queued: 200,
claimable: 100,
claimed: 40,
balance: 50,
});
expect(allocated.toNumber()).to.equal(60);
expect(added.toNumber()).to.equal(0);
});

it("adds nothing when the balance exactly equals the allocated funds", () => {
const { added } = compute({
queued: 200,
claimable: 100,
claimed: 40,
balance: 60,
});
expect(added.toNumber()).to.equal(0);
});

it("adds the full shortfall when unallocated funds exceed it", () => {
// shortfall = 100, unallocated = 500 - 60 = 440
const { added } = compute({
queued: 200,
claimable: 100,
claimed: 40,
balance: 500,
});
expect(added.toNumber()).to.equal(100);
});

it("adds only the unallocated funds when they are below the shortfall", () => {
// shortfall = 100, unallocated = 90 - 60 = 30
const { added } = compute({
queued: 200,
claimable: 100,
claimed: 40,
balance: 90,
});
expect(added.toNumber()).to.equal(30);
});
});
Loading