Skip to content
Open
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
2 changes: 1 addition & 1 deletion config/networks/sonic_mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ export async function getConfig(_hre: HardhatRuntimeEnvironment): Promise<Config
[wOSAddress]: {
feedAsset: wOSAddress,
feed1: "0x19E84B1f41d1Eb2ff22baC55797bD767558585De", // Our own ChainlinkDecimalConverter which wraps the wOS/OS Chainlink feed and converts 18 -> 8 decimals
feed2: "0xF6819756b86678dEd7A0aECD983697c4F7D42bbc", // Our own ChainlinkCompositeAggregator which composes OS/S and S/USD
feed2: "0xc76dFb89fF298145b417d221B2c747d84952e01d", // S/USD Chainlink feed (OS is assumed to be 1:1 with S)
lowerThresholdInBase1: 0n, // No thresholding
fixedPriceInBase1: 0n,
lowerThresholdInBase2: 0n, // No thresholding
Expand Down
168 changes: 168 additions & 0 deletions deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";

import { getConfig } from "../../config/config";
import { USD_REDSTONE_COMPOSITE_WRAPPER_WITH_THRESHOLDING_ID, WOS_TO_OS_DECIMAL_CONVERTER_ID } from "../../typescript/deploy-ids";
import { isMainnet } from "../../typescript/hardhat/deploy";
import { GovernanceExecutor } from "../../typescript/hardhat/governance";
import { SafeTransactionData } from "../../typescript/safe/types";

/**

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "secondaryThreshold.fixedPriceInBase" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "secondaryThreshold.lowerThresholdInBase" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "secondaryThreshold" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "primaryThreshold.fixedPriceInBase" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "primaryThreshold.lowerThresholdInBase" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "primaryThreshold" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "feed2" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "feed1" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "asset" declaration

Check warning on line 10 in deploy/20_wos_oracle_hotfix/01_queue_remove_os_s_dependency.ts

View workflow job for this annotation

GitHub Actions / Lint & Sanity Checks

Missing JSDoc @param "wrapperAddress" declaration
* Build Safe payload for addCompositeFeed(asset, feed1, feed2, thresholds...).
*/
function createAddCompositeFeedTx(
wrapperAddress: string,
asset: string,
feed1: string,
feed2: string,
primaryThreshold: { lowerThresholdInBase: bigint; fixedPriceInBase: bigint },
secondaryThreshold: { lowerThresholdInBase: bigint; fixedPriceInBase: bigint },
wrapperInterface: any,
): SafeTransactionData {
return {
to: wrapperAddress,
value: "0",
data: wrapperInterface.encodeFunctionData("addCompositeFeed", [
asset,
feed1,
feed2,
primaryThreshold.lowerThresholdInBase,
primaryThreshold.fixedPriceInBase,
secondaryThreshold.lowerThresholdInBase,
secondaryThreshold.fixedPriceInBase,
]),
};
}

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment): Promise<boolean> {
if (!isMainnet(hre.network.name)) {
console.log("ℹ️ wOS oracle hotfix is mainnet-only. Skipping on this network.");
return true;
}

const { deployments, ethers } = hre;
const { deployer } = await hre.getNamedAccounts();
const deployerSigner = await ethers.getSigner(deployer);
const config = await getConfig(hre);

const executor = new GovernanceExecutor(hre, deployerSigner, config.safeConfig);
await executor.initialize();

const governanceMultisig = config.walletAddresses.governanceMultisig;
console.log(`🔐 Governance multisig: ${governanceMultisig}`);

const wrapperDeployment = await deployments.get(USD_REDSTONE_COMPOSITE_WRAPPER_WITH_THRESHOLDING_ID);
const wrapper = await ethers.getContractAt(
"RedstoneChainlinkCompositeWrapperWithThresholding",
wrapperDeployment.address,
deployerSigner,
);

const wOSAddress = config.tokenAddresses.wOS;
const wSAddress = config.tokenAddresses.wS;
const usdConfig = config.oracleAggregators.USD;
const sUsdFeed = usdConfig.redstoneOracleAssets?.plainRedstoneOracleWrappers?.[wSAddress];

if (!wOSAddress) {
throw new Error("wOS address missing from config");
}

if (!sUsdFeed) {
throw new Error("S/USD feed missing from USD plainRedstoneOracleWrappers config");
}

const wOSToOSConverterDeployment = await deployments.get(WOS_TO_OS_DECIMAL_CONVERTER_ID);
const wOSToOSConverter = wOSToOSConverterDeployment.address;

const currentFeed = await wrapper.compositeFeeds(wOSAddress);
const currentFeed1 = currentFeed.feed1 as string;
const currentFeed2 = currentFeed.feed2 as string;

if (currentFeed1 === ethers.ZeroAddress || currentFeed2 === ethers.ZeroAddress) {
throw new Error("wOS composite feed is not configured on USD composite wrapper");
}

if (currentFeed1.toLowerCase() !== wOSToOSConverter.toLowerCase()) {
throw new Error(`Unexpected wOS primary feed. expected=${wOSToOSConverter}, actual=${currentFeed1}`);
}

if (currentFeed2.toLowerCase() === sUsdFeed.toLowerCase()) {
console.log("✅ wOS composite feed already uses S/USD directly. Nothing to queue.");
return true;
}

const primaryThreshold = {
lowerThresholdInBase: BigInt(currentFeed.primaryThreshold.lowerThresholdInBase),
fixedPriceInBase: BigInt(currentFeed.primaryThreshold.fixedPriceInBase),
};
const secondaryThreshold = {
lowerThresholdInBase: BigInt(currentFeed.secondaryThreshold.lowerThresholdInBase),
fixedPriceInBase: BigInt(currentFeed.secondaryThreshold.fixedPriceInBase),
};

console.log("🔧 Rewiring wOS/USD composite feed to remove OS/S dependency...");
console.log(` asset: ${wOSAddress}`);
console.log(` feed1 (wOS/OS): ${wOSToOSConverter}`);
console.log(` old feed2: ${currentFeed2}`);
console.log(` new feed2 (S/USD): ${sUsdFeed}`);

const complete = await executor.tryOrQueue(
async () => {
await wrapper.addCompositeFeed(
wOSAddress,
wOSToOSConverter,
sUsdFeed,
primaryThreshold.lowerThresholdInBase,
primaryThreshold.fixedPriceInBase,
secondaryThreshold.lowerThresholdInBase,
secondaryThreshold.fixedPriceInBase,
);
console.log(` ✅ Added rewired wOS composite feed`);
},
() =>
createAddCompositeFeedTx(
wrapperDeployment.address,
wOSAddress,
wOSToOSConverter,
sUsdFeed,
primaryThreshold,
secondaryThreshold,
wrapper.interface,
),
);

if (!complete) {
const flushed = await executor.flush("Overwrite wOS/USD composite feed to remove OS/S dependency");

if (executor.useSafe) {
if (!flushed) {
throw new Error("Failed to prepare Safe batch for wOS oracle hotfix");
}

console.log("⏳ Governance signatures required to execute queued Safe transactions.");
return false;
}
}

const finalFeed = await wrapper.compositeFeeds(wOSAddress);

if (String(finalFeed.feed2).toLowerCase() !== sUsdFeed.toLowerCase()) {
throw new Error(`Post-update verification failed. expected feed2=${sUsdFeed}, actual=${finalFeed.feed2}`);
}

const [priceInfo, isAlive] = await wrapper.getPriceInfo(wOSAddress);
const rewiredPrice = BigInt(priceInfo[0]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse getPriceInfo result without indexing bigint

wrapper.getPriceInfo(wOSAddress) returns (price, isAlive), so after const [priceInfo, isAlive] = ... the priceInfo variable is already the price value; indexing it with priceInfo[0] produces undefined and BigInt(undefined) throws. In runs where the script executes changes directly (for example, deployer has ORACLE_MANAGER_ROLE on mainnet), this causes the post-update verification path to fail even when the feed rewrite succeeded.

Useful? React with 👍 / 👎.


if (!isAlive || rewiredPrice <= 0n) {
throw new Error("Rewired wOS composite feed is not alive or returned non-positive price");
}

console.log(`✅ wOS oracle hotfix complete. price=${rewiredPrice.toString()}, isAlive=${isAlive}`);
return true;
};

func.id = "queue-remove-os-s-dependency-from-wos-oracle";
func.tags = ["usd-oracle", "oracle-wrapper", "wos-oracle-hotfix"];
func.dependencies = [USD_REDSTONE_COMPOSITE_WRAPPER_WITH_THRESHOLDING_ID, WOS_TO_OS_DECIMAL_CONVERTER_ID];

export default func;