Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8e5395e
lock node dependecies
EvanWang-TKSpring Apr 1, 2026
ce6387b
implement whitelist check functionality across factory contracts and …
EvanWang-TKSpring Apr 6, 2026
5e2d3d9
add mock whitelist manager and fix tests
EvanWang-TKSpring Apr 6, 2026
5347758
add whitelist check to vault to check the markets and pools
EvanWang-TKSpring Apr 6, 2026
265183e
clean router v1 code
EvanWang-TKSpring Apr 6, 2026
3a68578
always use market as parameter in router abi
EvanWang-TKSpring Apr 6, 2026
fd63cc0
implement whitelist check in TermMaxRouter and TermMaxRouterV2, updat…
EvanWang-TKSpring Apr 6, 2026
17412f5
fix issues, add owner check when deploying, add Registry logic(regist…
EvanWang-TKSpring Apr 6, 2026
174a363
refactor: introduce Roles contract for centralized role management an…
EvanWang-TKSpring Apr 6, 2026
b7b0010
clean check logic
EvanWang-TKSpring Apr 6, 2026
d82a68a
fix tests
EvanWang-TKSpring Apr 6, 2026
b33238c
fix the order issue that attacker may front-run delegate and deploy d…
EvanWang-TKSpring Apr 7, 2026
44b955c
implement order expiry timestamp management and related error handling
EvanWang-TKSpring Apr 7, 2026
0bebdc5
change version tag
EvanWang-TKSpring Apr 7, 2026
eae7366
Merge branch 'fix/order_issues_20260407' into feat/whitelist_check
EvanWang-TKSpring Apr 7, 2026
060d5ed
update whitelist manager and fix unit tests
EvanWang-TKSpring Apr 7, 2026
079adb7
add stable ERC4626 functionality with access control and mock impleme…
EvanWang-TKSpring Apr 7, 2026
d6f0b1e
update contracts to use VersionV2_0_1 for improved functionality and …
EvanWang-TKSpring Apr 7, 2026
1d9cc6e
remove todo comment
EvanWang-TKSpring Apr 7, 2026
4a10d39
fix: The logic issue when adding reserve via acess manager; Setting u…
EvanWang-TKSpring Apr 9, 2026
080ec32
feat: Integrate whitelist checks in MakerHelper
EvanWang-TKSpring Apr 9, 2026
c2adff6
Merge pull request #24 from term-structure/feat/whitelist_check
EvanWang-TKSpring Apr 14, 2026
c08bb0f
merge scripts from v2/deployment
EvanWang-TKSpring Apr 14, 2026
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,4 @@ ignition/deployments/chain-31337

# Soldeer
/dependencies
soldeer.lock
.DS_Store
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,5 @@ Add configurations to your .vscode/settings.json
```

```shell
$ yarn
$ npm ci
```
16 changes: 3 additions & 13 deletions contracts/v1/access/AccessManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {ITermMaxOrder} from "../ITermMaxOrder.sol";
import {IOracle} from "../oracle/IOracle.sol";
import {ITermMaxVault} from "../vault/ITermMaxVault.sol";
import {MarketConfig, FeeConfig, MarketInitialParams} from "../storage/TermMaxStorage.sol";
import {Roles} from "../../v2/access/Roles.sol";

interface IOwnable {
function transferOwnership(address newOwner) external;
Expand All @@ -27,20 +28,9 @@ interface IPausable {
* @title TermMax Access Manager
* @author Term Structure Labs
*/
contract AccessManager is AccessControlUpgradeable, UUPSUpgradeable {
contract AccessManager is AccessControlUpgradeable, UUPSUpgradeable, Roles {
error CannotRevokeDefaultAdminRole();

/// @notice Role to manage switch
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// @notice Role to manage configuration items
bytes32 public constant CONFIGURATOR_ROLE = keccak256("CONFIGURATOR_ROLE");
/// @notice Role to manage vault
bytes32 public constant VAULT_ROLE = keccak256("VAULT_ROLE");
/// @notice Role to manage oracle
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
/// @notice Role to manage market
bytes32 public constant MARKET_ROLE = keccak256("MARKET_ROLE");

function initialize(address admin) public initializer {
__UUPSUpgradeable_init();
__AccessControl_init();
Expand All @@ -50,7 +40,7 @@ contract AccessManager is AccessControlUpgradeable, UUPSUpgradeable {
/// @notice Set GT implementation to the factory
function setGtImplement(ITermMaxFactory factory, string memory gtImplementName, address gtImplement)
external
onlyRole(MARKET_ROLE)
onlyRole(TERMMAX_MARKET_FACTORY_ROLE)
{
factory.setGtImplement(gtImplementName, gtImplement);
}
Expand Down
48 changes: 0 additions & 48 deletions contracts/v1/router/ITermMaxRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,30 +102,6 @@ interface ITermMaxRouter {
uint256 deadline
) external returns (uint256 netTokenIn);

/**
* @notice Sells FT and XT tokens for underlying tokens
* @dev Executes multiple orders to sell tokens
* @param recipient Address to receive the output tokens
* @param market The market to sell tokens in
* @param ftInAmt Amount of FT tokens to sell
* @param xtInAmt Amount of XT tokens to sell
* @param orders Array of orders to execute
* @param amtsToSellTokens Array of amounts to sell for each order
* @param minTokenOut Minimum amount of output tokens to receive
* @param deadline The deadline timestamp for the transaction
* @return netTokenOut Actual amount of output tokens received
*/
function sellTokens(
address recipient,
ITermMaxMarket market,
uint128 ftInAmt,
uint128 xtInAmt,
ITermMaxOrder[] memory orders,
uint128[] memory amtsToSellTokens,
uint128 minTokenOut,
uint256 deadline
) external returns (uint256 netTokenOut);

/**
* @notice Creates a leveraged position from input tokens
* @dev Swaps tokens for XT and creates a leveraged position
Expand Down Expand Up @@ -290,28 +266,4 @@ interface ITermMaxRouter {
SwapUnit[] memory units,
uint256 minTokenOut
) external returns (uint256 redeemedAmt);

/**
* @notice Creates an order and deposits tokens
* @dev Creates a new order and deposits tokens to the market
* @param market The market to create order in
* @param maker Address of the order maker
* @param maxXtReserve Maximum amount of XT to reserve
* @param swapTrigger Swap trigger callback
* @param debtTokenToDeposit Amount of debt tokens to deposit
* @param ftToDeposit Amount of FT tokens to deposit
* @param xtToDeposit Amount of XT tokens to deposit
* @param curveCuts Curve cuts for the order
* @return order The created order
*/
function createOrderAndDeposit(
ITermMaxMarket market,
address maker,
uint256 maxXtReserve,
ISwapCallback swapTrigger,
uint256 debtTokenToDeposit,
uint128 ftToDeposit,
uint128 xtToDeposit,
CurveCuts memory curveCuts
) external returns (ITermMaxOrder order);
}
118 changes: 22 additions & 96 deletions contracts/v1/router/TermMaxRouter_V1_1_2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {ISwapCallback} from "../ISwapCallback.sol";
import {Constants} from "../lib/Constants.sol";
import {MathLib} from "../lib/MathLib.sol";
import {IGtRepayer} from "./IGtRepayer.sol";
import {WithWhitelistCheck, IWhitelistManager} from "../../v2/access/WithWhitelistCheck.sol";

/**
* @title TermMax Router V1.1.2
Expand All @@ -44,7 +45,8 @@ contract TermMaxRouter_V1_1_2 is
RouterErrors,
RouterEvents,
IGtRepayer,
ReentrancyGuardUpgradeable
ReentrancyGuardUpgradeable,
WithWhitelistCheck
{
using SafeCast for *;
using TransferUtils for IERC20;
Expand Down Expand Up @@ -78,6 +80,10 @@ contract TermMaxRouter_V1_1_2 is

function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {}

constructor(address _whitelistManager)
WithWhitelistCheck(_whitelistManager, IWhitelistManager.ContractModule.MARKET)
{}

function initialize(address admin) public initializer {
__ReentrancyGuard_init_unchained();
__UUPSUpgradeable_init();
Expand Down Expand Up @@ -227,30 +233,6 @@ contract TermMaxRouter_V1_1_2 is
}
}

function sellTokens(
address recipient,
ITermMaxMarket market,
uint128 ftInAmt,
uint128 xtInAmt,
ITermMaxOrder[] memory orders,
uint128[] memory amtsToSellTokens,
uint128 minTokenOut,
uint256 deadline
) external nonReentrant whenNotPaused returns (uint256 netTokenOut) {
(IERC20 ft, IERC20 xt,,, IERC20 debtToken) = market.tokens();
(uint256 maxBurn, IERC20 toenToSell) = ftInAmt > xtInAmt ? (xtInAmt, ft) : (ftInAmt, xt);

ft.safeTransferFrom(msg.sender, address(this), ftInAmt);
ft.safeIncreaseAllowance(address(market), maxBurn);
xt.safeTransferFrom(msg.sender, address(this), xtInAmt);
xt.safeIncreaseAllowance(address(market), maxBurn);
market.burn(recipient, maxBurn);
netTokenOut = _swapExactTokenToToken(toenToSell, debtToken, recipient, orders, amtsToSellTokens, 0, deadline);
netTokenOut += maxBurn;
if (netTokenOut < minTokenOut) revert InsufficientTokenOut(address(debtToken), netTokenOut, minTokenOut);
emit SellTokens(market, msg.sender, recipient, ftInAmt, xtInAmt, orders, amtsToSellTokens, netTokenOut);
}

function leverageFromToken(
address recipient,
ITermMaxMarket market,
Expand All @@ -261,7 +243,7 @@ contract TermMaxRouter_V1_1_2 is
uint128 maxLtv,
SwapUnit[] memory units,
uint256 deadline
) external nonReentrant whenNotPaused returns (uint256 gtId, uint256 netXtOut) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256 gtId, uint256 netXtOut) {
assembly {
tstore(T_CALLBACK_ADDRESS_STORE, market) // set callback address
}
Expand Down Expand Up @@ -292,7 +274,7 @@ contract TermMaxRouter_V1_1_2 is
uint128 tokenInAmt,
uint128 maxLtv,
SwapUnit[] memory units
) external nonReentrant whenNotPaused returns (uint256 gtId) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256 gtId) {
assembly {
tstore(T_CALLBACK_ADDRESS_STORE, market) // set callback address
}
Expand Down Expand Up @@ -336,7 +318,7 @@ contract TermMaxRouter_V1_1_2 is
uint128 xtInAmt,
uint128 minTokenOutAmt,
SwapUnit[] memory units
) external nonReentrant whenNotPaused returns (uint256 gtId) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256 gtId) {
assembly {
tstore(T_CALLBACK_ADDRESS_STORE, market) // set callback address
}
Expand Down Expand Up @@ -378,7 +360,7 @@ contract TermMaxRouter_V1_1_2 is
uint128 collateralInAmt,
uint128 maxLtv,
SwapUnit[] memory units
) external nonReentrant whenNotPaused returns (uint256 gtId) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256 gtId) {
assembly {
tstore(T_CALLBACK_ADDRESS_STORE, market) // set callback address
}
Expand Down Expand Up @@ -411,7 +393,7 @@ contract TermMaxRouter_V1_1_2 is
uint128[] memory tokenAmtsWantBuy,
uint128 maxDebtAmt,
uint256 deadline
) external nonReentrant whenNotPaused returns (uint256) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256) {
(IERC20 ft,, IGearingToken gt, address collateralAddr, IERC20 debtToken) = market.tokens();
IERC20(collateralAddr).safeTransferFrom(msg.sender, address(this), collInAmt);
IERC20(collateralAddr).safeIncreaseAllowance(address(gt), collInAmt);
Expand All @@ -434,6 +416,7 @@ contract TermMaxRouter_V1_1_2 is
external
nonReentrant
whenNotPaused
onlyWhitelisted(address(market))
returns (uint256)
{
(IERC20 ft, IERC20 xt, IGearingToken gt, address collateralAddr,) = market.tokens();
Expand Down Expand Up @@ -462,6 +445,7 @@ contract TermMaxRouter_V1_1_2 is
external
nonReentrant
whenNotPaused
onlyWhitelisted(address(market))
{
(IERC20 ft, IERC20 xt, IGearingToken gt,,) = market.tokens();

Expand Down Expand Up @@ -492,7 +476,7 @@ contract TermMaxRouter_V1_1_2 is
bool byDebtToken,
SwapUnit[] memory units,
uint256 deadline
) external nonReentrant whenNotPaused returns (uint256 netTokenOut) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256 netTokenOut) {
revert("Use flashRepayFromColl with expectedOutput param");
}

Expand All @@ -506,7 +490,7 @@ contract TermMaxRouter_V1_1_2 is
uint256 expectedOutput,
SwapUnit[] memory units,
uint256 deadline
) external nonReentrant whenNotPaused returns (uint256 netTokenOut) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256 netTokenOut) {
(IERC20 ft,, IGearingToken gtToken,, IERC20 debtToken) = market.tokens();
assembly {
// set callback address
Expand All @@ -521,35 +505,6 @@ contract TermMaxRouter_V1_1_2 is
debtToken.safeTransfer(recipient, netTokenOut);
}

function flashRepayToGetCollateral(
address recipient,
ITermMaxMarket market,
uint256 gtId,
uint256 expectedOutput,
SwapUnit[] memory units
) external nonReentrant whenNotPaused returns (uint256 netTokenOut) {
(IERC20 ft,, IGearingToken gtToken, address collateralAddr, IERC20 debtToken) = market.tokens();
assembly {
// set callback address
tstore(T_CALLBACK_ADDRESS_STORE, gtToken)
}
gtToken.safeTransferFrom(msg.sender, address(this), gtId, "");
bool byDebtToken = true;
ITermMaxOrder[] memory orders = new ITermMaxOrder[](0);
uint128[] memory amtsToBuyFt = new uint128[](0);
gtToken.flashRepay(gtId, byDebtToken, abi.encode(orders, amtsToBuyFt, ft, units, 0));
netTokenOut = IERC20(collateralAddr).balanceOf(address(this));
if (netTokenOut < expectedOutput) {
revert InsufficientTokenOut(collateralAddr, expectedOutput, netTokenOut);
}
IERC20(collateralAddr).safeTransfer(recipient, netTokenOut);
// transfer remaining debt token to recipient
uint256 debtBalance = debtToken.balanceOf(address(this));
if (debtBalance != 0) {
debtToken.safeTransfer(recipient, debtBalance);
}
}

/**
* @inheritdoc ITermMaxRouter
*/
Expand All @@ -561,7 +516,7 @@ contract TermMaxRouter_V1_1_2 is
uint128[] memory ftAmtsWantBuy,
uint128 maxTokenIn,
uint256 deadline
) external nonReentrant whenNotPaused returns (uint256 returnAmt) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256 returnAmt) {
(IERC20 ft,, IGearingToken gt,, IERC20 debtToken) = market.tokens();

debtToken.safeTransferFrom(msg.sender, address(this), maxTokenIn);
Expand Down Expand Up @@ -591,7 +546,7 @@ contract TermMaxRouter_V1_1_2 is
uint256 ftAmount,
SwapUnit[] memory units,
uint256 minTokenOut
) external nonReentrant whenNotPaused returns (uint256) {
) external nonReentrant whenNotPaused onlyWhitelisted(address(market)) returns (uint256) {
(IERC20 ft,,, address collateralAddr, IERC20 debtToken) = market.tokens();
ft.safeTransferFrom(msg.sender, address(this), ftAmount);
ft.safeIncreaseAllowance(address(market), ftAmount);
Expand All @@ -605,33 +560,6 @@ contract TermMaxRouter_V1_1_2 is
return redeemedAmt;
}

function createOrderAndDeposit(
ITermMaxMarket market,
address maker,
uint256 maxXtReserve,
ISwapCallback swapTrigger,
uint256 debtTokenToDeposit,
uint128 ftToDeposit,
uint128 xtToDeposit,
CurveCuts memory curveCuts
) external nonReentrant whenNotPaused returns (ITermMaxOrder order) {
(IERC20 ft, IERC20 xt,,, IERC20 debtToken) = market.tokens();
order = market.createOrder(maker, maxXtReserve, swapTrigger, curveCuts);
if (debtTokenToDeposit > 0) {
debtToken.safeTransferFrom(msg.sender, address(this), debtTokenToDeposit);
debtToken.safeIncreaseAllowance(address(market), debtTokenToDeposit);
market.mint(address(order), debtTokenToDeposit);
}
if (ftToDeposit > 0) {
ft.safeTransferFrom(msg.sender, address(order), ftToDeposit);
}
if (xtToDeposit > 0) {
xt.safeTransferFrom(msg.sender, address(order), xtToDeposit);
}

emit CreateOrderAndDeposit(market, order, maker, debtTokenToDeposit, ftToDeposit, xtToDeposit, curveCuts);
}

/// @dev Market flash leverage flashloan callback
function executeOperation(address, IERC20, uint256 amount, bytes memory data)
external
Expand Down Expand Up @@ -665,9 +593,7 @@ contract TermMaxRouter_V1_1_2 is
uint256 totalAmount = amount + tokenInAmt;
collateralData = _doSwap(abi.encode(totalAmount), units);
SwapUnit memory lastUnit = units[units.length - 1];
if (!adapterWhitelist[lastUnit.adapter]) {
revert AdapterNotWhitelisted(lastUnit.adapter);
}
_checkWhitelisted(lastUnit.adapter, IWhitelistManager.ContractModule.ADAPTER);

if (flashLoanType == FlashLoanType.COLLATERAL) {
IERC20 collateral = IERC20(lastUnit.tokenOut);
Expand Down Expand Up @@ -727,9 +653,8 @@ contract TermMaxRouter_V1_1_2 is
revert SwapUnitsIsEmpty();
}
for (uint256 i = 0; i < units.length; ++i) {
if (!adapterWhitelist[units[i].adapter]) {
revert AdapterNotWhitelisted(units[i].adapter);
}
_checkWhitelisted(units[i].adapter, IWhitelistManager.ContractModule.ADAPTER);

bytes memory dataToSwap =
abi.encodeCall(ISwapAdapter.swap, (units[i].tokenIn, units[i].tokenOut, inputData, units[i].swapData));

Expand Down Expand Up @@ -759,6 +684,7 @@ contract TermMaxRouter_V1_1_2 is
override
nonReentrant
whenNotPaused
onlyWhitelisted(address(market))
returns (uint128 repayAmt)
{
(IERC20 ft,, IGearingToken gt,, IERC20 debtToken) = market.tokens();
Expand Down
Loading
Loading