From e1d8dc6ccdec65e7d0f354482a31f146c3cd5a83 Mon Sep 17 00:00:00 2001 From: "abner.huang" Date: Tue, 24 Feb 2026 20:52:40 +0800 Subject: [PATCH 1/5] feat(SatoshiPeriphery): add swapInWithOkx function for ERC20 to DebtToken swaps --- src/core/helpers/SatoshiPeriphery.sol | 51 +++++++++++++++++++ .../helpers/interfaces/ISatoshiPeriphery.sol | 17 +++++++ 2 files changed, 68 insertions(+) diff --git a/src/core/helpers/SatoshiPeriphery.sol b/src/core/helpers/SatoshiPeriphery.sol index aaf51ca..0e7fc7e 100644 --- a/src/core/helpers/SatoshiPeriphery.sol +++ b/src/core/helpers/SatoshiPeriphery.sol @@ -12,6 +12,7 @@ import { ILiquidationFacet } from "../interfaces/ILiquidationFacet.sol"; import { ITroveManager } from "../interfaces/ITroveManager.sol"; import { IDebtToken } from "../interfaces/IDebtToken.sol"; +import { INexusYieldManagerFacet } from "../interfaces/INexusYieldManagerFacet.sol"; import { ISatoshiPeriphery, LzSendParam } from "./interfaces/ISatoshiPeriphery.sol"; import { IWETH } from "./interfaces/IWETH.sol"; import { @@ -347,4 +348,54 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea function _authorizeUpgrade(address newImplementation) internal view override onlyOwner { // No additional authorization logic is needed for this contract } + + /// @notice Swap any ERC20 → stable token via OKX DEX → DebtToken via NYM.swapIn + /// @dev ERC20 only — native token input is not supported + /// @dev fromToken must be pre-approved to this contract by msg.sender + /// @dev okxCalldata must be generated with this contract address as userWalletAddress + /// @dev DebtToken is always delivered to msg.sender on the same chain + function swapInWithOkx( + address fromToken, + uint256 fromAmount, + address okxRouter, + bytes calldata okxCalldata, + address stableAsset, + uint256 minDebtAmount + ) public { + IERC20(fromToken).safeTransferFrom(msg.sender, address(this), fromAmount); + + uint256 fromTokenBefore = IERC20(fromToken).balanceOf(address(this)); + + IERC20(fromToken).approve(okxRouter, fromAmount); + + uint256 stableBalanceBefore = IERC20(stableAsset).balanceOf(address(this)); + + (bool success,) = okxRouter.call(okxCalldata); + require(success, "SatoshiPeriphery: OKX swap failed"); + + IERC20(fromToken).approve(okxRouter, 0); + + uint256 fromTokenAfter = IERC20(fromToken).balanceOf(address(this)); + if (fromTokenAfter > fromTokenBefore - fromAmount) { + uint256 refund = fromTokenAfter - (fromTokenBefore - fromAmount); + IERC20(fromToken).safeTransfer(msg.sender, refund); + } + + uint256 stableReceived = IERC20(stableAsset).balanceOf(address(this)) - stableBalanceBefore; + require(stableReceived > 0, "SatoshiPeriphery: No stable tokens received from OKX"); + + IERC20(stableAsset).approve(xApp, stableReceived); + + uint256 debtTokenBalanceBefore = debtToken.balanceOf(address(this)); + + INexusYieldManagerFacet(xApp).swapIn(stableAsset, address(this), stableReceived); + + uint256 debtTokenReceived = debtToken.balanceOf(address(this)) - debtTokenBalanceBefore; + + if (debtTokenReceived < minDebtAmount) { + revert SlippageTooHigh(debtTokenReceived, minDebtAmount); + } + + debtToken.safeTransfer(msg.sender, debtTokenReceived); + } } diff --git a/src/core/helpers/interfaces/ISatoshiPeriphery.sol b/src/core/helpers/interfaces/ISatoshiPeriphery.sol index 29a152e..b9a5f89 100644 --- a/src/core/helpers/interfaces/ISatoshiPeriphery.sol +++ b/src/core/helpers/interfaces/ISatoshiPeriphery.sol @@ -22,6 +22,7 @@ interface ISatoshiPeriphery { error InvalidZeroAddress(); error RefundFailed(); error InsufficientMsgValue(uint256 msgValue, uint256 requiredValue); + error SlippageTooHigh(uint256 actual, uint256 minimum); function debtToken() external view returns (DebtTokenWithLz); @@ -100,4 +101,20 @@ interface ISatoshiPeriphery { ) external payable; + + /// @notice Swap any ERC20 → stable token via OKX DEX → DebtToken via NYM.swapIn (ERC20 only) + /// @param fromToken Input ERC20 token (must be approved to this contract) + /// @param fromAmount Raw input amount + /// @param okxRouter OKX DEX Router address from backend /okx/nym-swap response + /// @param okxCalldata OKX swap calldata from backend /okx/nym-swap response + /// @param stableAsset Stable token address from backend /okx/nym-swap response + /// @param minDebtAmount Minimum DebtToken to receive; revert if below this (slippage guard) + function swapInWithOkx( + address fromToken, + uint256 fromAmount, + address okxRouter, + bytes calldata okxCalldata, + address stableAsset, + uint256 minDebtAmount + ) external; } From aca0a8d48ecfbb724d8f0ef6c8521919542b799f Mon Sep 17 00:00:00 2001 From: "abner.huang" Date: Wed, 4 Mar 2026 21:00:49 +0800 Subject: [PATCH 2/5] feat(DeploySatoshiPeriphery): add deployment script and update SatoshiPeriphery interface for OKX swap functionality --- script/DeploySatoshiPeriphery.s.sol | 30 ++ src/core/helpers/SatoshiPeriphery.sol | 6 + .../helpers/interfaces/ISatoshiPeriphery.sol | 14 +- test/SwapInWithOkxForkTest.t.sol | 333 ++++++++++++++++++ 4 files changed, 377 insertions(+), 6 deletions(-) create mode 100644 script/DeploySatoshiPeriphery.s.sol create mode 100644 test/SwapInWithOkxForkTest.t.sol diff --git a/script/DeploySatoshiPeriphery.s.sol b/script/DeploySatoshiPeriphery.s.sol new file mode 100644 index 0000000..040660f --- /dev/null +++ b/script/DeploySatoshiPeriphery.s.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Script, console } from "forge-std/Script.sol"; + +import { SatoshiPeriphery } from "../src/core/helpers/SatoshiPeriphery.sol"; +import { ISatoshiPeriphery } from "../src/core/helpers/interfaces/ISatoshiPeriphery.sol"; +import { IDebtToken } from "../src/core/interfaces/IDebtToken.sol"; + +contract DeploySatoshiPeriphery is Script { + function run() external { + uint256 deployerKey = vm.envUint("DEPLOYMENT_PRIVATE_KEY"); + address debtToken = vm.envAddress("DEBT_TOKEN"); + address xApp = vm.envAddress("X_APP"); + address owner = vm.envAddress("OWNER"); + + vm.startBroadcast(deployerKey); + + address peripheryImpl = address(new SatoshiPeriphery()); + bytes memory data = + abi.encodeCall(ISatoshiPeriphery.initialize, (IDebtToken(debtToken), xApp, owner)); + ISatoshiPeriphery periphery = ISatoshiPeriphery(address(new ERC1967Proxy(peripheryImpl, data))); + + console.log("Implementation:", peripheryImpl); + console.log("Proxy:", address(periphery)); + + vm.stopBroadcast(); + } +} diff --git a/src/core/helpers/SatoshiPeriphery.sol b/src/core/helpers/SatoshiPeriphery.sol index 0e7fc7e..f22bae6 100644 --- a/src/core/helpers/SatoshiPeriphery.sol +++ b/src/core/helpers/SatoshiPeriphery.sol @@ -354,9 +354,12 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea /// @dev fromToken must be pre-approved to this contract by msg.sender /// @dev okxCalldata must be generated with this contract address as userWalletAddress /// @dev DebtToken is always delivered to msg.sender on the same chain + /// @dev okxApproveAddress is the tokenApproveAddress returned by the OKX /approve-transaction API + /// @dev okxRouter is the routerContractAddress returned by the OKX /swap API — these are different contracts function swapInWithOkx( address fromToken, uint256 fromAmount, + address okxApproveAddress, address okxRouter, bytes calldata okxCalldata, address stableAsset, @@ -366,6 +369,8 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea uint256 fromTokenBefore = IERC20(fromToken).balanceOf(address(this)); + // Must approve the token approve proxy, NOT the router — OKX uses a separate spender contract + IERC20(fromToken).approve(okxApproveAddress, fromAmount); IERC20(fromToken).approve(okxRouter, fromAmount); uint256 stableBalanceBefore = IERC20(stableAsset).balanceOf(address(this)); @@ -373,6 +378,7 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea (bool success,) = okxRouter.call(okxCalldata); require(success, "SatoshiPeriphery: OKX swap failed"); + IERC20(fromToken).approve(okxApproveAddress, 0); IERC20(fromToken).approve(okxRouter, 0); uint256 fromTokenAfter = IERC20(fromToken).balanceOf(address(this)); diff --git a/src/core/helpers/interfaces/ISatoshiPeriphery.sol b/src/core/helpers/interfaces/ISatoshiPeriphery.sol index b9a5f89..f793d8b 100644 --- a/src/core/helpers/interfaces/ISatoshiPeriphery.sol +++ b/src/core/helpers/interfaces/ISatoshiPeriphery.sol @@ -103,15 +103,17 @@ interface ISatoshiPeriphery { payable; /// @notice Swap any ERC20 → stable token via OKX DEX → DebtToken via NYM.swapIn (ERC20 only) - /// @param fromToken Input ERC20 token (must be approved to this contract) - /// @param fromAmount Raw input amount - /// @param okxRouter OKX DEX Router address from backend /okx/nym-swap response - /// @param okxCalldata OKX swap calldata from backend /okx/nym-swap response - /// @param stableAsset Stable token address from backend /okx/nym-swap response - /// @param minDebtAmount Minimum DebtToken to receive; revert if below this (slippage guard) + /// @param fromToken Input ERC20 token (must be approved to this contract) + /// @param fromAmount Raw input amount + /// @param okxApproveAddress OKX token-approve proxy address (spender for internal approval) + /// @param okxRouter OKX DEX Router address from backend /okx/nym-swap response + /// @param okxCalldata OKX swap calldata from backend /okx/nym-swap response + /// @param stableAsset Stable token address from backend /okx/nym-swap response + /// @param minDebtAmount Minimum DebtToken to receive; revert if below this (slippage guard) function swapInWithOkx( address fromToken, uint256 fromAmount, + address okxApproveAddress, address okxRouter, bytes calldata okxCalldata, address stableAsset, diff --git a/test/SwapInWithOkxForkTest.t.sol b/test/SwapInWithOkxForkTest.t.sol new file mode 100644 index 0000000..03a76dc --- /dev/null +++ b/test/SwapInWithOkxForkTest.t.sol @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @title SwapInWithOkxForkTest + * @dev BSC mainnet fork test for SatoshiPeriphery.swapInWithOkx() + * + * Flow being tested: + * USER approves RIVER → SatoshiPeriphery + * SatoshiPeriphery: RIVER → OKX DEX → USDT → NYM.swapIn → DebtToken → USER + * + * Refresh OKX_CALLDATA when stale (deadline ~1 week): + * curl "http://localhost:8083/okx/nym-swap?chainId=56&fromTokenAddress=0xdA7AD9dea9397cffdDAE2F8a052B82f1484252B3&amount=1000000000000000000&slippagePercent=1" + * Then replace OKX_CALLDATA, OKX_APPROVE_ADDR, OKX_ROUTER below. + * + * Run: + * cd satoshi-v2 + * forge test --match-contract SwapInWithOkxForkTest -vvvv + * forge test --match-contract SwapInWithOkxForkTest --match-test testDiagnostics -vvvv + * forge test --match-contract SwapInWithOkxForkTest --match-test testSwapInWithOkxStepByStep -vvvv + */ + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import { ISatoshiPeriphery } from "../src/core/helpers/interfaces/ISatoshiPeriphery.sol"; +import { INexusYieldManagerFacet } from "../src/core/interfaces/INexusYieldManagerFacet.sol"; + +contract SwapInWithOkxForkTest is Test { + // ── BSC mainnet addresses ───────────────────────────────────────────────── + address constant PERIPHERY = 0x246b28f44ec8CA47e83365f67Bd382D6A4952Ac8; + address constant X_APP = 0x07BbC5A83B83a5C440D1CAedBF1081426d0AA4Ec; + address constant DEBT_TOKEN = 0xb4818BB69478730EF4e33Cc068dD94278e2766cB; + address constant RIVER_TOKEN = 0xdA7AD9dea9397cffdDAE2F8a052B82f1484252B3; + address constant USDT_BSC = 0x55d398326f99059fF775485246999027B3197955; + + // Real wallet that holds RIVER on BSC (contract owner) + address constant USER = 0xf369359Cd4b9dABF61Aa26F19b6aa3CD88Ba39d6; + + // ── OKX addresses from /okx/nym-swap response ───────────────────────────── + // Last fetched: 2026-03-04 + address constant OKX_APPROVE_ADDR = 0x2c34A2Fb1d0b4f55de51E1d0bDEfaDDce6b7cDD6; + address constant OKX_ROUTER = 0x3156020dfF8D99af1dDC523ebDfb1ad2018554a0; + + uint256 constant FROM_AMOUNT = 1e18; // 1 RIVER + + // ── OKX calldata — each hex"" is exactly 32 bytes (64 hex chars) ────────── + // 4-byte selector f2c42696 + // Deadline word: 0x69a83891 = 2026-03-11 UTC (valid ~1 week from generation) + // minReceiveAmt: 0xeaaf379c22cd1766 ≈ 16.91 USDT (1% slippage on ~17.08 USDT) + bytes constant OKX_CALLDATA = abi.encodePacked( + hex"f2c42696", // selector + hex"000000000000000000000000000000000000000000000000000000000003606a", // w1 + hex"000000000000000000000000da7ad9dea9397cffddae2f8a052b82f1484252b3", // w2 fromToken + hex"00000000000000000000000055d398326f99059ff775485246999027b3197955", // w3 toToken + hex"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // w4 amount + hex"000000000000000000000000000000000000000000000000eaaf379c22cd1766", // w5 minReceive + hex"0000000000000000000000000000000000000000000000000000000069a83891", // w6 deadline + hex"00000000000000000000000000000000000000000000000000000000000000e0", // w7 offset + hex"0000000000000000000000000000000000000000000000000000000000000001", // w8 + hex"0000000000000000000000000000000000000000000000000000000000000020", // w9 + hex"00000000000000000000000000000000000000000000000000000000000000a0", // w10 + hex"00000000000000000000000000000000000000000000000000000000000000e0", // w11 + hex"0000000000000000000000000000000000000000000000000000000000000120", // w12 + hex"0000000000000000000000000000000000000000000000000000000000000160", // w13 + hex"000000000000000000000000da7ad9dea9397cffddae2f8a052b82f1484252b3", // w14 + hex"0000000000000000000000000000000000000000000000000000000000000001", // w15 + hex"0000000000000000000000007a7ad9aa93cd0a2d0255326e5fb145cec14997ff", // w16 + hex"0000000000000000000000000000000000000000000000000000000000000001", // w17 + hex"0000000000000000000000007a7ad9aa93cd0a2d0255326e5fb145cec14997ff", // w18 + hex"0000000000000000000000000000000000000000000000000000000000000001", // w19 + hex"800000000000000000012710886928eb467ef5e69b4ebc2c8af4275b21af41bd", // w20 + hex"0000000000000000000000000000000000000000000000000000000000000001", // w21 + hex"0000000000000000000000000000000000000000000000000000000000000020", // w22 + hex"00000000000000000000000000000000000000000000000000000000000000a0", // w23 + hex"0000000000000000000000000000000000000000000000000000000000000000", // w24 + hex"0000000000000000000000000000000000000000000000000000000000000040", // w25 + hex"0000000000000000000000000000000000000000000000000000000000000040", // w26 + hex"000000000000000000000000da7ad9dea9397cffddae2f8a052b82f1484252b3", // w27 + hex"00000000000000000000000055d398326f99059ff775485246999027b3197955", // w28 + hex"777777771111800000000000000000000000000000000000ed0e13f837d6ebad", // w29 + hex"777777771111000000000064fa00a9ed787f3793db668bff3e6e6e7db0f92a1b" // w30 + ); + + ISatoshiPeriphery periphery; + IERC20 river; + IERC20 usdt; + IERC20 debtToken; + + function setUp() public { + vm.createSelectFork("bsc"); + + periphery = ISatoshiPeriphery(PERIPHERY); + river = IERC20(RIVER_TOKEN); + usdt = IERC20(USDT_BSC); + debtToken = IERC20(DEBT_TOKEN); + } + + // ───────────────────────────────────────────────────────────────────────── + // DIAGNOSTIC — run first to inspect on-chain state without triggering swap + // ───────────────────────────────────────────────────────────────────────── + function testDiagnostics() public view { + console.log("=== BSC FORK DIAGNOSTICS ==="); + console.log("block.number :", block.number); + console.log("block.timestamp:", block.timestamp); + + // ── Periphery fields ───────────────────────────────────────────────── + address deployedXApp = periphery.xApp(); + address deployedDebtToken = address(periphery.debtToken()); + + console.log("\n--- SatoshiPeriphery ---"); + console.log("periphery.xApp() :", deployedXApp); + console.log("periphery.debtToken() :", deployedDebtToken); + console.log("expected xApp :", X_APP); + console.log("expected debtToken :", DEBT_TOKEN); + console.log("xApp match :", deployedXApp == X_APP); + console.log("debtToken match :", deployedDebtToken == DEBT_TOKEN); + + // ── swapInWithOkx exists on deployed bytecode? ──────────────────────── + // Zero-amount call will revert inside the function (not as "unknown selector") + bytes4 sel = ISatoshiPeriphery.swapInWithOkx.selector; + console.log("\n--- swapInWithOkx ---"); + console.logBytes4(sel); + (bool selExists,) = PERIPHERY.staticcall( + abi.encodeWithSelector(sel, RIVER_TOKEN, 0, address(0), address(0), hex"", USDT_BSC, 0) + ); + // staticcall will revert (state changes) — but if selector is missing it's a different revert + // We just check it doesn't silently return empty (i.e., function is present) + console.log("selector call returned (expect false/revert):", selExists); + + // ── User balances ───────────────────────────────────────────────────── + console.log("\n--- USER balances ---"); + console.log("RIVER :", river.balanceOf(USER)); + console.log("USDT :", usdt.balanceOf(USER)); + console.log("debtToken:", debtToken.balanceOf(USER)); + + // ── NYM state ──────────────────────────────────────────────────────── + INexusYieldManagerFacet nym = INexusYieldManagerFacet(X_APP); + bool nymPaused = nym.isNymPaused(); + bool usdtSupported = nym.isAssetSupported(USDT_BSC); + + console.log("\n--- NYM (xApp:", X_APP, ") ---"); + console.log("NYM paused :", nymPaused); + console.log("USDT supported :", usdtSupported); + + if (usdtSupported) { + console.log("mintCap :", nym.debtTokenMintCap(USDT_BSC)); + console.log("dailyCap :", nym.dailyDebtTokenMintCap(USDT_BSC)); + console.log("dailyRemain :", nym.debtTokenDailyMintCapRemain(USDT_BSC)); + console.log("totalMinted :", nym.debtTokenMinted(USDT_BSC)); + console.log("feeIn(1e18=1%) :", nym.feeIn(USDT_BSC)); + } + + // ── OKX contract code ───────────────────────────────────────────────── + console.log("\n--- OKX contracts ---"); + console.log("OKX_APPROVE_ADDR codeSize:", OKX_APPROVE_ADDR.code.length); + console.log("OKX_ROUTER codeSize:", OKX_ROUTER.code.length); + + // ── Periphery residual balances ─────────────────────────────────────── + console.log("\n--- Periphery residual balances ---"); + console.log("RIVER :", river.balanceOf(PERIPHERY)); + console.log("USDT :", usdt.balanceOf(PERIPHERY)); + console.log("debtToken:", debtToken.balanceOf(PERIPHERY)); + + // ── Calldata length sanity ──────────────────────────────────────────── + console.log("\nOKX_CALLDATA length (bytes):", OKX_CALLDATA.length); + // expected: 4 + 30*32 = 964 + } + + // ───────────────────────────────────────────────────────────────────────── + // STEP-BY-STEP — isolates exactly which sub-step fails + // ───────────────────────────────────────────────────────────────────────── + function testSwapInWithOkxStepByStep() public { + console.log("=== STEP-BY-STEP SWAP TEST ==="); + console.log("block:", block.number, "| ts:", block.timestamp); + console.log("calldata length:", OKX_CALLDATA.length); + + deal(address(river), USER, FROM_AMOUNT); + + // ── Step 1: USER → Periphery transfer ──────────────────────────────── + console.log("\n[Step 1] transferFrom USER -> Periphery"); + vm.prank(USER); + river.approve(PERIPHERY, FROM_AMOUNT); + + uint256 periRiverBefore = river.balanceOf(PERIPHERY); + vm.prank(PERIPHERY); + river.transferFrom(USER, PERIPHERY, FROM_AMOUNT); + uint256 periRiverAfter = river.balanceOf(PERIPHERY); + console.log("Periphery RIVER delta:", periRiverAfter - periRiverBefore); + require(periRiverAfter - periRiverBefore == FROM_AMOUNT, "Step 1 FAILED"); + console.log("[Step 1] PASS"); + + // ── Step 2: Periphery approves OKX ─────────────────────────────────── + console.log("\n[Step 2] Periphery approves OKX proxy + router"); + vm.startPrank(PERIPHERY); + river.approve(OKX_APPROVE_ADDR, FROM_AMOUNT); + river.approve(OKX_ROUTER, FROM_AMOUNT); + console.log("Allowance -> OKX_APPROVE_ADDR:", river.allowance(PERIPHERY, OKX_APPROVE_ADDR)); + console.log("Allowance -> OKX_ROUTER :", river.allowance(PERIPHERY, OKX_ROUTER)); + vm.stopPrank(); + console.log("[Step 2] PASS"); + + // ── Step 3: Call OKX router (RIVER → USDT) ─────────────────────────── + console.log("\n[Step 3] OKX router call: RIVER -> USDT"); + uint256 periUsdtBefore = usdt.balanceOf(PERIPHERY); + uint256 periRiverPre = river.balanceOf(PERIPHERY); + + vm.prank(PERIPHERY); + (bool okxSuccess, bytes memory okxReturnData) = OKX_ROUTER.call(OKX_CALLDATA); + + uint256 periUsdtAfter = usdt.balanceOf(PERIPHERY); + uint256 periRiverPost = river.balanceOf(PERIPHERY); + + console.log("OKX call success :", okxSuccess); + console.log("RIVER consumed :", periRiverPre - periRiverPost); + console.log("USDT received :", periUsdtAfter - periUsdtBefore); + + if (!okxSuccess) { + console.log("OKX revert bytes:"); + console.logBytes(okxReturnData); + if (okxReturnData.length >= 4) { + bytes4 errSel = bytes4(okxReturnData); + console.log("error selector:"); + console.logBytes4(errSel); + // Try to decode as Error(string) + if (errSel == bytes4(keccak256("Error(string)"))) { + (string memory reason) = abi.decode(okxReturnData[4:], (string)); + console.log("revert reason:", reason); + } + } + revert("Step 3 FAILED: OKX swap reverted"); + } + + uint256 usdtReceived = periUsdtAfter - periUsdtBefore; + require(usdtReceived > 0, "Step 3 FAILED: No USDT received from OKX"); + console.log("[Step 3] PASS - USDT received:", usdtReceived); + + // ── Step 4: NYM.swapIn (USDT → debtToken) ──────────────────────────── + console.log("\n[Step 4] NYM.swapIn: USDT -> debtToken"); + + INexusYieldManagerFacet nym = INexusYieldManagerFacet(X_APP); + console.log("USDT supported in NYM :", nym.isAssetSupported(USDT_BSC)); + console.log("NYM paused :", nym.isNymPaused()); + console.log("daily cap remain :", nym.debtTokenDailyMintCapRemain(USDT_BSC)); + + uint256 periDebtBefore = debtToken.balanceOf(PERIPHERY); + + vm.startPrank(PERIPHERY); + usdt.approve(X_APP, usdtReceived); + uint256 debtMinted = nym.swapIn(USDT_BSC, PERIPHERY, usdtReceived); + vm.stopPrank(); + + uint256 periDebtAfter = debtToken.balanceOf(PERIPHERY); + console.log("debtToken return val :", debtMinted); + console.log("debtToken balance delta:", periDebtAfter - periDebtBefore); + require(periDebtAfter > periDebtBefore, "Step 4 FAILED: No debtToken received from NYM"); + console.log("[Step 4] PASS - debtToken received:", debtMinted); + + // ── Step 5: Transfer debtToken to user ─────────────────────────────── + console.log("\n[Step 5] Transfer debtToken to USER"); + uint256 userDebtBefore = debtToken.balanceOf(USER); + vm.prank(PERIPHERY); + debtToken.transfer(USER, debtMinted); + uint256 userDebtAfter = debtToken.balanceOf(USER); + console.log("USER debtToken received:", userDebtAfter - userDebtBefore); + require(userDebtAfter - userDebtBefore == debtMinted, "Step 5 FAILED"); + console.log("[Step 5] PASS"); + + console.log("\n=== ALL STEPS PASSED ==="); + console.log("Total debtToken to user:", debtMinted); + } + + // ───────────────────────────────────────────────────────────────────────── + // END-TO-END — calls swapInWithOkx() on the deployed contract + // ───────────────────────────────────────────────────────────────────────── + function testSwapInWithOkxE2E() public { + console.log("=== E2E: swapInWithOkx() block:", block.number); + + deal(address(river), USER, FROM_AMOUNT); + + uint256 userDebtBefore = debtToken.balanceOf(USER); + uint256 userRiverBefore = river.balanceOf(USER); + console.log("USER RIVER before :", userRiverBefore); + console.log("USER debtToken before:", userDebtBefore); + + vm.startPrank(USER); + river.approve(PERIPHERY, FROM_AMOUNT); + + periphery.swapInWithOkx( + RIVER_TOKEN, + FROM_AMOUNT, + OKX_APPROVE_ADDR, + OKX_ROUTER, + OKX_CALLDATA, + USDT_BSC, + 0 // minDebtAmount = 0 to skip slippage guard while debugging + ); + vm.stopPrank(); + + uint256 userDebtAfter = debtToken.balanceOf(USER); + uint256 userRiverAfter = river.balanceOf(USER); + + console.log("USER RIVER after :", userRiverAfter); + console.log("USER debtToken after :", userDebtAfter); + console.log("debtToken received :", userDebtAfter - userDebtBefore); + + assertGt(userDebtAfter, userDebtBefore, "E2E: User must receive debtToken"); + assertLe(userRiverAfter, userRiverBefore, "E2E: RIVER must be consumed"); + } + + // ───────────────────────────────────────────────────────────────────────── + // SLIPPAGE GUARD — expects revert when minDebtAmount is unreachable + // ───────────────────────────────────────────────────────────────────────── + function testSwapInWithOkxSlippageRevert() public { + deal(address(river), USER, FROM_AMOUNT); + + vm.startPrank(USER); + river.approve(PERIPHERY, FROM_AMOUNT); + + vm.expectRevert(); // SlippageTooHigh(actual, minimum) + periphery.swapInWithOkx( + RIVER_TOKEN, + FROM_AMOUNT, + OKX_APPROVE_ADDR, + OKX_ROUTER, + OKX_CALLDATA, + USDT_BSC, + type(uint256).max + ); + vm.stopPrank(); + } +} From aa169aff071146ce5fd28772ddb3d523effa5359 Mon Sep 17 00:00:00 2001 From: "abner.huang" Date: Wed, 4 Mar 2026 21:09:51 +0800 Subject: [PATCH 3/5] feat(SwapInWithOkxForkTest): implement live test for fetching fresh calldata and update OKX API endpoint --- test/SwapInWithOkxForkTest.t.sol | 97 +++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/test/SwapInWithOkxForkTest.t.sol b/test/SwapInWithOkxForkTest.t.sol index 03a76dc..33e5052 100644 --- a/test/SwapInWithOkxForkTest.t.sol +++ b/test/SwapInWithOkxForkTest.t.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.20; * SatoshiPeriphery: RIVER → OKX DEX → USDT → NYM.swapIn → DebtToken → USER * * Refresh OKX_CALLDATA when stale (deadline ~1 week): - * curl "http://localhost:8083/okx/nym-swap?chainId=56&fromTokenAddress=0xdA7AD9dea9397cffdDAE2F8a052B82f1484252B3&amount=1000000000000000000&slippagePercent=1" + * curl "http://api-airdrop.river.inc/okx/nym-swap?chainId=56&fromTokenAddress=0xdA7AD9dea9397cffdDAE2F8a052B82f1484252B3&amount=1000000000000000000&slippagePercent=1" * Then replace OKX_CALLDATA, OKX_APPROVE_ADDR, OKX_ROUTER below. * * Run: @@ -21,7 +21,7 @@ pragma solidity ^0.8.20; */ import { Test } from "forge-std/Test.sol"; -import { console } from "forge-std/console.sol"; +import { console2 as console } from "forge-std/console2.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISatoshiPeriphery } from "../src/core/helpers/interfaces/ISatoshiPeriphery.sol"; @@ -83,6 +83,48 @@ contract SwapInWithOkxForkTest is Test { hex"777777771111000000000064fa00a9ed787f3793db668bff3e6e6e7db0f92a1b" // w30 ); + // ── Live-refresh helper ─────────────────────────────────────────────────── + + struct NymSwapQuote { + address okxApproveAddress; + address okxRouter; + bytes okxCalldata; + address stableAsset; + address peripheryAddress; + } + + /// @dev Calls the local backend and parses the /okx/nym-swap response. + /// Requires the backend to be running at api-airdrop.river.inc. + /// Requires ffi = true in foundry.toml (already set). + function _fetchNymSwapQuote( + address fromToken, + uint256 amount, + uint256 slippagePercent + ) internal returns (NymSwapQuote memory q) { + string memory url = string.concat( + "http://api-airdrop.river.inc/okx/nym-swap?chainId=56&fromTokenAddress=", + vm.toString(fromToken), + "&amount=", + vm.toString(amount), + "&slippagePercent=", + vm.toString(slippagePercent) + ); + + string[] memory cmd = new string[](3); + cmd[0] = "curl"; + cmd[1] = "-s"; + cmd[2] = url; + + bytes memory raw = vm.ffi(cmd); + string memory json = string(raw); + + q.okxApproveAddress = vm.parseJsonAddress(json, ".okxApproveAddress"); + q.okxRouter = vm.parseJsonAddress(json, ".okxRouter"); + q.okxCalldata = vm.parseJsonBytes(json, ".okxCalldata"); + q.stableAsset = vm.parseJsonAddress(json, ".stableAsset"); + q.peripheryAddress = vm.parseJsonAddress(json, ".peripheryAddress"); + } + ISatoshiPeriphery periphery; IERC20 river; IERC20 usdt; @@ -225,7 +267,9 @@ contract SwapInWithOkxForkTest is Test { console.logBytes4(errSel); // Try to decode as Error(string) if (errSel == bytes4(keccak256("Error(string)"))) { - (string memory reason) = abi.decode(okxReturnData[4:], (string)); + bytes memory stripped = new bytes(okxReturnData.length - 4); + for (uint256 i = 0; i < stripped.length; i++) stripped[i] = okxReturnData[i + 4]; + (string memory reason) = abi.decode(stripped, (string)); console.log("revert reason:", reason); } } @@ -309,6 +353,53 @@ contract SwapInWithOkxForkTest is Test { assertLe(userRiverAfter, userRiverBefore, "E2E: RIVER must be consumed"); } + // ───────────────────────────────────────────────────────────────────────── + // LIVE test — fetches fresh calldata from the local backend at runtime. + // Requires: backend running on api-airdrop.river.inc AND ffi = true (already set). + // Run alone: forge test --match-test testSwapInWithOkxLive -vvvv + // ───────────────────────────────────────────────────────────────────────── + function testSwapInWithOkxLive() public { + console.log("=== LIVE E2E TEST (fresh calldata via /okx/nym-swap) ==="); + + NymSwapQuote memory q = _fetchNymSwapQuote(RIVER_TOKEN, FROM_AMOUNT, 1); + + console.log("okxApproveAddress :", q.okxApproveAddress); + console.log("okxRouter :", q.okxRouter); + console.log("stableAsset :", q.stableAsset); + console.log("peripheryAddress :", q.peripheryAddress); + console.log("okxCalldata length:", q.okxCalldata.length); + + deal(address(river), USER, FROM_AMOUNT); + + uint256 userDebtBefore = debtToken.balanceOf(USER); + uint256 userRiverBefore = river.balanceOf(USER); + console.log("USER RIVER before :", userRiverBefore); + console.log("USER debtToken before:", userDebtBefore); + + vm.startPrank(USER); + river.approve(q.peripheryAddress, FROM_AMOUNT); + + ISatoshiPeriphery(q.peripheryAddress).swapInWithOkx( + RIVER_TOKEN, + FROM_AMOUNT, + q.okxApproveAddress, + q.okxRouter, + q.okxCalldata, + q.stableAsset, + 0 + ); + vm.stopPrank(); + + uint256 userDebtAfter = debtToken.balanceOf(USER); + uint256 userRiverAfter = river.balanceOf(USER); + console.log("USER RIVER after :", userRiverAfter); + console.log("USER debtToken after :", userDebtAfter); + console.log("debtToken received :", userDebtAfter - userDebtBefore); + + assertEq(userRiverAfter, 0, "All RIVER should be spent"); + assertGt(userDebtAfter, userDebtBefore, "User must receive debtToken"); + } + // ───────────────────────────────────────────────────────────────────────── // SLIPPAGE GUARD — expects revert when minDebtAmount is unreachable // ───────────────────────────────────────────────────────────────────────── From 71c0af9442df3481e6021100c8101b75883d736f Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Mon, 16 Mar 2026 16:25:31 +0800 Subject: [PATCH 4/5] fix: swapInWithOkx and remove unused import --- foundry.toml | 16 +- script/DeploySatoshiPeriphery.s.sol | 3 +- script/DeploySetup.s.sol | 4 +- .../cygnus/SetCygnusVaultConfig.s.sol | 3 - script/upgrade/UpgradeNYM.s.sol | 4 +- src/OSHI/OSHIToken.sol | 5 +- src/OSHI/RewardManager.sol | 2 + src/OSHI/interfaces/IRewardManager.sol | 1 - src/core/DebtToken.sol | 5 +- src/core/GasPool.sol | 5 +- src/core/SatoshiXApp.sol | 5 +- src/core/facets/BorrowerOperationsFacet.sol | 5 +- src/core/facets/FactoryFacet.sol | 8 +- src/core/facets/LiquidationFacet.sol | 11 +- src/core/facets/StabilityPoolFacet.sol | 26 +- src/core/helpers/MultiTroveGetter.sol | 8 +- src/core/helpers/SatoshiPeriphery.sol | 88 ++-- src/core/helpers/TroveManagerGetter.sol | 6 +- .../helpers/interfaces/ISatoshiPeriphery.sol | 26 +- src/core/helpers/interfaces/ITroveHelper.sol | 9 +- .../interfaces/ITroveManagerGetter.sol | 5 +- .../interfaces/IBorrowerOperationsFacet.sol | 5 +- src/core/interfaces/IGasPool.sol | 5 +- src/core/interfaces/ILiquidationFacet.sol | 3 + src/core/interfaces/ISatoshiXApp.sol | 5 +- src/core/interfaces/ISortedTroves.sol | 1 + src/core/libs/BorrowerOperationsLib.sol | 8 +- src/core/libs/OFTPermitUpgradeable.sol | 5 +- src/core/libs/StabilityPoolLib.sol | 8 +- .../interfaces/vault/IDelegationManager.sol | 8 +- src/library/interfaces/vault/ISlasher.sol | 8 +- src/vault/PellVault.sol | 11 +- src/vault/UniswapV3Vault.sol | 39 +- src/vault/VaultCore.sol | 9 +- src/vault/VaultManager.sol | 2 +- src/vault/interfaces/IDelegationManager.sol | 8 +- src/vault/interfaces/ISlasher.sol | 8 +- test/DebtTokenTest.t.sol | 9 +- test/FeeTest.t.sol | 1 - test/NexusYieldTest.t.sol | 49 +- test/OSHITokenTest.t.sol | 3 - test/RedeemTest.t.sol | 20 +- test/RewardManagerTest.t.sol | 26 +- test/StabilityPoolTest.t.sol | 45 +- test/SwapInWithOkxForkTest.t.sol | 424 ------------------ test/TroveManagerTest.t.sol | 23 +- test/mocks/MyOFT.t.sol | 199 ++++---- test/utils/DeployBase.t.sol | 9 +- 48 files changed, 277 insertions(+), 909 deletions(-) delete mode 100644 test/SwapInWithOkxForkTest.t.sol diff --git a/foundry.toml b/foundry.toml index 0206dd6..d91f03c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ out = "out" solc_version = "0.8.22" optimizer = true optimizer_runs = 200 -fs_permissions = [{ access = "read", path = "./"}] +fs_permissions = [{ access = "read", path = "./" }] libs = ['lib'] @@ -23,14 +23,14 @@ remappings = [ ] [fmt] # See https://book.getfoundry.sh/reference/config/formatter -int_types = "long" -line_length = 120 -bracket_spacing = true +int_types = "long" +line_length = 120 +bracket_spacing = true multiline_func_header = "all" -number_underscore = "thousands" -quote_style = "double" -sort_imports = true -ignore = ['./lib/**/*'] +number_underscore = "thousands" +quote_style = "double" +sort_imports = true +ignore = ['./lib/**/*'] [profile.default.rpc_endpoints] holesky = "${RPC_URL_HOLESKY}" diff --git a/script/DeploySatoshiPeriphery.s.sol b/script/DeploySatoshiPeriphery.s.sol index 040660f..7dc577b 100644 --- a/script/DeploySatoshiPeriphery.s.sol +++ b/script/DeploySatoshiPeriphery.s.sol @@ -18,8 +18,7 @@ contract DeploySatoshiPeriphery is Script { vm.startBroadcast(deployerKey); address peripheryImpl = address(new SatoshiPeriphery()); - bytes memory data = - abi.encodeCall(ISatoshiPeriphery.initialize, (IDebtToken(debtToken), xApp, owner)); + bytes memory data = abi.encodeCall(ISatoshiPeriphery.initialize, (IDebtToken(debtToken), xApp, owner)); ISatoshiPeriphery periphery = ISatoshiPeriphery(address(new ERC1967Proxy(peripheryImpl, data))); console.log("Implementation:", peripheryImpl); diff --git a/script/DeploySetup.s.sol b/script/DeploySetup.s.sol index 889e6d0..0d697b8 100644 --- a/script/DeploySetup.s.sol +++ b/script/DeploySetup.s.sol @@ -478,9 +478,7 @@ contract Deployer is Script, IERC2535DiamondCutInternal { bytes4[] memory selectors = new bytes4[](1); selectors[0] = Initializer.init.selector; facetCuts[0] = IERC2535DiamondCutInternal.FacetCut({ - target: address(initializer), - action: IERC2535DiamondCutInternal.FacetCutAction.ADD, - selectors: selectors + target: address(initializer), action: IERC2535DiamondCutInternal.FacetCutAction.ADD, selectors: selectors }); assert(address(debtToken) != address(0)); diff --git a/script/strategyVault/cygnus/SetCygnusVaultConfig.s.sol b/script/strategyVault/cygnus/SetCygnusVaultConfig.s.sol index 742f211..d113857 100644 --- a/script/strategyVault/cygnus/SetCygnusVaultConfig.s.sol +++ b/script/strategyVault/cygnus/SetCygnusVaultConfig.s.sol @@ -2,9 +2,6 @@ pragma solidity ^0.8.20; import { CygnusVault } from "../../../src/vault/CygnusVault.sol"; -import { IVault } from "../../../src/vault/interfaces/IVault.sol"; -import { IVaultManager } from "../../../src/vault/interfaces/IVaultManager.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Script, console2 } from "forge-std/Script.sol"; address constant CYGNUS_VAULT_ADDRESS = 0xE8c5b4517610006C1fb0eD5467E01e4bAd43558D; diff --git a/script/upgrade/UpgradeNYM.s.sol b/script/upgrade/UpgradeNYM.s.sol index bc8b685..54e4c4a 100644 --- a/script/upgrade/UpgradeNYM.s.sol +++ b/script/upgrade/UpgradeNYM.s.sol @@ -29,9 +29,7 @@ contract UpgradeNYMScript is Script { IERC2535DiamondCutInternal.FacetCut[] memory facetCuts = new IERC2535DiamondCutInternal.FacetCut[](1); facetCuts[0] = IERC2535DiamondCutInternal.FacetCut({ - target: newNYMImpl, - action: IERC2535DiamondCutInternal.FacetCutAction.ADD, - selectors: selectors + target: newNYMImpl, action: IERC2535DiamondCutInternal.FacetCutAction.ADD, selectors: selectors }); ISatoshiXApp XAPP = ISatoshiXApp(SATOSHI_X_APP_ADDRESS); diff --git a/src/OSHI/OSHIToken.sol b/src/OSHI/OSHIToken.sol index 43df691..7aa16db 100644 --- a/src/OSHI/OSHIToken.sol +++ b/src/OSHI/OSHIToken.sol @@ -8,8 +8,9 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import { ERC20PermitUpgradeable } from - "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import { + ERC20PermitUpgradeable +} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract OSHIToken is IOSHIToken, ERC20Upgradeable, ERC20PermitUpgradeable, UUPSUpgradeable, OwnableUpgradeable { diff --git a/src/OSHI/RewardManager.sol b/src/OSHI/RewardManager.sol index 2ce419e..87311b7 100644 --- a/src/OSHI/RewardManager.sol +++ b/src/OSHI/RewardManager.sol @@ -416,4 +416,6 @@ contract RewardManager is IRewardManager, UUPSUpgradeable, OwnableUpgradeable { ) isRegistered = true; require(isRegistered, "RewardManager: Caller is not Valid"); } + + receive() external payable { } } diff --git a/src/OSHI/interfaces/IRewardManager.sol b/src/OSHI/interfaces/IRewardManager.sol index 48762a8..861334f 100644 --- a/src/OSHI/interfaces/IRewardManager.sol +++ b/src/OSHI/interfaces/IRewardManager.sol @@ -13,7 +13,6 @@ enum LockDuration { SIX, // 6 months NINE, // 9 months TWELVE // 12 months - } uint256 constant NUMBER_OF_LOCK_DURATIONS = 4; diff --git a/src/core/DebtToken.sol b/src/core/DebtToken.sol index 18c503e..e9ca883 100644 --- a/src/core/DebtToken.sol +++ b/src/core/DebtToken.sol @@ -14,8 +14,9 @@ import { IDebtToken } from "./interfaces/IDebtToken.sol"; import { ITroveManager } from "./interfaces/ITroveManager.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import { ERC20PermitUpgradeable } from - "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import { + ERC20PermitUpgradeable +} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; /** * @title DebtToken diff --git a/src/core/GasPool.sol b/src/core/GasPool.sol index 24ff343..f80e1ee 100644 --- a/src/core/GasPool.sol +++ b/src/core/GasPool.sol @@ -11,5 +11,6 @@ import { IGasPool } from "./interfaces/IGasPool.sol"; * */ contract GasPool is IGasPool { -// do nothing, as the core contracts have permission to send to and burn from this address -} + // do nothing, as the core contracts have permission to send to and burn from this address + + } diff --git a/src/core/SatoshiXApp.sol b/src/core/SatoshiXApp.sol index 5a6f466..3f01523 100644 --- a/src/core/SatoshiXApp.sol +++ b/src/core/SatoshiXApp.sol @@ -16,5 +16,6 @@ import { SolidStateDiamond } from "../library/proxy/SolidStateDiamond.sol"; */ // solhint-disable-next-line no-empty-blocks contract SatoshiXApp is SolidStateDiamond, AccessControl { -// No extra logic -} + // No extra logic + + } diff --git a/src/core/facets/BorrowerOperationsFacet.sol b/src/core/facets/BorrowerOperationsFacet.sol index 2a70060..18aa821 100644 --- a/src/core/facets/BorrowerOperationsFacet.sol +++ b/src/core/facets/BorrowerOperationsFacet.sol @@ -368,8 +368,9 @@ contract BorrowerOperationsFacet is IBorrowerOperationsFacet, AccessControlInter require(_debtChange != 0, "BorrowerOps: Debt increase requires non-zero debtChange"); BorrowerOperationsLib._requireValidMaxFeePercentage(_maxFeePercentage); - vars.netDebtChange += - _triggerBorrowingFee(s, troveManager, collateralToken, account, _maxFeePercentage, _debtChange); + vars.netDebtChange += _triggerBorrowingFee( + s, troveManager, collateralToken, account, _maxFeePercentage, _debtChange + ); } // Calculate old and new ICRs and check if adjustment satisfies all conditions for the current system mode diff --git a/src/core/facets/FactoryFacet.sol b/src/core/facets/FactoryFacet.sol index a508335..392ff39 100644 --- a/src/core/facets/FactoryFacet.sol +++ b/src/core/facets/FactoryFacet.sol @@ -154,13 +154,7 @@ contract FactoryFacet is IFactoryFacet, AccessControlInternal { return ITroveManager(address(new BeaconProxy(address(s.troveManagerBeacon), data))); } - function setTMRewardRate( - uint128[] calldata _numerator, - uint128 _denominator - ) - external - onlyRole(Config.OWNER_ROLE) - { + function setTMRewardRate(uint128[] calldata _numerator, uint128 _denominator) external onlyRole(Config.OWNER_ROLE) { AppStorage.Layout storage s = AppStorage.layout(); // console.log("setTMRewardRate", _numerator.length, s.troveManagers.length); require(_numerator.length == s.troveManagers.length, "Factory: invalid length"); diff --git a/src/core/facets/LiquidationFacet.sol b/src/core/facets/LiquidationFacet.sol index a0c0cf2..2c53b7b 100644 --- a/src/core/facets/LiquidationFacet.sol +++ b/src/core/facets/LiquidationFacet.sol @@ -114,8 +114,8 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn ); if (singleLiquidation.debtToOffset == 0) continue; debtInStabPool -= singleLiquidation.debtToOffset; - entireSystemColl -= - (singleLiquidation.collToSendToSP + singleLiquidation.collSurplus) * troveManagerValues.price; + entireSystemColl -= (singleLiquidation.collToSendToSP + singleLiquidation.collSurplus) + * troveManagerValues.price; entireSystemDebt -= singleLiquidation.debtToOffset; _applyLiquidationValuesToTotals(totals, singleLiquidation); unchecked { @@ -295,9 +295,10 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn singleLiquidation.collToSendToSP, singleLiquidation.debtToRedistribute, singleLiquidation.collToRedistribute - ) = _getOffsetAndRedistributionVals( - singleLiquidation.entireTroveDebt, collToLiquidate, _debtInStabPool, sunsetting - ); + ) = + _getOffsetAndRedistributionVals( + singleLiquidation.entireTroveDebt, collToLiquidate, _debtInStabPool, sunsetting + ); troveManager.closeTroveByLiquidation(_borrower); diff --git a/src/core/facets/StabilityPoolFacet.sol b/src/core/facets/StabilityPoolFacet.sol index e80140b..1d1023a 100644 --- a/src/core/facets/StabilityPoolFacet.sol +++ b/src/core/facets/StabilityPoolFacet.sol @@ -5,9 +5,12 @@ import { SatoshiMath } from "../../library/SatoshiMath.sol"; import { AppStorage } from "../AppStorage.sol"; import { Config } from "../Config.sol"; -import { IDebtToken } from "../interfaces/IDebtToken.sol"; import { - AccountDeposit, IStabilityPoolFacet, Queue, Snapshots, SunsetIndex + AccountDeposit, + IStabilityPoolFacet, + Queue, + Snapshots, + SunsetIndex } from "../interfaces/IStabilityPoolFacet.sol"; import { StabilityPoolLib } from "../libs/StabilityPoolLib.sol"; @@ -38,8 +41,9 @@ contract StabilityPoolFacet is IStabilityPoolFacet, AccessControlInternal { function startCollateralSunset(IERC20 collateral) external onlyRole(Config.OWNER_ROLE) { AppStorage.Layout storage s = AppStorage.layout(); require(s.indexByCollateral[collateral] > 0, "Collateral already sunsetting"); - s.sunsetIndexes[s.queue.nextSunsetIndexKey++] = - SunsetIndex(uint128(s.indexByCollateral[collateral] - 1), uint128(block.timestamp + Config.SUNSET_DURATION)); + s.sunsetIndexes[s.queue.nextSunsetIndexKey++] = SunsetIndex( + uint128(s.indexByCollateral[collateral] - 1), uint128(block.timestamp + Config.SUNSET_DURATION) + ); delete s.indexByCollateral[collateral]; //This will prevent calls to the SP in case of liquidations emit CollateralSunset(address(collateral)); } @@ -191,11 +195,11 @@ contract StabilityPoolFacet is IStabilityPoolFacet, AccessControlInternal { uint256 firstPortion = sums[i] - depSums[i]; uint256 secondPortion = nextSums[i] / Config.SCALE_FACTOR; uint8 _decimals = IERC20Metadata(address(s.collateralTokens[i])).decimals(); - depositorGains[i] += uint80( - ( - initialDeposit * SatoshiMath._getOriginalCollateralAmount(firstPortion + secondPortion, _decimals) - / P_Snapshot / SatoshiMath.DECIMAL_PRECISION - ) + depositorGains[ + i + ] += uint80( + (initialDeposit * SatoshiMath._getOriginalCollateralAmount(firstPortion + secondPortion, _decimals) + / P_Snapshot / SatoshiMath.DECIMAL_PRECISION) ); } return (hasGains); @@ -234,8 +238,8 @@ contract StabilityPoolFacet is IStabilityPoolFacet, AccessControlInternal { (s.epochToScaleToG[epochSnapshot][scaleSnapshot + 1] + marginalOSHIGain) / Config.SCALE_FACTOR; } - return s.storedPendingReward[_depositor] - + (initialDeposit * (firstPortion + secondPortion)) / snapshots.P / SatoshiMath.DECIMAL_PRECISION; + return s.storedPendingReward[_depositor] + (initialDeposit * (firstPortion + secondPortion)) / snapshots.P + / SatoshiMath.DECIMAL_PRECISION; } function _claimableReward(AppStorage.Layout storage s, address _depositor) private view returns (uint256) { diff --git a/src/core/helpers/MultiTroveGetter.sol b/src/core/helpers/MultiTroveGetter.sol index 2a164bc..74dd0ff 100644 --- a/src/core/helpers/MultiTroveGetter.sol +++ b/src/core/helpers/MultiTroveGetter.sol @@ -76,9 +76,7 @@ contract MultiTroveGetter is IMultiTroveGetter { _troves[idx].stake, /* status */ /* arrayIndex */ - /* interestIndex */ - , - , + /* interestIndex */,, ) = troveManager.troves(currentTroveowner); (_troves[idx].snapshotCollateral, _troves[idx].snapshotDebt) = @@ -125,9 +123,7 @@ contract MultiTroveGetter is IMultiTroveGetter { _troves[idx].stake, /* status */ /* arrayIndex */ - /* interestIndex */ - , - , + /* interestIndex */,, ) = troveManager.troves(currentTroveowner); (_troves[idx].snapshotCollateral, _troves[idx].snapshotDebt) = diff --git a/src/core/helpers/SatoshiPeriphery.sol b/src/core/helpers/SatoshiPeriphery.sol index f22bae6..7ae3ccc 100644 --- a/src/core/helpers/SatoshiPeriphery.sol +++ b/src/core/helpers/SatoshiPeriphery.sol @@ -26,18 +26,21 @@ import { } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /** * @title Satoshi Borrower Operations Router * Handle the native token and ERC20 for the borrower operations */ -contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradeable { +contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; using SafeERC20 for DebtTokenWithLz; DebtTokenWithLz public debtToken; address public xApp; + address public okxRouter; + address public okxApprove; function initialize(IDebtToken _debtToken, address _xApp, address _owner) external initializer { if (address(_debtToken) == address(0)) revert InvalidZeroAddress(); @@ -48,6 +51,7 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea __Ownable_init(_owner); __UUPSUpgradeable_init_unchained(); + __ReentrancyGuard_init_unchained(); } receive() external payable { @@ -83,9 +87,8 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea uint256 debtTokenBalanceBefore = debtToken.balanceOf(address(this)); - IBorrowerOperationsFacet(xApp).openTrove( - troveManager, msg.sender, _maxFeePercentage, _collAmount, _debtAmount, _upperHint, _lowerHint - ); + IBorrowerOperationsFacet(xApp) + .openTrove(troveManager, msg.sender, _maxFeePercentage, _collAmount, _debtAmount, _upperHint, _lowerHint); uint256 debtTokenBalanceAfter = debtToken.balanceOf(address(this)); uint256 userDebtAmount = debtTokenBalanceAfter - debtTokenBalanceBefore; @@ -99,14 +102,7 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea /// @param _collAmount The amount of additional collateral /// @param _upperHint The upper hint (for querying the position of the sorted trove) /// @param _lowerHint The lower hint (for querying the position of the sorted trove) - function addColl( - ITroveManager troveManager, - uint256 _collAmount, - address _upperHint, - address _lowerHint - ) - external - { + function addColl(ITroveManager troveManager, uint256 _collAmount, address _upperHint, address _lowerHint) external { IERC20 collateralToken = troveManager.collateralToken(); _beforeAddColl(collateralToken, _collAmount); @@ -159,9 +155,8 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea payable { uint256 debtTokenBalanceBefore = debtToken.balanceOf(address(this)); - IBorrowerOperationsFacet(xApp).withdrawDebt( - troveManager, msg.sender, _maxFeePercentage, _debtAmount, _upperHint, _lowerHint - ); + IBorrowerOperationsFacet(xApp) + .withdrawDebt(troveManager, msg.sender, _maxFeePercentage, _debtAmount, _upperHint, _lowerHint); uint256 debtTokenBalanceAfter = debtToken.balanceOf(address(this)); uint256 userDebtAmount = debtTokenBalanceAfter - debtTokenBalanceBefore; require(userDebtAmount == _debtAmount, "SatoshiPeriphery: Debt amount mismatch"); @@ -214,17 +209,18 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea _beforeRepayDebt(_debtChange); } - IBorrowerOperationsFacet(xApp).adjustTrove( - troveManager, - msg.sender, - _maxFeePercentage, - _collDeposit, - _collWithdrawal, - _debtChange, - _isDebtIncrease, - _upperHint, - _lowerHint - ); + IBorrowerOperationsFacet(xApp) + .adjustTrove( + troveManager, + msg.sender, + _maxFeePercentage, + _collDeposit, + _collWithdrawal, + _debtChange, + _isDebtIncrease, + _upperHint, + _lowerHint + ); uint256 debtTokenBalanceAfter = debtToken.balanceOf(address(this)); // withdraw collateral @@ -349,37 +345,55 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea // No additional authorization logic is needed for this contract } + /// @notice Set the OKX DEX router address (routerContractAddress from OKX /swap API) + function setOkxRouter(address _okxRouter) external onlyOwner { + if (_okxRouter == address(0)) revert InvalidZeroAddress(); + okxRouter = _okxRouter; + emit OkxRouterSet(_okxRouter); + } + + /// @notice Set the OKX token-approve proxy address (tokenApproveAddress from OKX /approve-transaction API) + function setOkxApprove(address _okxApprove) external onlyOwner { + if (_okxApprove == address(0)) revert InvalidZeroAddress(); + okxApprove = _okxApprove; + emit OkxApproveSet(_okxApprove); + } + /// @notice Swap any ERC20 → stable token via OKX DEX → DebtToken via NYM.swapIn /// @dev ERC20 only — native token input is not supported /// @dev fromToken must be pre-approved to this contract by msg.sender /// @dev okxCalldata must be generated with this contract address as userWalletAddress /// @dev DebtToken is always delivered to msg.sender on the same chain - /// @dev okxApproveAddress is the tokenApproveAddress returned by the OKX /approve-transaction API - /// @dev okxRouter is the routerContractAddress returned by the OKX /swap API — these are different contracts function swapInWithOkx( address fromToken, uint256 fromAmount, - address okxApproveAddress, - address okxRouter, bytes calldata okxCalldata, address stableAsset, uint256 minDebtAmount - ) public { + ) + public + nonReentrant + { + address _okxRouter = okxRouter; + address _okxApprove = okxApprove; + require(_okxRouter != address(0), "SatoshiPeriphery: okxRouter not set"); + require(_okxApprove != address(0), "SatoshiPeriphery: okxApprove not set"); + IERC20(fromToken).safeTransferFrom(msg.sender, address(this), fromAmount); uint256 fromTokenBefore = IERC20(fromToken).balanceOf(address(this)); // Must approve the token approve proxy, NOT the router — OKX uses a separate spender contract - IERC20(fromToken).approve(okxApproveAddress, fromAmount); - IERC20(fromToken).approve(okxRouter, fromAmount); + IERC20(fromToken).forceApprove(_okxApprove, fromAmount); + IERC20(fromToken).forceApprove(_okxRouter, fromAmount); uint256 stableBalanceBefore = IERC20(stableAsset).balanceOf(address(this)); - (bool success,) = okxRouter.call(okxCalldata); + (bool success,) = _okxRouter.call(okxCalldata); require(success, "SatoshiPeriphery: OKX swap failed"); - IERC20(fromToken).approve(okxApproveAddress, 0); - IERC20(fromToken).approve(okxRouter, 0); + IERC20(fromToken).forceApprove(_okxApprove, 0); + IERC20(fromToken).forceApprove(_okxRouter, 0); uint256 fromTokenAfter = IERC20(fromToken).balanceOf(address(this)); if (fromTokenAfter > fromTokenBefore - fromAmount) { @@ -390,7 +404,7 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea uint256 stableReceived = IERC20(stableAsset).balanceOf(address(this)) - stableBalanceBefore; require(stableReceived > 0, "SatoshiPeriphery: No stable tokens received from OKX"); - IERC20(stableAsset).approve(xApp, stableReceived); + IERC20(stableAsset).forceApprove(xApp, stableReceived); uint256 debtTokenBalanceBefore = debtToken.balanceOf(address(this)); diff --git a/src/core/helpers/TroveManagerGetter.sol b/src/core/helpers/TroveManagerGetter.sol index 95884e7..d23d788 100644 --- a/src/core/helpers/TroveManagerGetter.sol +++ b/src/core/helpers/TroveManagerGetter.sol @@ -59,11 +59,7 @@ contract TroveManagerGetter is ITroveManagerGetter { /** * @notice Returns a list of trove managers where `account` has an existing trove */ - function getActiveTroveManagersForAccount(address account) - external - view - returns (ITroveManager[] memory, uint256) - { + function getActiveTroveManagersForAccount(address account) external view returns (ITroveManager[] memory, uint256) { uint256 length = IFactoryFacet(satoshiXApp).troveManagerCount(); ITroveManager[] memory troveManagers = new ITroveManager[](length); uint256 tmCount; diff --git a/src/core/helpers/interfaces/ISatoshiPeriphery.sol b/src/core/helpers/interfaces/ISatoshiPeriphery.sol index f793d8b..826a57e 100644 --- a/src/core/helpers/interfaces/ISatoshiPeriphery.sol +++ b/src/core/helpers/interfaces/ISatoshiPeriphery.sol @@ -24,12 +24,23 @@ interface ISatoshiPeriphery { error InsufficientMsgValue(uint256 msgValue, uint256 requiredValue); error SlippageTooHigh(uint256 actual, uint256 minimum); + event OkxRouterSet(address indexed okxRouter); + event OkxApproveSet(address indexed okxApprove); + function debtToken() external view returns (DebtTokenWithLz); function xApp() external view returns (address); + function okxRouter() external view returns (address); + + function okxApprove() external view returns (address); + function initialize(IDebtToken _debtToken, address _xApp, address _owner) external; + function setOkxRouter(address _okxRouter) external; + + function setOkxApprove(address _okxApprove) external; + function openTrove( ITroveManager troveManager, uint256 _maxFeePercentage, @@ -42,13 +53,7 @@ interface ISatoshiPeriphery { external payable; - function addColl( - ITroveManager troveManager, - uint256 _collAmount, - address _upperHint, - address _lowerHint - ) - external; + function addColl(ITroveManager troveManager, uint256 _collAmount, address _upperHint, address _lowerHint) external; function withdrawColl( ITroveManager troveManager, @@ -105,18 +110,15 @@ interface ISatoshiPeriphery { /// @notice Swap any ERC20 → stable token via OKX DEX → DebtToken via NYM.swapIn (ERC20 only) /// @param fromToken Input ERC20 token (must be approved to this contract) /// @param fromAmount Raw input amount - /// @param okxApproveAddress OKX token-approve proxy address (spender for internal approval) - /// @param okxRouter OKX DEX Router address from backend /okx/nym-swap response /// @param okxCalldata OKX swap calldata from backend /okx/nym-swap response /// @param stableAsset Stable token address from backend /okx/nym-swap response /// @param minDebtAmount Minimum DebtToken to receive; revert if below this (slippage guard) function swapInWithOkx( address fromToken, uint256 fromAmount, - address okxApproveAddress, - address okxRouter, bytes calldata okxCalldata, address stableAsset, uint256 minDebtAmount - ) external; + ) + external; } diff --git a/src/core/helpers/interfaces/ITroveHelper.sol b/src/core/helpers/interfaces/ITroveHelper.sol index 9b53ff5..b2db5f1 100644 --- a/src/core/helpers/interfaces/ITroveHelper.sol +++ b/src/core/helpers/interfaces/ITroveHelper.sol @@ -4,14 +4,7 @@ pragma solidity ^0.8.20; import { ITroveManager } from "../../interfaces/ITroveManager.sol"; interface ITroveHelper { - function getNicrByTime( - ITroveManager troveManager, - address _borrower, - uint256 time - ) - external - view - returns (uint256); + function getNicrByTime(ITroveManager troveManager, address _borrower, uint256 time) external view returns (uint256); function getNicrListByTime( ITroveManager troveManager, diff --git a/src/core/helpers/interfaces/ITroveManagerGetter.sol b/src/core/helpers/interfaces/ITroveManagerGetter.sol index dde2dd2..617c261 100644 --- a/src/core/helpers/interfaces/ITroveManagerGetter.sol +++ b/src/core/helpers/interfaces/ITroveManagerGetter.sol @@ -13,8 +13,5 @@ interface ITroveManagerGetter { function getAllCollateralsAndTroveManagers() external view returns (Collateral[] memory); - function getActiveTroveManagersForAccount(address account) - external - view - returns (ITroveManager[] memory, uint256); + function getActiveTroveManagersForAccount(address account) external view returns (ITroveManager[] memory, uint256); } diff --git a/src/core/interfaces/IBorrowerOperationsFacet.sol b/src/core/interfaces/IBorrowerOperationsFacet.sol index e9fe9ab..8210208 100644 --- a/src/core/interfaces/IBorrowerOperationsFacet.sol +++ b/src/core/interfaces/IBorrowerOperationsFacet.sol @@ -112,10 +112,7 @@ interface IBorrowerOperationsFacet { function minNetDebt() external view returns (uint256); - function troveManagersData(ITroveManager _troveManager) - external - view - returns (IERC20 collateralToken, uint16 index); + function troveManagersData(ITroveManager _troveManager) external view returns (IERC20 collateralToken, uint16 index); function forceResetTM(ITroveManager[] calldata _troveManagers) external; } diff --git a/src/core/interfaces/IGasPool.sol b/src/core/interfaces/IGasPool.sol index a62fd11..a862748 100644 --- a/src/core/interfaces/IGasPool.sol +++ b/src/core/interfaces/IGasPool.sol @@ -2,5 +2,6 @@ pragma solidity ^0.8.20; interface IGasPool { -// do nothing, as the core contracts have permission to send to and burn from this address -} + // do nothing, as the core contracts have permission to send to and burn from this address + + } diff --git a/src/core/interfaces/ILiquidationFacet.sol b/src/core/interfaces/ILiquidationFacet.sol index 6207d20..daf6fcb 100644 --- a/src/core/interfaces/ILiquidationFacet.sol +++ b/src/core/interfaces/ILiquidationFacet.sol @@ -12,6 +12,7 @@ struct TroveManagerValues { ///< Minimum Collateral Ratio bool sunsetting; } + ///< Indicates if the Trove Manager is being phased out /// @notice Represents values involved in a liquidation process @@ -34,6 +35,7 @@ struct LiquidationValues { ///< Collateral to redistribute uint256 collSurplus; } + ///< Surplus collateral /// @notice Represents total values involved in a sequence of liquidations @@ -56,6 +58,7 @@ struct LiquidationTotals { ///< Total collateral to redistribute uint256 totalCollSurplus; } + ///< Total collateral surplus /// @title ILiquidationFacet diff --git a/src/core/interfaces/ISatoshiXApp.sol b/src/core/interfaces/ISatoshiXApp.sol index 2ce497a..b0f41bf 100644 --- a/src/core/interfaces/ISatoshiXApp.sol +++ b/src/core/interfaces/ISatoshiXApp.sol @@ -5,5 +5,6 @@ import { IAccessControl } from "@solidstate/contracts/access/access_control/IAcc import { ISolidStateDiamond } from "@solidstate/contracts/proxy/diamond/ISolidStateDiamond.sol"; interface ISatoshiXApp is ISolidStateDiamond, IAccessControl { -// inherit from ISolidStateDiamond and IAccessControl, no additional interfaces -} + // inherit from ISolidStateDiamond and IAccessControl, no additional interfaces + + } diff --git a/src/core/interfaces/ISortedTroves.sol b/src/core/interfaces/ISortedTroves.sol index 92cb01b..e3d56f2 100644 --- a/src/core/interfaces/ISortedTroves.sol +++ b/src/core/interfaces/ISortedTroves.sol @@ -116,6 +116,7 @@ struct Node { ///< Id of next node (smaller NICR) in the list. address prevId; } + ///< Id of previous node (larger NICR) in the list. // Information for the list diff --git a/src/core/libs/BorrowerOperationsLib.sol b/src/core/libs/BorrowerOperationsLib.sol index b9bd3c6..81c4a37 100644 --- a/src/core/libs/BorrowerOperationsLib.sol +++ b/src/core/libs/BorrowerOperationsLib.sol @@ -166,10 +166,10 @@ library BorrowerOperationsLib { { uint256 loopEnd = balances.collaterals.length; for (uint256 i; i < loopEnd;) { - totalPricedCollateral += ( - SatoshiMath._getScaledCollateralAmount(balances.collaterals[i], balances.decimals[i]) - * balances.prices[i] - ); + totalPricedCollateral += (SatoshiMath._getScaledCollateralAmount( + balances.collaterals[i], balances.decimals[i] + ) + * balances.prices[i]); totalDebt += balances.debts[i]; unchecked { ++i; diff --git a/src/core/libs/OFTPermitUpgradeable.sol b/src/core/libs/OFTPermitUpgradeable.sol index 2514a6e..02054af 100644 --- a/src/core/libs/OFTPermitUpgradeable.sol +++ b/src/core/libs/OFTPermitUpgradeable.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.20; import { IOFT, OFTCoreUpgradeable } from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTCoreUpgradeable.sol"; -import { ERC20PermitUpgradeable } from - "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import { + ERC20PermitUpgradeable +} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; /** * @title OFT Contract diff --git a/src/core/libs/StabilityPoolLib.sol b/src/core/libs/StabilityPoolLib.sol index 1d37a75..c115c63 100644 --- a/src/core/libs/StabilityPoolLib.sol +++ b/src/core/libs/StabilityPoolLib.sol @@ -104,7 +104,8 @@ library StabilityPoolLib { * 4) Store these errors for use in the next correction when this function is called. * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended. */ - uint256 collateralNumerator = (_collToAdd * SatoshiMath.DECIMAL_PRECISION) + s.lastCollateralError_Offset[idx]; + uint256 collateralNumerator = + (_collToAdd * SatoshiMath.DECIMAL_PRECISION) + s.lastCollateralError_Offset[idx]; if (_debtToOffset == _totalDebtTokenDeposits) { debtLossPerUnitStaked = SatoshiMath.DECIMAL_PRECISION; // When the Pool depletes to 0, so does each deposit @@ -226,10 +227,7 @@ library StabilityPoolLib { return releasedToken; } - function _getCompoundedDebtDeposit( - AppStorage.Layout storage s, - address _depositor - ) + function _getCompoundedDebtDeposit(AppStorage.Layout storage s, address _depositor) internal view returns (uint256) diff --git a/src/library/interfaces/vault/IDelegationManager.sol b/src/library/interfaces/vault/IDelegationManager.sol index 7f6e150..917bc7d 100644 --- a/src/library/interfaces/vault/IDelegationManager.sol +++ b/src/library/interfaces/vault/IDelegationManager.sol @@ -287,13 +287,7 @@ interface IDelegationManager { /** * @notice Given array of strategies, returns array of shares for the operator */ - function getOperatorShares( - address operator, - IStrategy[] memory strategies - ) - external - view - returns (uint256[] memory); + function getOperatorShares(address operator, IStrategy[] memory strategies) external view returns (uint256[] memory); /** * @notice Given a list of strategies, return the minimum cooldown that must pass to withdraw diff --git a/src/library/interfaces/vault/ISlasher.sol b/src/library/interfaces/vault/ISlasher.sol index 21a38f3..e99cc4f 100644 --- a/src/library/interfaces/vault/ISlasher.sol +++ b/src/library/interfaces/vault/ISlasher.sol @@ -186,13 +186,7 @@ interface ISlasher { returns (uint32); /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].latestServeUntil`. - function getMiddlewareTimesIndexServeUntilTimestamp( - address operator, - uint32 index - ) - external - view - returns (uint32); + function getMiddlewareTimesIndexServeUntilTimestamp(address operator, uint32 index) external view returns (uint32); /// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`. function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256); diff --git a/src/vault/PellVault.sol b/src/vault/PellVault.sol index dd4cd81..1687425 100644 --- a/src/vault/PellVault.sol +++ b/src/vault/PellVault.sol @@ -187,9 +187,7 @@ contract PellVault is VaultCore { IDelegationManager.QueuedWithdrawalParams[] memory queuedWithdrawal = new IDelegationManager.QueuedWithdrawalParams[](1); queuedWithdrawal[0] = IDelegationManager.QueuedWithdrawalParams({ - strategies: strategies, - shares: shares, - withdrawer: address(this) + strategies: strategies, shares: shares, withdrawer: address(this) }); uint256 nonce = IDelegationManager(delegationManager).cumulativeWithdrawalsQueued(address(this)); @@ -231,9 +229,8 @@ contract PellVault is VaultCore { middlewareTimesIndexes[0] = 0; bool[] memory receiveAsTokens = new bool[](1); receiveAsTokens[0] = true; - IDelegationManager(delegationManager).completeQueuedWithdrawals( - withdrawals, tokens, middlewareTimesIndexes, receiveAsTokens - ); + IDelegationManager(delegationManager) + .completeQueuedWithdrawals(withdrawals, tokens, middlewareTimesIndexes, receiveAsTokens); _removeWithdrawalQueue(0); @@ -253,7 +250,7 @@ contract PellVault is VaultCore { strategy_[0] = withdrawalQueue[index].strategies[0]; if ( uint256(withdrawalQueue[index].startTimestamp) - + IDelegationManager(delegationManager).getWithdrawalDelay(strategy_) > block.timestamp + + IDelegationManager(delegationManager).getWithdrawalDelay(strategy_) > block.timestamp ) { revert WithdrawalTimeNotAvailable(); } diff --git a/src/vault/UniswapV3Vault.sol b/src/vault/UniswapV3Vault.sol index 462f4a5..2431f16 100644 --- a/src/vault/UniswapV3Vault.sol +++ b/src/vault/UniswapV3Vault.sol @@ -288,15 +288,15 @@ contract UniV3DexVault is VaultCore { IERC20(deposits[tokenId].token0).approve(address(nonfungiblePositionManager), amount0ToMint); IERC20(deposits[tokenId].token1).approve(address(nonfungiblePositionManager), amount1ToMint); - INonfungiblePositionManager.IncreaseLiquidityParams memory params = INonfungiblePositionManager - .IncreaseLiquidityParams({ - tokenId: tokenId, - amount0Desired: amount0ToMint, - amount1Desired: amount1ToMint, - amount0Min: amount0Min, - amount1Min: amount1Min, - deadline: block.timestamp - }); + INonfungiblePositionManager.IncreaseLiquidityParams memory params = + INonfungiblePositionManager.IncreaseLiquidityParams({ + tokenId: tokenId, + amount0Desired: amount0ToMint, + amount1Desired: amount1ToMint, + amount0Min: amount0Min, + amount1Min: amount1Min, + deadline: block.timestamp + }); nonfungiblePositionManager.increaseLiquidity(params); @@ -322,10 +322,7 @@ contract UniV3DexVault is VaultCore { // set amount0Max and amount1Max to uint256.max to collect all fees INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams({ - tokenId: tokenId, - recipient: vaultManager, - amount0Max: type(uint128).max, - amount1Max: type(uint128).max + tokenId: tokenId, recipient: vaultManager, amount0Max: type(uint128).max, amount1Max: type(uint128).max }); (uint256 amount0, uint256 amount1) = nonfungiblePositionManager.collect(params); @@ -369,14 +366,14 @@ contract UniV3DexVault is VaultCore { if (liquidity == 0) revert ZeroLiquidity(); if (liquidity > deposits[tokenId].liquidity) revert InvalidLiquidity(liquidity); - INonfungiblePositionManager.DecreaseLiquidityParams memory params = INonfungiblePositionManager - .DecreaseLiquidityParams({ - tokenId: tokenId, - liquidity: liquidity, - amount0Min: amount0Min, - amount1Min: amount1Min, - deadline: block.timestamp - }); + INonfungiblePositionManager.DecreaseLiquidityParams memory params = + INonfungiblePositionManager.DecreaseLiquidityParams({ + tokenId: tokenId, + liquidity: liquidity, + amount0Min: amount0Min, + amount1Min: amount1Min, + deadline: block.timestamp + }); nonfungiblePositionManager.decreaseLiquidity(params); deposits[tokenId].liquidity -= liquidity; diff --git a/src/vault/VaultCore.sol b/src/vault/VaultCore.sol index dc5785d..34e0401 100644 --- a/src/vault/VaultCore.sol +++ b/src/vault/VaultCore.sol @@ -55,12 +55,5 @@ abstract contract VaultCore is IVault, UUPSUpgradeable, OwnableUpgradeable { function getPosition(address token) external view virtual returns (uint256); - function constructExitByTroveManagerData( - address token, - uint256 amount - ) - external - view - virtual - returns (bytes memory); + function constructExitByTroveManagerData(address token, uint256 amount) external view virtual returns (bytes memory); } diff --git a/src/vault/VaultManager.sol b/src/vault/VaultManager.sol index ad62c57..2b281d1 100644 --- a/src/vault/VaultManager.sol +++ b/src/vault/VaultManager.sol @@ -14,7 +14,7 @@ import { IVaultManager } from "./interfaces/IVaultManager.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -/* +/* * @title VaultManager * @dev The contract is responsible for managing the vaults * Each TroveManager has a VaultManager diff --git a/src/vault/interfaces/IDelegationManager.sol b/src/vault/interfaces/IDelegationManager.sol index 2fd490e..36e617f 100644 --- a/src/vault/interfaces/IDelegationManager.sol +++ b/src/vault/interfaces/IDelegationManager.sol @@ -334,13 +334,7 @@ interface IDelegationManager is ISignatureUtils { /** * @notice Given array of strategies, returns array of shares for the operator */ - function getOperatorShares( - address operator, - IStrategy[] memory strategies - ) - external - view - returns (uint256[] memory); + function getOperatorShares(address operator, IStrategy[] memory strategies) external view returns (uint256[] memory); /** * @notice Given a list of strategies, return the minimum cooldown that must pass to withdraw diff --git a/src/vault/interfaces/ISlasher.sol b/src/vault/interfaces/ISlasher.sol index 21a38f3..e99cc4f 100644 --- a/src/vault/interfaces/ISlasher.sol +++ b/src/vault/interfaces/ISlasher.sol @@ -186,13 +186,7 @@ interface ISlasher { returns (uint32); /// @notice Getter function for fetching `operatorToMiddlewareTimes[operator][index].latestServeUntil`. - function getMiddlewareTimesIndexServeUntilTimestamp( - address operator, - uint32 index - ) - external - view - returns (uint32); + function getMiddlewareTimesIndexServeUntilTimestamp(address operator, uint32 index) external view returns (uint32); /// @notice Getter function for fetching `_operatorToWhitelistedContractsByUpdate[operator].size`. function operatorWhitelistedContractsLinkedListSize(address operator) external view returns (uint256); diff --git a/test/DebtTokenTest.t.sol b/test/DebtTokenTest.t.sol index f084771..b27ea27 100644 --- a/test/DebtTokenTest.t.sol +++ b/test/DebtTokenTest.t.sol @@ -2,16 +2,15 @@ pragma solidity ^0.8.20; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { Test, console } from "forge-std/Test.sol"; -import { Vm } from "forge-std/Vm.sol"; import { FlashloanTester } from "../src/test/FlashloanTester.sol"; -import { DEBT_TOKEN_NAME, DEBT_TOKEN_SYMBOL, DEPLOYER, OWNER } from "./TestConfig.sol"; +import { DEBT_TOKEN_NAME, DEBT_TOKEN_SYMBOL, OWNER } from "./TestConfig.sol"; import { DeployBase } from "./utils/DeployBase.t.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import { ERC20PermitUpgradeable } from - "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import { + ERC20PermitUpgradeable +} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; import { stdJson } from "forge-std/StdJson.sol"; diff --git a/test/FeeTest.t.sol b/test/FeeTest.t.sol index 8b13789..e69de29 100644 --- a/test/FeeTest.t.sol +++ b/test/FeeTest.t.sol @@ -1 +0,0 @@ - diff --git a/test/NexusYieldTest.t.sol b/test/NexusYieldTest.t.sol index f68484e..c463918 100644 --- a/test/NexusYieldTest.t.sol +++ b/test/NexusYieldTest.t.sol @@ -1,60 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import { CommunityIssuance } from "../src/OSHI/CommunityIssuance.sol"; - -import { OSHIToken } from "../src/OSHI/OSHIToken.sol"; -import { RewardManager } from "../src/OSHI/RewardManager.sol"; -import { ICommunityIssuance } from "../src/OSHI/interfaces/ICommunityIssuance.sol"; -import { IOSHIToken } from "../src/OSHI/interfaces/IOSHIToken.sol"; import { IRewardManager, LockDuration } from "../src/OSHI/interfaces/IRewardManager.sol"; import { Config } from "../src/core/Config.sol"; - -import { DebtToken } from "../src/core/DebtToken.sol"; -import { Initializer } from "../src/core/Initializer.sol"; -import { SatoshiXApp } from "../src/core/SatoshiXApp.sol"; - -import { SortedTroves } from "../src/core/SortedTroves.sol"; -import { TroveManager } from "../src/core/TroveManager.sol"; -import { BorrowerOperationsFacet } from "../src/core/facets/BorrowerOperationsFacet.sol"; -import { CoreFacet } from "../src/core/facets/CoreFacet.sol"; -import { FactoryFacet } from "../src/core/facets/FactoryFacet.sol"; -import { LiquidationFacet } from "../src/core/facets/LiquidationFacet.sol"; -import { NexusYieldManagerFacet } from "../src/core/facets/NexusYieldManagerFacet.sol"; -import { PriceFeedAggregatorFacet } from "../src/core/facets/PriceFeedAggregatorFacet.sol"; -import { StabilityPoolFacet } from "../src/core/facets/StabilityPoolFacet.sol"; - -import { SatoshiPeriphery } from "../src/core/helpers/SatoshiPeriphery.sol"; -import { IMultiCollateralHintHelpers } from "../src/core/helpers/interfaces/IMultiCollateralHintHelpers.sol"; -import { ISatoshiPeriphery, LzSendParam } from "../src/core/helpers/interfaces/ISatoshiPeriphery.sol"; -import { IBorrowerOperationsFacet } from "../src/core/interfaces/IBorrowerOperationsFacet.sol"; -import { ICoreFacet } from "../src/core/interfaces/ICoreFacet.sol"; -import { IDebtToken } from "../src/core/interfaces/IDebtToken.sol"; -import { DeploymentParams, IFactoryFacet } from "../src/core/interfaces/IFactoryFacet.sol"; -import { ILiquidationFacet } from "../src/core/interfaces/ILiquidationFacet.sol"; import { AssetConfig, INexusYieldManagerFacet } from "../src/core/interfaces/INexusYieldManagerFacet.sol"; -import { IPriceFeedAggregatorFacet } from "../src/core/interfaces/IPriceFeedAggregatorFacet.sol"; -import { ISatoshiXApp } from "../src/core/interfaces/ISatoshiXApp.sol"; - import { ISortedTroves } from "../src/core/interfaces/ISortedTroves.sol"; -import { IStabilityPoolFacet } from "../src/core/interfaces/IStabilityPoolFacet.sol"; -import { ITroveManager, TroveManagerOperation } from "../src/core/interfaces/ITroveManager.sol"; -import { SatoshiMath } from "../src/library/SatoshiMath.sol"; - -import { AggregatorV3Interface } from "../src/priceFeed/interfaces/AggregatorV3Interface.sol"; -import { IPriceFeed } from "../src/priceFeed/interfaces/IPriceFeed.sol"; +import { ITroveManager } from "../src/core/interfaces/ITroveManager.sol"; import "./TestConfig.sol"; import { ERC20Mock } from "./mocks/ERC20Mock.sol"; -import { OracleMock, RoundData } from "./mocks/OracleMock.sol"; -import { DeployBase, LocalVars } from "./utils/DeployBase.t.sol"; -import { HintLib } from "./utils/HintLib.sol"; +import { RoundData } from "./mocks/OracleMock.sol"; +import { DeployBase } from "./utils/DeployBase.t.sol"; import { TroveBase } from "./utils/TroveBase.t.sol"; -import { MessagingFee } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { console } from "forge-std/console.sol"; contract mock6 is ERC20Mock { constructor() ERC20Mock("MOCK", "MOCK") { } diff --git a/test/OSHITokenTest.t.sol b/test/OSHITokenTest.t.sol index e6aeac2..147b5b2 100644 --- a/test/OSHITokenTest.t.sol +++ b/test/OSHITokenTest.t.sol @@ -52,9 +52,6 @@ import { MessagingFee } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.s import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { console } from "forge-std/console.sol"; contract OSHITokenTest is DeployBase, TroveBase { using Math for uint256; diff --git a/test/RedeemTest.t.sol b/test/RedeemTest.t.sol index e0631bf..3e36cb7 100644 --- a/test/RedeemTest.t.sol +++ b/test/RedeemTest.t.sol @@ -108,10 +108,7 @@ contract RedeemTest is DeployBase, TroveBase { // price drop _updateRoundData( RoundData({ - answer: 3_050_000_000_000, - startedAt: block.timestamp, - updatedAt: block.timestamp, - answeredInRound: 1 + answer: 3_050_000_000_000, startedAt: block.timestamp, updatedAt: block.timestamp, answeredInRound: 1 }) ); @@ -136,10 +133,7 @@ contract RedeemTest is DeployBase, TroveBase { // price drop _updateRoundData( RoundData({ - answer: 4_000_000_000_000, - startedAt: block.timestamp, - updatedAt: block.timestamp, - answeredInRound: 1 + answer: 4_000_000_000_000, startedAt: block.timestamp, updatedAt: block.timestamp, answeredInRound: 1 }) ); @@ -163,10 +157,7 @@ contract RedeemTest is DeployBase, TroveBase { // price drop _updateRoundData( RoundData({ - answer: 4_000_000_000_000, - startedAt: block.timestamp, - updatedAt: block.timestamp, - answeredInRound: 1 + answer: 4_000_000_000_000, startedAt: block.timestamp, updatedAt: block.timestamp, answeredInRound: 1 }) ); @@ -195,10 +186,7 @@ contract RedeemTest is DeployBase, TroveBase { _updateRoundData( RoundData({ - answer: 4_000_000_000_000, - startedAt: block.timestamp, - updatedAt: block.timestamp, - answeredInRound: 1 + answer: 4_000_000_000_000, startedAt: block.timestamp, updatedAt: block.timestamp, answeredInRound: 1 }) ); diff --git a/test/RewardManagerTest.t.sol b/test/RewardManagerTest.t.sol index b10fcef..bf60d10 100644 --- a/test/RewardManagerTest.t.sol +++ b/test/RewardManagerTest.t.sol @@ -1,12 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import { CommunityIssuance } from "../src/OSHI/CommunityIssuance.sol"; - -import { OSHIToken } from "../src/OSHI/OSHIToken.sol"; -import { RewardManager } from "../src/OSHI/RewardManager.sol"; -import { ICommunityIssuance } from "../src/OSHI/interfaces/ICommunityIssuance.sol"; -import { IOSHIToken } from "../src/OSHI/interfaces/IOSHIToken.sol"; import { IRewardManager, LockDuration } from "../src/OSHI/interfaces/IRewardManager.sol"; import { DebtToken } from "../src/core/DebtToken.sol"; import { Initializer } from "../src/core/Initializer.sol"; @@ -29,31 +23,17 @@ import { IBorrowerOperationsFacet } from "../src/core/interfaces/IBorrowerOperat import { ICoreFacet } from "../src/core/interfaces/ICoreFacet.sol"; import { IDebtToken } from "../src/core/interfaces/IDebtToken.sol"; import { DeploymentParams, IFactoryFacet } from "../src/core/interfaces/IFactoryFacet.sol"; -import { ILiquidationFacet } from "../src/core/interfaces/ILiquidationFacet.sol"; -import { INexusYieldManagerFacet } from "../src/core/interfaces/INexusYieldManagerFacet.sol"; -import { IPriceFeedAggregatorFacet } from "../src/core/interfaces/IPriceFeedAggregatorFacet.sol"; -import { ISatoshiXApp } from "../src/core/interfaces/ISatoshiXApp.sol"; import { ISortedTroves } from "../src/core/interfaces/ISortedTroves.sol"; -import { IStabilityPoolFacet } from "../src/core/interfaces/IStabilityPoolFacet.sol"; -import { ITroveManager, TroveManagerOperation } from "../src/core/interfaces/ITroveManager.sol"; -import { SatoshiMath } from "../src/library/SatoshiMath.sol"; +import { ITroveManager } from "../src/core/interfaces/ITroveManager.sol"; -import { AggregatorV3Interface } from "../src/priceFeed/interfaces/AggregatorV3Interface.sol"; -import { IPriceFeed } from "../src/priceFeed/interfaces/IPriceFeed.sol"; import "./TestConfig.sol"; import { ERC20Mock } from "./mocks/ERC20Mock.sol"; -import { OracleMock, RoundData } from "./mocks/OracleMock.sol"; -import { DeployBase, LocalVars } from "./utils/DeployBase.t.sol"; -import { HintLib } from "./utils/HintLib.sol"; +import { RoundData } from "./mocks/OracleMock.sol"; +import { DeployBase } from "./utils/DeployBase.t.sol"; import { TroveBase } from "./utils/TroveBase.t.sol"; -import { MessagingFee } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { console } from "forge-std/console.sol"; contract RewardManagerTest is DeployBase, TroveBase { using Math for uint256; diff --git a/test/StabilityPoolTest.t.sol b/test/StabilityPoolTest.t.sol index 00ee482..d5488ee 100644 --- a/test/StabilityPoolTest.t.sol +++ b/test/StabilityPoolTest.t.sol @@ -1,59 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import { CommunityIssuance } from "../src/OSHI/CommunityIssuance.sol"; - -import { OSHIToken } from "../src/OSHI/OSHIToken.sol"; -import { RewardManager } from "../src/OSHI/RewardManager.sol"; -import { ICommunityIssuance } from "../src/OSHI/interfaces/ICommunityIssuance.sol"; -import { IOSHIToken } from "../src/OSHI/interfaces/IOSHIToken.sol"; -import { IRewardManager } from "../src/OSHI/interfaces/IRewardManager.sol"; -import { DebtToken } from "../src/core/DebtToken.sol"; -import { Initializer } from "../src/core/Initializer.sol"; -import { SatoshiXApp } from "../src/core/SatoshiXApp.sol"; - -import { SortedTroves } from "../src/core/SortedTroves.sol"; -import { TroveManager } from "../src/core/TroveManager.sol"; -import { BorrowerOperationsFacet } from "../src/core/facets/BorrowerOperationsFacet.sol"; -import { CoreFacet } from "../src/core/facets/CoreFacet.sol"; -import { FactoryFacet } from "../src/core/facets/FactoryFacet.sol"; -import { LiquidationFacet } from "../src/core/facets/LiquidationFacet.sol"; -import { NexusYieldManagerFacet } from "../src/core/facets/NexusYieldManagerFacet.sol"; -import { PriceFeedAggregatorFacet } from "../src/core/facets/PriceFeedAggregatorFacet.sol"; -import { StabilityPoolFacet } from "../src/core/facets/StabilityPoolFacet.sol"; - -import { SatoshiPeriphery } from "../src/core/helpers/SatoshiPeriphery.sol"; - -import { IMultiCollateralHintHelpers } from "../src/core/helpers/interfaces/IMultiCollateralHintHelpers.sol"; -import { ISatoshiPeriphery } from "../src/core/helpers/interfaces/ISatoshiPeriphery.sol"; -import { IWETH } from "../src/core/helpers/interfaces/IWETH.sol"; -import { IBorrowerOperationsFacet } from "../src/core/interfaces/IBorrowerOperationsFacet.sol"; -import { ICoreFacet } from "../src/core/interfaces/ICoreFacet.sol"; -import { IDebtToken } from "../src/core/interfaces/IDebtToken.sol"; -import { DeploymentParams, IFactoryFacet } from "../src/core/interfaces/IFactoryFacet.sol"; -import { ILiquidationFacet } from "../src/core/interfaces/ILiquidationFacet.sol"; -import { INexusYieldManagerFacet } from "../src/core/interfaces/INexusYieldManagerFacet.sol"; -import { IPriceFeedAggregatorFacet } from "../src/core/interfaces/IPriceFeedAggregatorFacet.sol"; -import { ISatoshiXApp } from "../src/core/interfaces/ISatoshiXApp.sol"; - import { ISortedTroves } from "../src/core/interfaces/ISortedTroves.sol"; -import { IStabilityPoolFacet } from "../src/core/interfaces/IStabilityPoolFacet.sol"; import { ITroveManager } from "../src/core/interfaces/ITroveManager.sol"; import { SatoshiMath } from "../src/library/SatoshiMath.sol"; -import { AggregatorV3Interface } from "../src/priceFeed/interfaces/AggregatorV3Interface.sol"; -import { IPriceFeed } from "../src/priceFeed/interfaces/IPriceFeed.sol"; import "./TestConfig.sol"; -import { OracleMock, RoundData } from "./mocks/OracleMock.sol"; +import { RoundData } from "./mocks/OracleMock.sol"; import { DeployBase } from "./utils/DeployBase.t.sol"; -import { HintLib } from "./utils/HintLib.sol"; import { TroveBase } from "./utils/TroveBase.t.sol"; import { IERC20Errors } from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { console } from "forge-std/console.sol"; contract StabilityPoolTest is DeployBase, TroveBase { uint256 maxFeePercentage = 0.05e18; // 5% diff --git a/test/SwapInWithOkxForkTest.t.sol b/test/SwapInWithOkxForkTest.t.sol deleted file mode 100644 index 33e5052..0000000 --- a/test/SwapInWithOkxForkTest.t.sol +++ /dev/null @@ -1,424 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -/** - * @title SwapInWithOkxForkTest - * @dev BSC mainnet fork test for SatoshiPeriphery.swapInWithOkx() - * - * Flow being tested: - * USER approves RIVER → SatoshiPeriphery - * SatoshiPeriphery: RIVER → OKX DEX → USDT → NYM.swapIn → DebtToken → USER - * - * Refresh OKX_CALLDATA when stale (deadline ~1 week): - * curl "http://api-airdrop.river.inc/okx/nym-swap?chainId=56&fromTokenAddress=0xdA7AD9dea9397cffdDAE2F8a052B82f1484252B3&amount=1000000000000000000&slippagePercent=1" - * Then replace OKX_CALLDATA, OKX_APPROVE_ADDR, OKX_ROUTER below. - * - * Run: - * cd satoshi-v2 - * forge test --match-contract SwapInWithOkxForkTest -vvvv - * forge test --match-contract SwapInWithOkxForkTest --match-test testDiagnostics -vvvv - * forge test --match-contract SwapInWithOkxForkTest --match-test testSwapInWithOkxStepByStep -vvvv - */ - -import { Test } from "forge-std/Test.sol"; -import { console2 as console } from "forge-std/console2.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { ISatoshiPeriphery } from "../src/core/helpers/interfaces/ISatoshiPeriphery.sol"; -import { INexusYieldManagerFacet } from "../src/core/interfaces/INexusYieldManagerFacet.sol"; - -contract SwapInWithOkxForkTest is Test { - // ── BSC mainnet addresses ───────────────────────────────────────────────── - address constant PERIPHERY = 0x246b28f44ec8CA47e83365f67Bd382D6A4952Ac8; - address constant X_APP = 0x07BbC5A83B83a5C440D1CAedBF1081426d0AA4Ec; - address constant DEBT_TOKEN = 0xb4818BB69478730EF4e33Cc068dD94278e2766cB; - address constant RIVER_TOKEN = 0xdA7AD9dea9397cffdDAE2F8a052B82f1484252B3; - address constant USDT_BSC = 0x55d398326f99059fF775485246999027B3197955; - - // Real wallet that holds RIVER on BSC (contract owner) - address constant USER = 0xf369359Cd4b9dABF61Aa26F19b6aa3CD88Ba39d6; - - // ── OKX addresses from /okx/nym-swap response ───────────────────────────── - // Last fetched: 2026-03-04 - address constant OKX_APPROVE_ADDR = 0x2c34A2Fb1d0b4f55de51E1d0bDEfaDDce6b7cDD6; - address constant OKX_ROUTER = 0x3156020dfF8D99af1dDC523ebDfb1ad2018554a0; - - uint256 constant FROM_AMOUNT = 1e18; // 1 RIVER - - // ── OKX calldata — each hex"" is exactly 32 bytes (64 hex chars) ────────── - // 4-byte selector f2c42696 - // Deadline word: 0x69a83891 = 2026-03-11 UTC (valid ~1 week from generation) - // minReceiveAmt: 0xeaaf379c22cd1766 ≈ 16.91 USDT (1% slippage on ~17.08 USDT) - bytes constant OKX_CALLDATA = abi.encodePacked( - hex"f2c42696", // selector - hex"000000000000000000000000000000000000000000000000000000000003606a", // w1 - hex"000000000000000000000000da7ad9dea9397cffddae2f8a052b82f1484252b3", // w2 fromToken - hex"00000000000000000000000055d398326f99059ff775485246999027b3197955", // w3 toToken - hex"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // w4 amount - hex"000000000000000000000000000000000000000000000000eaaf379c22cd1766", // w5 minReceive - hex"0000000000000000000000000000000000000000000000000000000069a83891", // w6 deadline - hex"00000000000000000000000000000000000000000000000000000000000000e0", // w7 offset - hex"0000000000000000000000000000000000000000000000000000000000000001", // w8 - hex"0000000000000000000000000000000000000000000000000000000000000020", // w9 - hex"00000000000000000000000000000000000000000000000000000000000000a0", // w10 - hex"00000000000000000000000000000000000000000000000000000000000000e0", // w11 - hex"0000000000000000000000000000000000000000000000000000000000000120", // w12 - hex"0000000000000000000000000000000000000000000000000000000000000160", // w13 - hex"000000000000000000000000da7ad9dea9397cffddae2f8a052b82f1484252b3", // w14 - hex"0000000000000000000000000000000000000000000000000000000000000001", // w15 - hex"0000000000000000000000007a7ad9aa93cd0a2d0255326e5fb145cec14997ff", // w16 - hex"0000000000000000000000000000000000000000000000000000000000000001", // w17 - hex"0000000000000000000000007a7ad9aa93cd0a2d0255326e5fb145cec14997ff", // w18 - hex"0000000000000000000000000000000000000000000000000000000000000001", // w19 - hex"800000000000000000012710886928eb467ef5e69b4ebc2c8af4275b21af41bd", // w20 - hex"0000000000000000000000000000000000000000000000000000000000000001", // w21 - hex"0000000000000000000000000000000000000000000000000000000000000020", // w22 - hex"00000000000000000000000000000000000000000000000000000000000000a0", // w23 - hex"0000000000000000000000000000000000000000000000000000000000000000", // w24 - hex"0000000000000000000000000000000000000000000000000000000000000040", // w25 - hex"0000000000000000000000000000000000000000000000000000000000000040", // w26 - hex"000000000000000000000000da7ad9dea9397cffddae2f8a052b82f1484252b3", // w27 - hex"00000000000000000000000055d398326f99059ff775485246999027b3197955", // w28 - hex"777777771111800000000000000000000000000000000000ed0e13f837d6ebad", // w29 - hex"777777771111000000000064fa00a9ed787f3793db668bff3e6e6e7db0f92a1b" // w30 - ); - - // ── Live-refresh helper ─────────────────────────────────────────────────── - - struct NymSwapQuote { - address okxApproveAddress; - address okxRouter; - bytes okxCalldata; - address stableAsset; - address peripheryAddress; - } - - /// @dev Calls the local backend and parses the /okx/nym-swap response. - /// Requires the backend to be running at api-airdrop.river.inc. - /// Requires ffi = true in foundry.toml (already set). - function _fetchNymSwapQuote( - address fromToken, - uint256 amount, - uint256 slippagePercent - ) internal returns (NymSwapQuote memory q) { - string memory url = string.concat( - "http://api-airdrop.river.inc/okx/nym-swap?chainId=56&fromTokenAddress=", - vm.toString(fromToken), - "&amount=", - vm.toString(amount), - "&slippagePercent=", - vm.toString(slippagePercent) - ); - - string[] memory cmd = new string[](3); - cmd[0] = "curl"; - cmd[1] = "-s"; - cmd[2] = url; - - bytes memory raw = vm.ffi(cmd); - string memory json = string(raw); - - q.okxApproveAddress = vm.parseJsonAddress(json, ".okxApproveAddress"); - q.okxRouter = vm.parseJsonAddress(json, ".okxRouter"); - q.okxCalldata = vm.parseJsonBytes(json, ".okxCalldata"); - q.stableAsset = vm.parseJsonAddress(json, ".stableAsset"); - q.peripheryAddress = vm.parseJsonAddress(json, ".peripheryAddress"); - } - - ISatoshiPeriphery periphery; - IERC20 river; - IERC20 usdt; - IERC20 debtToken; - - function setUp() public { - vm.createSelectFork("bsc"); - - periphery = ISatoshiPeriphery(PERIPHERY); - river = IERC20(RIVER_TOKEN); - usdt = IERC20(USDT_BSC); - debtToken = IERC20(DEBT_TOKEN); - } - - // ───────────────────────────────────────────────────────────────────────── - // DIAGNOSTIC — run first to inspect on-chain state without triggering swap - // ───────────────────────────────────────────────────────────────────────── - function testDiagnostics() public view { - console.log("=== BSC FORK DIAGNOSTICS ==="); - console.log("block.number :", block.number); - console.log("block.timestamp:", block.timestamp); - - // ── Periphery fields ───────────────────────────────────────────────── - address deployedXApp = periphery.xApp(); - address deployedDebtToken = address(periphery.debtToken()); - - console.log("\n--- SatoshiPeriphery ---"); - console.log("periphery.xApp() :", deployedXApp); - console.log("periphery.debtToken() :", deployedDebtToken); - console.log("expected xApp :", X_APP); - console.log("expected debtToken :", DEBT_TOKEN); - console.log("xApp match :", deployedXApp == X_APP); - console.log("debtToken match :", deployedDebtToken == DEBT_TOKEN); - - // ── swapInWithOkx exists on deployed bytecode? ──────────────────────── - // Zero-amount call will revert inside the function (not as "unknown selector") - bytes4 sel = ISatoshiPeriphery.swapInWithOkx.selector; - console.log("\n--- swapInWithOkx ---"); - console.logBytes4(sel); - (bool selExists,) = PERIPHERY.staticcall( - abi.encodeWithSelector(sel, RIVER_TOKEN, 0, address(0), address(0), hex"", USDT_BSC, 0) - ); - // staticcall will revert (state changes) — but if selector is missing it's a different revert - // We just check it doesn't silently return empty (i.e., function is present) - console.log("selector call returned (expect false/revert):", selExists); - - // ── User balances ───────────────────────────────────────────────────── - console.log("\n--- USER balances ---"); - console.log("RIVER :", river.balanceOf(USER)); - console.log("USDT :", usdt.balanceOf(USER)); - console.log("debtToken:", debtToken.balanceOf(USER)); - - // ── NYM state ──────────────────────────────────────────────────────── - INexusYieldManagerFacet nym = INexusYieldManagerFacet(X_APP); - bool nymPaused = nym.isNymPaused(); - bool usdtSupported = nym.isAssetSupported(USDT_BSC); - - console.log("\n--- NYM (xApp:", X_APP, ") ---"); - console.log("NYM paused :", nymPaused); - console.log("USDT supported :", usdtSupported); - - if (usdtSupported) { - console.log("mintCap :", nym.debtTokenMintCap(USDT_BSC)); - console.log("dailyCap :", nym.dailyDebtTokenMintCap(USDT_BSC)); - console.log("dailyRemain :", nym.debtTokenDailyMintCapRemain(USDT_BSC)); - console.log("totalMinted :", nym.debtTokenMinted(USDT_BSC)); - console.log("feeIn(1e18=1%) :", nym.feeIn(USDT_BSC)); - } - - // ── OKX contract code ───────────────────────────────────────────────── - console.log("\n--- OKX contracts ---"); - console.log("OKX_APPROVE_ADDR codeSize:", OKX_APPROVE_ADDR.code.length); - console.log("OKX_ROUTER codeSize:", OKX_ROUTER.code.length); - - // ── Periphery residual balances ─────────────────────────────────────── - console.log("\n--- Periphery residual balances ---"); - console.log("RIVER :", river.balanceOf(PERIPHERY)); - console.log("USDT :", usdt.balanceOf(PERIPHERY)); - console.log("debtToken:", debtToken.balanceOf(PERIPHERY)); - - // ── Calldata length sanity ──────────────────────────────────────────── - console.log("\nOKX_CALLDATA length (bytes):", OKX_CALLDATA.length); - // expected: 4 + 30*32 = 964 - } - - // ───────────────────────────────────────────────────────────────────────── - // STEP-BY-STEP — isolates exactly which sub-step fails - // ───────────────────────────────────────────────────────────────────────── - function testSwapInWithOkxStepByStep() public { - console.log("=== STEP-BY-STEP SWAP TEST ==="); - console.log("block:", block.number, "| ts:", block.timestamp); - console.log("calldata length:", OKX_CALLDATA.length); - - deal(address(river), USER, FROM_AMOUNT); - - // ── Step 1: USER → Periphery transfer ──────────────────────────────── - console.log("\n[Step 1] transferFrom USER -> Periphery"); - vm.prank(USER); - river.approve(PERIPHERY, FROM_AMOUNT); - - uint256 periRiverBefore = river.balanceOf(PERIPHERY); - vm.prank(PERIPHERY); - river.transferFrom(USER, PERIPHERY, FROM_AMOUNT); - uint256 periRiverAfter = river.balanceOf(PERIPHERY); - console.log("Periphery RIVER delta:", periRiverAfter - periRiverBefore); - require(periRiverAfter - periRiverBefore == FROM_AMOUNT, "Step 1 FAILED"); - console.log("[Step 1] PASS"); - - // ── Step 2: Periphery approves OKX ─────────────────────────────────── - console.log("\n[Step 2] Periphery approves OKX proxy + router"); - vm.startPrank(PERIPHERY); - river.approve(OKX_APPROVE_ADDR, FROM_AMOUNT); - river.approve(OKX_ROUTER, FROM_AMOUNT); - console.log("Allowance -> OKX_APPROVE_ADDR:", river.allowance(PERIPHERY, OKX_APPROVE_ADDR)); - console.log("Allowance -> OKX_ROUTER :", river.allowance(PERIPHERY, OKX_ROUTER)); - vm.stopPrank(); - console.log("[Step 2] PASS"); - - // ── Step 3: Call OKX router (RIVER → USDT) ─────────────────────────── - console.log("\n[Step 3] OKX router call: RIVER -> USDT"); - uint256 periUsdtBefore = usdt.balanceOf(PERIPHERY); - uint256 periRiverPre = river.balanceOf(PERIPHERY); - - vm.prank(PERIPHERY); - (bool okxSuccess, bytes memory okxReturnData) = OKX_ROUTER.call(OKX_CALLDATA); - - uint256 periUsdtAfter = usdt.balanceOf(PERIPHERY); - uint256 periRiverPost = river.balanceOf(PERIPHERY); - - console.log("OKX call success :", okxSuccess); - console.log("RIVER consumed :", periRiverPre - periRiverPost); - console.log("USDT received :", periUsdtAfter - periUsdtBefore); - - if (!okxSuccess) { - console.log("OKX revert bytes:"); - console.logBytes(okxReturnData); - if (okxReturnData.length >= 4) { - bytes4 errSel = bytes4(okxReturnData); - console.log("error selector:"); - console.logBytes4(errSel); - // Try to decode as Error(string) - if (errSel == bytes4(keccak256("Error(string)"))) { - bytes memory stripped = new bytes(okxReturnData.length - 4); - for (uint256 i = 0; i < stripped.length; i++) stripped[i] = okxReturnData[i + 4]; - (string memory reason) = abi.decode(stripped, (string)); - console.log("revert reason:", reason); - } - } - revert("Step 3 FAILED: OKX swap reverted"); - } - - uint256 usdtReceived = periUsdtAfter - periUsdtBefore; - require(usdtReceived > 0, "Step 3 FAILED: No USDT received from OKX"); - console.log("[Step 3] PASS - USDT received:", usdtReceived); - - // ── Step 4: NYM.swapIn (USDT → debtToken) ──────────────────────────── - console.log("\n[Step 4] NYM.swapIn: USDT -> debtToken"); - - INexusYieldManagerFacet nym = INexusYieldManagerFacet(X_APP); - console.log("USDT supported in NYM :", nym.isAssetSupported(USDT_BSC)); - console.log("NYM paused :", nym.isNymPaused()); - console.log("daily cap remain :", nym.debtTokenDailyMintCapRemain(USDT_BSC)); - - uint256 periDebtBefore = debtToken.balanceOf(PERIPHERY); - - vm.startPrank(PERIPHERY); - usdt.approve(X_APP, usdtReceived); - uint256 debtMinted = nym.swapIn(USDT_BSC, PERIPHERY, usdtReceived); - vm.stopPrank(); - - uint256 periDebtAfter = debtToken.balanceOf(PERIPHERY); - console.log("debtToken return val :", debtMinted); - console.log("debtToken balance delta:", periDebtAfter - periDebtBefore); - require(periDebtAfter > periDebtBefore, "Step 4 FAILED: No debtToken received from NYM"); - console.log("[Step 4] PASS - debtToken received:", debtMinted); - - // ── Step 5: Transfer debtToken to user ─────────────────────────────── - console.log("\n[Step 5] Transfer debtToken to USER"); - uint256 userDebtBefore = debtToken.balanceOf(USER); - vm.prank(PERIPHERY); - debtToken.transfer(USER, debtMinted); - uint256 userDebtAfter = debtToken.balanceOf(USER); - console.log("USER debtToken received:", userDebtAfter - userDebtBefore); - require(userDebtAfter - userDebtBefore == debtMinted, "Step 5 FAILED"); - console.log("[Step 5] PASS"); - - console.log("\n=== ALL STEPS PASSED ==="); - console.log("Total debtToken to user:", debtMinted); - } - - // ───────────────────────────────────────────────────────────────────────── - // END-TO-END — calls swapInWithOkx() on the deployed contract - // ───────────────────────────────────────────────────────────────────────── - function testSwapInWithOkxE2E() public { - console.log("=== E2E: swapInWithOkx() block:", block.number); - - deal(address(river), USER, FROM_AMOUNT); - - uint256 userDebtBefore = debtToken.balanceOf(USER); - uint256 userRiverBefore = river.balanceOf(USER); - console.log("USER RIVER before :", userRiverBefore); - console.log("USER debtToken before:", userDebtBefore); - - vm.startPrank(USER); - river.approve(PERIPHERY, FROM_AMOUNT); - - periphery.swapInWithOkx( - RIVER_TOKEN, - FROM_AMOUNT, - OKX_APPROVE_ADDR, - OKX_ROUTER, - OKX_CALLDATA, - USDT_BSC, - 0 // minDebtAmount = 0 to skip slippage guard while debugging - ); - vm.stopPrank(); - - uint256 userDebtAfter = debtToken.balanceOf(USER); - uint256 userRiverAfter = river.balanceOf(USER); - - console.log("USER RIVER after :", userRiverAfter); - console.log("USER debtToken after :", userDebtAfter); - console.log("debtToken received :", userDebtAfter - userDebtBefore); - - assertGt(userDebtAfter, userDebtBefore, "E2E: User must receive debtToken"); - assertLe(userRiverAfter, userRiverBefore, "E2E: RIVER must be consumed"); - } - - // ───────────────────────────────────────────────────────────────────────── - // LIVE test — fetches fresh calldata from the local backend at runtime. - // Requires: backend running on api-airdrop.river.inc AND ffi = true (already set). - // Run alone: forge test --match-test testSwapInWithOkxLive -vvvv - // ───────────────────────────────────────────────────────────────────────── - function testSwapInWithOkxLive() public { - console.log("=== LIVE E2E TEST (fresh calldata via /okx/nym-swap) ==="); - - NymSwapQuote memory q = _fetchNymSwapQuote(RIVER_TOKEN, FROM_AMOUNT, 1); - - console.log("okxApproveAddress :", q.okxApproveAddress); - console.log("okxRouter :", q.okxRouter); - console.log("stableAsset :", q.stableAsset); - console.log("peripheryAddress :", q.peripheryAddress); - console.log("okxCalldata length:", q.okxCalldata.length); - - deal(address(river), USER, FROM_AMOUNT); - - uint256 userDebtBefore = debtToken.balanceOf(USER); - uint256 userRiverBefore = river.balanceOf(USER); - console.log("USER RIVER before :", userRiverBefore); - console.log("USER debtToken before:", userDebtBefore); - - vm.startPrank(USER); - river.approve(q.peripheryAddress, FROM_AMOUNT); - - ISatoshiPeriphery(q.peripheryAddress).swapInWithOkx( - RIVER_TOKEN, - FROM_AMOUNT, - q.okxApproveAddress, - q.okxRouter, - q.okxCalldata, - q.stableAsset, - 0 - ); - vm.stopPrank(); - - uint256 userDebtAfter = debtToken.balanceOf(USER); - uint256 userRiverAfter = river.balanceOf(USER); - console.log("USER RIVER after :", userRiverAfter); - console.log("USER debtToken after :", userDebtAfter); - console.log("debtToken received :", userDebtAfter - userDebtBefore); - - assertEq(userRiverAfter, 0, "All RIVER should be spent"); - assertGt(userDebtAfter, userDebtBefore, "User must receive debtToken"); - } - - // ───────────────────────────────────────────────────────────────────────── - // SLIPPAGE GUARD — expects revert when minDebtAmount is unreachable - // ───────────────────────────────────────────────────────────────────────── - function testSwapInWithOkxSlippageRevert() public { - deal(address(river), USER, FROM_AMOUNT); - - vm.startPrank(USER); - river.approve(PERIPHERY, FROM_AMOUNT); - - vm.expectRevert(); // SlippageTooHigh(actual, minimum) - periphery.swapInWithOkx( - RIVER_TOKEN, - FROM_AMOUNT, - OKX_APPROVE_ADDR, - OKX_ROUTER, - OKX_CALLDATA, - USDT_BSC, - type(uint256).max - ); - vm.stopPrank(); - } -} diff --git a/test/TroveManagerTest.t.sol b/test/TroveManagerTest.t.sol index 5200006..93836ed 100644 --- a/test/TroveManagerTest.t.sol +++ b/test/TroveManagerTest.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import { CommunityIssuance } from "../src/OSHI/CommunityIssuance.sol"; - import { OSHIToken } from "../src/OSHI/OSHIToken.sol"; import { RewardManager } from "../src/OSHI/RewardManager.sol"; import { ICommunityIssuance } from "../src/OSHI/interfaces/ICommunityIssuance.sol"; @@ -30,28 +28,16 @@ import { IWETH } from "../src/core/helpers/interfaces/IWETH.sol"; import { IBorrowerOperationsFacet } from "../src/core/interfaces/IBorrowerOperationsFacet.sol"; import { ICoreFacet } from "../src/core/interfaces/ICoreFacet.sol"; import { IDebtToken } from "../src/core/interfaces/IDebtToken.sol"; -import { DeploymentParams, IFactoryFacet } from "../src/core/interfaces/IFactoryFacet.sol"; -import { ILiquidationFacet } from "../src/core/interfaces/ILiquidationFacet.sol"; -import { INexusYieldManagerFacet } from "../src/core/interfaces/INexusYieldManagerFacet.sol"; -import { IPriceFeedAggregatorFacet } from "../src/core/interfaces/IPriceFeedAggregatorFacet.sol"; -import { ISatoshiXApp } from "../src/core/interfaces/ISatoshiXApp.sol"; +import { DeploymentParams } from "../src/core/interfaces/IFactoryFacet.sol"; import { ISortedTroves } from "../src/core/interfaces/ISortedTroves.sol"; -import { IStabilityPoolFacet } from "../src/core/interfaces/IStabilityPoolFacet.sol"; import { ITroveManager } from "../src/core/interfaces/ITroveManager.sol"; -import { AggregatorV3Interface } from "../src/priceFeed/interfaces/AggregatorV3Interface.sol"; -import { IPriceFeed } from "../src/priceFeed/interfaces/IPriceFeed.sol"; import "./TestConfig.sol"; -import { OracleMock, RoundData } from "./mocks/OracleMock.sol"; +import { RoundData } from "./mocks/OracleMock.sol"; import { DeployBase } from "./utils/DeployBase.t.sol"; -import { HintLib } from "./utils/HintLib.sol"; import { TroveBase } from "./utils/TroveBase.t.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { Vm } from "forge-std/Vm.sol"; -import { console } from "forge-std/console.sol"; contract TroveManagerTest is DeployBase, TroveBase { uint256 maxFeePercentage = 0.05e18; // 5% @@ -146,10 +132,7 @@ contract TroveManagerTest is DeployBase, TroveBase { vm.warp(block.timestamp + 365 days); _updateRoundData( RoundData({ - answer: 4_000_000_000_000, - startedAt: block.timestamp, - updatedAt: block.timestamp, - answeredInRound: 2 + answer: 4_000_000_000_000, startedAt: block.timestamp, updatedAt: block.timestamp, answeredInRound: 2 }) ); _openTrove(USER_A, 1e18, 1000e18); diff --git a/test/mocks/MyOFT.t.sol b/test/mocks/MyOFT.t.sol index e0bdf99..29d96b3 100644 --- a/test/mocks/MyOFT.t.sol +++ b/test/mocks/MyOFT.t.sol @@ -9,7 +9,8 @@ import { OFTMock } from "./OFTMock.sol"; // OApp imports import { - EnforcedOptionParam, IOAppOptionsType3 + EnforcedOptionParam, + IOAppOptionsType3 } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol"; import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol"; @@ -31,113 +32,89 @@ import "forge-std/console.sol"; // import {TestHelperOz5} from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; contract MyOFTTest { -// using OptionsBuilder for bytes; - -// uint32 private aEid = 1; -// uint32 private bEid = 2; - -// OFTMock private aOFT; -// OFTMock private bOFT; - -// address private userA = address(0x1); -// address private userB = address(0x2); -// uint256 private initialBalance = 100 ether; - -// function setUp() public virtual override { -// vm.deal(userA, 1000 ether); -// vm.deal(userB, 1000 ether); - -// super.setUp(); -// setUpEndpoints(2, LibraryType.UltraLightNode); - -// aOFT = OFTMock( -// _deployOApp(type(OFTMock).creationCode, abi.encode("aOFT", "aOFT", address(endpoints[aEid]), address(this))) -// ); - -// bOFT = OFTMock( -// _deployOApp(type(OFTMock).creationCode, abi.encode("bOFT", "bOFT", address(endpoints[bEid]), address(this))) -// ); - -// // config and wire the ofts -// address[] memory ofts = new address[](2); -// ofts[0] = address(aOFT); -// ofts[1] = address(bOFT); -// this.wireOApps(ofts); - -// // mint tokens -// aOFT.mint(userA, initialBalance); -// bOFT.mint(userB, initialBalance); -// } - -// function test_constructor() public { -// assertEq(aOFT.owner(), address(this)); -// assertEq(bOFT.owner(), address(this)); - -// assertEq(aOFT.balanceOf(userA), initialBalance); -// assertEq(bOFT.balanceOf(userB), initialBalance); - -// assertEq(aOFT.token(), address(aOFT)); -// assertEq(bOFT.token(), address(bOFT)); -// } - -// function test_send_oft() public { -// uint256 tokensToSend = 1 ether; -// bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); -// SendParam memory sendParam = -// SendParam(bEid, addressToBytes32(userB), tokensToSend, tokensToSend, options, "", ""); -// MessagingFee memory fee = aOFT.quoteSend(sendParam, false); - -// assertEq(aOFT.balanceOf(userA), initialBalance); -// assertEq(bOFT.balanceOf(userB), initialBalance); - -// vm.prank(userA); -// aOFT.send{value: fee.nativeFee}(sendParam, fee, payable(address(this))); -// verifyPackets(bEid, addressToBytes32(address(bOFT))); - -// assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); -// assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend); -// } - -// function test_send_oft_compose_msg() public { -// uint256 tokensToSend = 1 ether; - -// OFTComposerMock composer = new OFTComposerMock(); - -// bytes memory options = -// OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0).addExecutorLzComposeOption(0, 500000, 0); -// bytes memory composeMsg = hex"1234"; -// SendParam memory sendParam = -// SendParam(bEid, addressToBytes32(address(composer)), tokensToSend, tokensToSend, options, composeMsg, ""); -// MessagingFee memory fee = aOFT.quoteSend(sendParam, false); - -// assertEq(aOFT.balanceOf(userA), initialBalance); -// assertEq(bOFT.balanceOf(address(composer)), 0); - -// vm.prank(userA); -// (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = -// aOFT.send{value: fee.nativeFee}(sendParam, fee, payable(address(this))); -// verifyPackets(bEid, addressToBytes32(address(bOFT))); - -// // lzCompose params -// uint32 dstEid_ = bEid; -// address from_ = address(bOFT); -// bytes memory options_ = options; -// bytes32 guid_ = msgReceipt.guid; -// address to_ = address(composer); -// bytes memory composerMsg_ = OFTComposeMsgCodec.encode( -// msgReceipt.nonce, aEid, oftReceipt.amountReceivedLD, abi.encodePacked(addressToBytes32(userA), composeMsg) -// ); -// this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_); - -// assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); -// assertEq(bOFT.balanceOf(address(composer)), tokensToSend); - -// assertEq(composer.from(), from_); -// assertEq(composer.guid(), guid_); -// assertEq(composer.message(), composerMsg_); -// assertEq(composer.executor(), address(this)); -// assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test -// } - -// TODO import the rest of oft tests? -} + // using OptionsBuilder for bytes; + // uint32 private aEid = 1; + // uint32 private bEid = 2; + // OFTMock private aOFT; + // OFTMock private bOFT; + // address private userA = address(0x1); + // address private userB = address(0x2); + // uint256 private initialBalance = 100 ether; + // function setUp() public virtual override { + // vm.deal(userA, 1000 ether); + // vm.deal(userB, 1000 ether); + // super.setUp(); + // setUpEndpoints(2, LibraryType.UltraLightNode); + // aOFT = OFTMock( + // _deployOApp(type(OFTMock).creationCode, abi.encode("aOFT", "aOFT", address(endpoints[aEid]), address(this))) + // ); + // bOFT = OFTMock( + // _deployOApp(type(OFTMock).creationCode, abi.encode("bOFT", "bOFT", address(endpoints[bEid]), address(this))) + // ); + // // config and wire the ofts + // address[] memory ofts = new address[](2); + // ofts[0] = address(aOFT); + // ofts[1] = address(bOFT); + // this.wireOApps(ofts); + // // mint tokens + // aOFT.mint(userA, initialBalance); + // bOFT.mint(userB, initialBalance); + // } + // function test_constructor() public { + // assertEq(aOFT.owner(), address(this)); + // assertEq(bOFT.owner(), address(this)); + // assertEq(aOFT.balanceOf(userA), initialBalance); + // assertEq(bOFT.balanceOf(userB), initialBalance); + // assertEq(aOFT.token(), address(aOFT)); + // assertEq(bOFT.token(), address(bOFT)); + // } + // function test_send_oft() public { + // uint256 tokensToSend = 1 ether; + // bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + // SendParam memory sendParam = + // SendParam(bEid, addressToBytes32(userB), tokensToSend, tokensToSend, options, "", ""); + // MessagingFee memory fee = aOFT.quoteSend(sendParam, false); + // assertEq(aOFT.balanceOf(userA), initialBalance); + // assertEq(bOFT.balanceOf(userB), initialBalance); + // vm.prank(userA); + // aOFT.send{value: fee.nativeFee}(sendParam, fee, payable(address(this))); + // verifyPackets(bEid, addressToBytes32(address(bOFT))); + // assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + // assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend); + // } + // function test_send_oft_compose_msg() public { + // uint256 tokensToSend = 1 ether; + // OFTComposerMock composer = new OFTComposerMock(); + // bytes memory options = + // OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0).addExecutorLzComposeOption(0, 500000, 0); + // bytes memory composeMsg = hex"1234"; + // SendParam memory sendParam = + // SendParam(bEid, addressToBytes32(address(composer)), tokensToSend, tokensToSend, options, composeMsg, ""); + // MessagingFee memory fee = aOFT.quoteSend(sendParam, false); + // assertEq(aOFT.balanceOf(userA), initialBalance); + // assertEq(bOFT.balanceOf(address(composer)), 0); + // vm.prank(userA); + // (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = + // aOFT.send{value: fee.nativeFee}(sendParam, fee, payable(address(this))); + // verifyPackets(bEid, addressToBytes32(address(bOFT))); + // // lzCompose params + // uint32 dstEid_ = bEid; + // address from_ = address(bOFT); + // bytes memory options_ = options; + // bytes32 guid_ = msgReceipt.guid; + // address to_ = address(composer); + // bytes memory composerMsg_ = OFTComposeMsgCodec.encode( + // msgReceipt.nonce, aEid, oftReceipt.amountReceivedLD, abi.encodePacked(addressToBytes32(userA), composeMsg) + // ); + // this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_); + // assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + // assertEq(bOFT.balanceOf(address(composer)), tokensToSend); + // assertEq(composer.from(), from_); + // assertEq(composer.guid(), guid_); + // assertEq(composer.message(), composerMsg_); + // assertEq(composer.executor(), address(this)); + // assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test + // } + // TODO import the rest of oft tests? + + } diff --git a/test/utils/DeployBase.t.sol b/test/utils/DeployBase.t.sol index 360a45f..67d53cc 100644 --- a/test/utils/DeployBase.t.sol +++ b/test/utils/DeployBase.t.sol @@ -485,9 +485,7 @@ abstract contract DeployBase is Test { bytes4[] memory selectors = new bytes4[](1); selectors[0] = Initializer.init.selector; facetCuts[0] = IERC2535DiamondCutInternal.FacetCut({ - target: address(initializer), - action: IERC2535DiamondCutInternal.FacetCutAction.ADD, - selectors: selectors + target: address(initializer), action: IERC2535DiamondCutInternal.FacetCutAction.ADD, selectors: selectors }); assert(address(debtToken) != address(0)); @@ -525,10 +523,7 @@ abstract contract DeployBase is Test { function _deployMockTroveManager(address deployer) internal returns (ISortedTroves, ITroveManager) { collateralMock = new ERC20Mock("Collateral", "COLL"); initRoundData = RoundData({ - answer: 4_000_000_000_000, - startedAt: block.timestamp, - updatedAt: block.timestamp, - answeredInRound: 1 + answer: 4_000_000_000_000, startedAt: block.timestamp, updatedAt: block.timestamp, answeredInRound: 1 }); DeploymentParams memory deploymentParams = DeploymentParams({ minuteDecayFactor: MINUTE_DECAY_FACTOR, From 04a0258be7030c00c8f521fb9b09ed0d52637755 Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Wed, 18 Mar 2026 11:03:41 +0800 Subject: [PATCH 5/5] chore(periphery): remove approve to DexRouter --- src/core/helpers/SatoshiPeriphery.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/helpers/SatoshiPeriphery.sol b/src/core/helpers/SatoshiPeriphery.sol index 7ae3ccc..3ee7166 100644 --- a/src/core/helpers/SatoshiPeriphery.sol +++ b/src/core/helpers/SatoshiPeriphery.sol @@ -385,7 +385,6 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea // Must approve the token approve proxy, NOT the router — OKX uses a separate spender contract IERC20(fromToken).forceApprove(_okxApprove, fromAmount); - IERC20(fromToken).forceApprove(_okxRouter, fromAmount); uint256 stableBalanceBefore = IERC20(stableAsset).balanceOf(address(this)); @@ -393,7 +392,6 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea require(success, "SatoshiPeriphery: OKX swap failed"); IERC20(fromToken).forceApprove(_okxApprove, 0); - IERC20(fromToken).forceApprove(_okxRouter, 0); uint256 fromTokenAfter = IERC20(fromToken).balanceOf(address(this)); if (fromTokenAfter > fromTokenBefore - fromAmount) {