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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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']

Expand All @@ -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}"
Expand Down
36 changes: 36 additions & 0 deletions script/DeploySatoshiPeriphery.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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");
address okxRouter = vm.envAddress("OKX_ROUTER");
address okxApprove = vm.envAddress("OKX_APPROVE");

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)));

periphery.setOkxRouter(okxRouter);
periphery.setOkxApprove(okxApprove);

console.log("Implementation:", peripheryImpl);
console.log("Proxy:", address(periphery));
console.log("OKX Router:", okxRouter);
console.log("OKX Approve:", okxApprove);

vm.stopBroadcast();
}
}
3 changes: 0 additions & 3 deletions script/strategyVault/cygnus/SetCygnusVaultConfig.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions script/upgrade/UpgradeNYM.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/OSHI/RewardManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -423,4 +423,6 @@ contract RewardManager is IRewardManager, UUPSUpgradeable, OwnableUpgradeable {
) isRegistered = true;
require(isRegistered, "RewardManager: Caller is not Valid");
}

receive() external payable { }
}
8 changes: 1 addition & 7 deletions src/core/facets/FactoryFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion src/core/facets/LiquidationFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn
if (singleLiquidation.debtToOffset == 0) continue;
debtInStabPool -= singleLiquidation.debtToOffset;
entireSystemColl -= (singleLiquidation.collToSendToSP + singleLiquidation.collSurplus)
* troveManagerValues.price;
* troveManagerValues.price;
entireSystemDebt -= singleLiquidation.debtToOffset;
_applyLiquidationValuesToTotals(totals, singleLiquidation);
unchecked {
Expand Down
1 change: 0 additions & 1 deletion src/core/facets/StabilityPoolFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ 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,
Expand Down
128 changes: 119 additions & 9 deletions src/core/helpers/SatoshiPeriphery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,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();
Expand All @@ -47,6 +51,7 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea

__Ownable_init(_owner);
__UUPSUpgradeable_init_unchained();
__ReentrancyGuard_init_unchained();
}

receive() external payable {
Expand Down Expand Up @@ -97,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);
Expand Down Expand Up @@ -346,4 +344,116 @@ contract SatoshiPeriphery is ISatoshiPeriphery, UUPSUpgradeable, OwnableUpgradea
function _authorizeUpgrade(address newImplementation) internal view override onlyOwner {
// 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 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,
bytes calldata okxCalldata,
address stableAsset,
uint256 minDebtAmount
)
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));

IERC20(fromToken).forceApprove(_okxApprove, fromAmount);

uint256 stableBalanceBefore = IERC20(stableAsset).balanceOf(address(this));

(bool success,) = _okxRouter.call(okxCalldata);
require(success, "SatoshiPeriphery: OKX swap failed");

IERC20(fromToken).forceApprove(_okxApprove, 0);

uint256 fromTokenAfter = IERC20(fromToken).balanceOf(address(this));
if (fromTokenAfter > fromTokenBefore - fromAmount) {
uint256 refund = fromTokenAfter - (fromTokenBefore - fromAmount);
IERC20(fromToken).safeTransfer(msg.sender, refund);
}

_okxSwapInToDebt(stableAsset, stableBalanceBefore, minDebtAmount);
}

/// @notice Swap native gas token (ETH/BNB/…) → stable token via OKX DEX → DebtToken via NYM.swapIn
/// @dev msg.value is the native token amount to swap
/// @dev okxCalldata must be generated with this contract address as userWalletAddress
/// @dev DebtToken is always delivered to msg.sender on the same chain
function swapInWithOkxNative(
bytes calldata okxCalldata,
address stableAsset,
uint256 minDebtAmount
)
external
payable
nonReentrant
{
address _okxRouter = okxRouter;
require(_okxRouter != address(0), "SatoshiPeriphery: okxRouter not set");
require(msg.value > 0, "SatoshiPeriphery: No native token sent");

uint256 nativeBefore = address(this).balance - msg.value;
uint256 stableBalanceBefore = IERC20(stableAsset).balanceOf(address(this));

(bool success,) = _okxRouter.call{value: msg.value}(okxCalldata);
require(success, "SatoshiPeriphery: OKX swap failed");

uint256 nativeAfter = address(this).balance;
if (nativeAfter > nativeBefore) {
(bool refundSuccess,) = msg.sender.call{value: nativeAfter - nativeBefore}("");
if (!refundSuccess) revert RefundFailed();
}

_okxSwapInToDebt(stableAsset, stableBalanceBefore, minDebtAmount);
}

/// @dev Shared tail for both swapInWithOkx and swapInWithOkxNative:
/// stable token → NYM.swapIn → DebtToken → msg.sender
function _okxSwapInToDebt(
address stableAsset,
uint256 stableBalanceBefore,
uint256 minDebtAmount
) private {
uint256 stableReceived = IERC20(stableAsset).balanceOf(address(this)) - stableBalanceBefore;
require(stableReceived > 0, "SatoshiPeriphery: No stable tokens received from OKX");

IERC20(stableAsset).forceApprove(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);
}
}
47 changes: 40 additions & 7 deletions src/core/helpers/interfaces/ISatoshiPeriphery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,25 @@ interface ISatoshiPeriphery {
error InvalidZeroAddress();
error RefundFailed();
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,
Expand All @@ -41,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,
Expand Down Expand Up @@ -100,4 +106,31 @@ interface ISatoshiPeriphery {
)
external
payable;

/// @notice Swap any ERC20 → stable token via OKX DEX → DebtToken via NYM.swapIn
/// @param fromToken Input ERC20 token (must be approved to this contract)
/// @param fromAmount Raw input amount
/// @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,
bytes calldata okxCalldata,
address stableAsset,
uint256 minDebtAmount
)
external;

/// @notice Swap native gas token (ETH/BNB/…) → stable token via OKX DEX → DebtToken via NYM.swapIn
/// @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 swapInWithOkxNative(
bytes calldata okxCalldata,
address stableAsset,
uint256 minDebtAmount
)
external
payable;
}
9 changes: 1 addition & 8 deletions src/core/helpers/interfaces/ITroveHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 1 addition & 4 deletions src/core/helpers/interfaces/ITroveManagerGetter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading