Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
160 commits
Select commit Hold shift + click to select a range
00240ba
feat: Execution adapter for ERC4626 vaults supporting both same-asset…
ojasarora77 Jan 2, 2026
13ae9d7
feat: LP input and output swaps
ojasarora77 Jan 2, 2026
2d01d1e
feat: Price adapter for assets using Chainlink oracle feeds
ojasarora77 Jan 2, 2026
763ef88
feat: rice adapter for ERC4626 vaults supporting both same-asset and …
ojasarora77 Jan 2, 2026
0ad48f5
feat: new and updated interface for adapters
ojasarora77 Jan 2, 2026
bb689e7
chore: add new error types
ojasarora77 Jan 2, 2026
39a57c0
chore: mock contracts for testing
ojasarora77 Jan 2, 2026
de84b63
chore: cleanup of old duplicate adapter
ojasarora77 Jan 2, 2026
de814f0
test: swap execuction
ojasarora77 Jan 2, 2026
d304dc7
test: mainnet fork testing for ERC4626 vault execution adapter
ojasarora77 Jan 2, 2026
fddbcd2
test: modify the existing tests as per new adapters
ojasarora77 Jan 2, 2026
dab9789
refactor: ERC4626ExecutionAdapter naming and validation
ojasarora77 Jan 13, 2026
b2415da
fix: comments and error lib repetead functions
ojasarora77 Jan 13, 2026
e03c9e4
chore: formatting
ojasarora77 Jan 13, 2026
8266a79
refactor: move slippage calculation from adapter to orchestrator
ojasarora77 Jan 13, 2026
604f4f0
feat: SwapExecutor to support both IExecutionAdapter, ISwapExecutor
matteoettam09 Jan 14, 2026
fa8a099
feat: implement dynamic swap executor architecture for vault adapters
ojasarora77 Jan 14, 2026
004450a
refactor: tests and functions refactor
ojasarora77 Jan 14, 2026
281912f
fix: modifed test as per contract name and functional changes
ojasarora77 Jan 14, 2026
2154404
feat: Consolidated separate mappings into a single struct
ojasarora77 Jan 14, 2026
33c681d
feat: slippage validation in _executeSell
ojasarora77 Jan 14, 2026
e5c3589
chore: main merge conflicts
matteoettam09 Jan 15, 2026
9c93c37
fix: duplicate decimals check
matteoettam09 Jan 15, 2026
c1b6329
feat: generic 4626 price adapter pseudocode
matteoettam09 Jan 15, 2026
6b3386a
chore: MockERC4626PriceAdapter review
matteoettam09 Jan 15, 2026
ae50fd4
fix: uniswap contracts deps
ojasarora77 Jan 16, 2026
baaf848
feat: enhanced price adapter functionality
ojasarora77 Jan 16, 2026
a636fb1
chore: lint
ojasarora77 Jan 16, 2026
5d090e5
chore: removed mainnet forking from config
ojasarora77 Jan 16, 2026
247c675
fix: tests as per the adpater architecture
ojasarora77 Jan 16, 2026
50c0d5e
chore: configured mainet forking RPC for CI
ojasarora77 Jan 20, 2026
7c9918b
fix: serect refrence in CI
ojasarora77 Jan 20, 2026
a4f318e
chore: fetching latest block for mainnet forking tests
ojasarora77 Jan 20, 2026
6d68ac9
fix: workflow file for mainnet forking
ojasarora77 Jan 20, 2026
f929a5e
chore: absolute path for forking tests
ojasarora77 Jan 20, 2026
0c46572
test: ChainlinkPriceAdapter tests
ojasarora77 Jan 21, 2026
243e58a
test: cross-asset vault pricing composition
ojasarora77 Jan 21, 2026
0b4e3ef
fix: price price staleness error
ojasarora77 Jan 21, 2026
4a9c337
chore: coverage for mainnet forking tests
ojasarora77 Jan 21, 2026
f58541e
fix: formatting
ojasarora77 Jan 21, 2026
5bbe130
Merge pull request #134 from OrionFinanceAI/dev
matteoettam09 Jan 22, 2026
e73b144
feat: decouple slippage tolerance and equilibrium buffer amount
matteoettam09 Jan 22, 2026
14f3a00
chore: set up conditional mainnet fokring
ojasarora77 Jan 23, 2026
ad466ef
chore: merge LO changes from main
matteoettam09 Jan 25, 2026
6f8a8db
fix: remove redundant state payload from _currentEpoch
matteoettam09 Jan 25, 2026
0665230
chore: simplify state hashing internal functions
matteoettam09 Jan 25, 2026
1d6c8e5
feat: Use domain separation for cryptographic robustness
matteoettam09 Jan 25, 2026
dd28482
feat: emit epoch prices for oracle health monitoring
matteoettam09 Jan 25, 2026
0b1fde1
feat: maintain public epoch prices getter
matteoettam09 Jan 25, 2026
440bb59
chore: refacto pausing APIs
matteoettam09 Jan 25, 2026
aec931a
feat: StatesStruct
matteoettam09 Jan 25, 2026
fdfdaac
fix: remove redundant checks
matteoettam09 Jan 25, 2026
9c7c533
fix: simplify zkVM API
matteoettam09 Jan 25, 2026
052b409
fix: simplify epochStateCommitment for zkVM
matteoettam09 Jan 25, 2026
e7f6b0d
feat: zkVM-compatible performUpKeep APIs
matteoettam09 Jan 26, 2026
9b0242b
fix: add to commit missing states for fee computation
matteoettam09 Jan 27, 2026
7c1df14
fix: update full buffer amount from zkVM payload
matteoettam09 Jan 27, 2026
ccb0421
chore: hardhat setup for reproducible environment and static zkVM fix…
matteoettam09 Jan 28, 2026
0674bfc
chore: gitignore
matteoettam09 Jan 29, 2026
d60e828
feat: add getAllOrionManagers() in config contract
matteoettam09 Jan 29, 2026
f20f18e
feat: sp1 local deployment for RPC-free reproducible testing
matteoettam09 Jan 29, 2026
9f4d9b4
feat: limit name/symbol vault names
matteoettam09 Jan 29, 2026
30f78f6
feat (OrionConfig): let manager decommission vaults themselves
matteoettam09 Jan 29, 2026
f1107db
feat(OrionConfig): getAllDecommissionedVaults
matteoettam09 Jan 29, 2026
1894462
test: reject invalid or malicious proofs
matteoettam09 Jan 29, 2026
68a4b05
feat: emit event on pendingProtocolFees accrual
matteoettam09 Jan 29, 2026
2b0c9c2
feat: execution minibatch
matteoettam09 Jan 29, 2026
4c7560c
chore: deprecate deltaBufferAmount
matteoettam09 Jan 29, 2026
0ee0782
test: closes #95
matteoettam09 Jan 29, 2026
280822d
feat: record and commit to _failedEpochTokens
matteoettam09 Jan 29, 2026
1f226b4
fix: remove redundant state commit
matteoettam09 Jan 29, 2026
e918b27
fix: remove redundant state commit
matteoettam09 Jan 29, 2026
48e0e75
chore(deps-dev): bump the development-dependencies group with 16 updates
dependabot[bot] Feb 2, 2026
c73ac06
Merge pull request #138 from OrionFinanceAI/dependabot/npm_and_yarn/d…
matteoettam09 Feb 2, 2026
6a66818
chore: bump zk circuit
matteoettam09 Feb 2, 2026
d98b6fe
fix: epoch fallback when failing position
matteoettam09 Feb 2, 2026
42a6d3f
feat: getAllWhitelistedAssetNames
matteoettam09 Feb 3, 2026
ed99be8
chore: PR review
matteoettam09 Feb 3, 2026
4fa0d8e
fix: decommission removed asset for correct zkVM accounting
matteoettam09 Feb 3, 2026
6754b39
feat: tests with zkVM features
matteoettam09 Feb 4, 2026
f1353ce
fix: timeout
matteoettam09 Feb 4, 2026
d6e8cca
fix: timeout
matteoettam09 Feb 4, 2026
2ba9528
chore: remove RPC from coverage in ci
matteoettam09 Feb 4, 2026
0d0d149
chore: deterministic test environment without RPC dependency
matteoettam09 Feb 4, 2026
d236bf5
chore: deterministic test environment without RPC dependency
matteoettam09 Feb 4, 2026
510f90b
chore: RedeemBeforeDepositOrder.test.ts
matteoettam09 Feb 4, 2026
85c92e4
chore: remove RPC from coverage in ci
matteoettam09 Feb 4, 2026
c387df4
test: ignore sp1 contracts
matteoettam09 Feb 4, 2026
58fde06
Merge pull request #140 from OrionFinanceAI/zk-orchestrator
matteoettam09 Feb 4, 2026
836c673
test: accounting coverage
matteoettam09 Feb 5, 2026
a3d93ec
Merge pull request #141 from OrionFinanceAI/dev
matteoettam09 Feb 5, 2026
b32467d
chore: merge main into adapter-execution branch
ojasarora77 Feb 8, 2026
6366542
fix: update test calls to match new 3-parameter IExecutionAdapter sig…
Feb 8, 2026
5ad219d
fix: update test files to use new adapter contract names
Feb 8, 2026
3ddadb4
fix: update adapter constructor calls to include liquidityOrchestrato…
Feb 8, 2026
f2d683f
fix: use MockPriceAdapter for same-asset vault tests instead of ERC46…
ojasarora77 Feb 8, 2026
d156ede
fix: precision test
ojasarora77 Feb 9, 2026
a275c10
feat: slippage management architecture to the LiquidityOrchestrator
ojasarora77 Feb 9, 2026
f6c55e4
test: slippage management in the LiquidityOrchestrator
ojasarora77 Feb 9, 2026
1ada1c5
chore: formatting
ojasarora77 Feb 9, 2026
2da74df
cleanup: estimatedUnderlyingAmount param as its calculated ofchain in LO
ojasarora77 Feb 9, 2026
b512240
Merge remote-tracking branch 'origin/main' into adapter-execution
ojasarora77 Feb 9, 2026
625ac90
fix: tests now use the new adapter
ojasarora77 Feb 10, 2026
fd6dc11
fix: bump
matteoettam09 Feb 10, 2026
74009bb
fix: env variables ci
matteoettam09 Feb 11, 2026
6803647
fix: deprecate ISwapExecutor for LO-compatible uniswap v3 integrations
matteoettam09 Feb 11, 2026
d0d26ec
fix: enfore slippage limit for both legs in LO
matteoettam09 Feb 11, 2026
6ca3aa2
fix: Uniswap adapter to implement IExecutionAdapter for direct LO com…
matteoettam09 Feb 11, 2026
23c2db5
docs: Convert angle-bracketed URLs to markdown links
matteoettam09 Feb 12, 2026
4a96aab
feat: _buySameAsset and _buyCrossAsset in adapters
ojasarora77 Feb 17, 2026
4e6c292
feat: mock adapters for testing the buy asset flow
ojasarora77 Feb 17, 2026
c3933fa
chore: cleanup
ojasarora77 Feb 17, 2026
fb815cd
test: buy flow with quoter via mainnet forking contracts
ojasarora77 Feb 17, 2026
824e8ac
Merge branch 'main' into adapter-execution
ojasarora77 Feb 17, 2026
aface62
chore: fulfil args
ojasarora77 Feb 17, 2026
a893a1d
chore: linter ignores monorepos
ojasarora77 Feb 17, 2026
301f5f7
chore: ignore slither flase positive
ojasarora77 Feb 17, 2026
f0a6168
test: accuracy and accounting testing
ojasarora77 Feb 17, 2026
4db4480
chore: mock adapter for previewBuy and buy testing
ojasarora77 Feb 17, 2026
b0dadb0
test: Atomic Guarantee
ojasarora77 Feb 17, 2026
8f15840
test: cross asset buy - dust and execution
ojasarora77 Feb 17, 2026
464ca3d
chore: mocks to test swap execution
ojasarora77 Feb 17, 2026
cc3de96
fix: formatting
ojasarora77 Feb 17, 2026
ed4c150
test: UniswapV3ExecutionAdapter Unit tests
ojasarora77 Feb 17, 2026
c6ade82
fix: correct args passed in test cases, complying to new adapters
ojasarora77 Feb 18, 2026
0e73d2e
chore: mainnet fork in CI - network reset
ojasarora77 Feb 18, 2026
1910172
frix: non-determinism in mock contract
ojasarora77 Feb 18, 2026
a07fae7
docs: verbose
ojasarora77 Feb 18, 2026
ce4fc08
test: stricter slippage testing
ojasarora77 Feb 18, 2026
3c24a45
chore: zero-address guard on LIQUIDITY_ORCHESTRATOR
ojasarora77 Feb 18, 2026
9e675f7
chore: security docs and overide specifiers
ojasarora77 Feb 18, 2026
8973540
chore: verbose error messages
ojasarora77 Feb 18, 2026
cf3fcc5
chore: swap order of tests in CI
ojasarora77 Feb 18, 2026
c0d8a04
fix: deciamls not hardcoded to 6
ojasarora77 Feb 19, 2026
bca7e47
chore: checks for price >1
ojasarora77 Feb 19, 2026
1ab9672
feat: ownable2step in chainlink price adapter
ojasarora77 Feb 19, 2026
3ed957e
fix: bigInt division
ojasarora77 Feb 19, 2026
6b38576
chore: verbose comments
ojasarora77 Feb 19, 2026
f727d11
chore: deterministic block number for mainnet forking
ojasarora77 Feb 19, 2026
caa1dc6
fix: deps
ojasarora77 Feb 19, 2026
2519631
fix: deps
ojasarora77 Feb 19, 2026
86fc6fe
chore: remove chainlink contracts to fix deps
ojasarora77 Feb 19, 2026
1aa9c28
fix: formatting
ojasarora77 Feb 19, 2026
6ce6ca0
chore: downgrade chainlink deps
ojasarora77 Feb 19, 2026
e0eacea
fix: ownable2step in tests
ojasarora77 Feb 19, 2026
01d3a88
fix: PR conflicts
matteoettam09 Feb 23, 2026
2f2919d
chore: mock to test refacto
matteoettam09 Feb 23, 2026
1d8768d
fix: ci
matteoettam09 Feb 23, 2026
9ef2e35
feat: ERC4626ExecutionAdapter to call previewBuy and tolerate dust
matteoettam09 Feb 23, 2026
962450e
fix: use openzeppelin Ownable
matteoettam09 Feb 23, 2026
07ba0f0
feat: default to permissionless exeution adapters
matteoettam09 Feb 23, 2026
626a698
feat: UniswapV3ExecutionAdapter avoid cross-adapter transactions
matteoettam09 Feb 23, 2026
a9c1bc4
feat: underlying-agnostic vault price adapter
matteoettam09 Feb 23, 2026
5f2d51a
chore: redundant variable
matteoettam09 Feb 23, 2026
7ba0d68
fix: avoid overflow risk in inverse chainlink feeds
matteoettam09 Feb 23, 2026
1055e87
test: vault whitelisted before underlying
ojasarora77 Feb 24, 2026
b76ac90
fix: args in chainlink test
ojasarora77 Feb 24, 2026
195655a
fix: bigInt
ojasarora77 Feb 24, 2026
c800d82
test: buy slippage enforcement checks
ojasarora77 Feb 24, 2026
7cc292c
test: dust amount in ERC4626ExecutionAdapters
ojasarora77 Feb 24, 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
30 changes: 28 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,35 @@ jobs:
echo "## Lint results" >> $GITHUB_STEP_SUMMARY
echo "✅ Passed" >> $GITHUB_STEP_SUMMARY

- name: "Run tests and generate coverage report"
- name: "Run mainnet fork tests"
run:
pnpm test test/crossAsset/ChainlinkPriceAdapter.test.ts test/crossAsset/ERC4626PriceAdapter.test.ts
test/crossAsset/ERC4626ExecutionAdapter.test.ts
env:
RPC_URL: ${{ secrets.RPC_URL }}
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
LP_PRIVATE_KEY: ${{ secrets.LP_PRIVATE_KEY }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
FORK_MAINNET: "true"
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}

- name: "Run unit tests"
run: pnpm test
env:
RPC_URL: ${{ secrets.RPC_URL }}
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
LP_PRIVATE_KEY: ${{ secrets.LP_PRIVATE_KEY }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}

- name: "Generate coverage report"
run: pnpm coverage
env:
RPC_URL: ${{ secrets.RPC_URL }}
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
LP_PRIVATE_KEY: ${{ secrets.LP_PRIVATE_KEY }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
FORK_MAINNET: "true"
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}

- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v5
Expand Down Expand Up @@ -147,7 +174,6 @@ jobs:

find artifacts/contracts -name "*.json" \
! -name "*.dbg.json" \
! -path "*/mocks/*" \
! -path "*/test/*" \
-exec sh -c '
for file do
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ docs/
res/
running_node/
artifacts/
edr-cache/

scripts/*.ts

Expand Down
1 change: 0 additions & 1 deletion .solhintignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# directories
**/artifacts
**/node_modules
contracts/mocks
contracts/test
contracts/sp1-contracts
1 change: 0 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ coverage:

ignore:
- "test/**"
- "**/mocks/**"
- "**/*.t.sol"
- "artifacts/**"
- "audits/**"
Expand Down
47 changes: 37 additions & 10 deletions contracts/LiquidityOrchestrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* - Handling slippage and market execution differences from adapter price estimates via liquidity buffer.
* @custom:security-contact security@orionfinance.ai
*/
contract LiquidityOrchestrator is

Check warning on line 34 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Contract has 22 states declarations but allowed no more than 15
Initializable,
Ownable2StepUpgradeable,
ReentrancyGuardUpgradeable,
Expand Down Expand Up @@ -169,6 +169,7 @@
}

/// @dev Restricts function to only self
/// Used on _executeSell and _executeBuy so they can stay external (required for try/catch)
modifier onlySelf() {
if (msg.sender != address(this)) revert ErrorsLib.NotAuthorized();
_;
Expand Down Expand Up @@ -284,6 +285,7 @@

/// @inheritdoc ILiquidityOrchestrator
function setSlippageTolerance(uint256 _slippageTolerance) external onlyOwner {
if (_slippageTolerance > BASIS_POINTS_FACTOR) revert ErrorsLib.InvalidArguments();
slippageTolerance = _slippageTolerance;
}

Expand Down Expand Up @@ -697,7 +699,7 @@
address token = sellLeg.sellingTokens[i];
if (token == address(underlyingAsset)) continue;
uint256 amount = sellLeg.sellingAmounts[i];
try this._executeSell(token, amount, sellLeg.sellingEstimatedUnderlyingAmounts[i]) {

Check warning on line 702 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Code contains empty blocks
// successful execution, continue.
} catch {
currentPhase = LiquidityUpkeepPhase.StateCommitment;
Expand Down Expand Up @@ -728,7 +730,7 @@
address token = buyLeg.buyingTokens[i];
if (token == address(underlyingAsset)) continue;
uint256 amount = buyLeg.buyingAmounts[i];
try this._executeBuy(token, amount, buyLeg.buyingEstimatedUnderlyingAmounts[i]) {

Check warning on line 733 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Code contains empty blocks
// successful execution, continue.
} catch {
currentPhase = LiquidityUpkeepPhase.StateCommitment;
Expand All @@ -754,19 +756,43 @@
}
}

/// @notice Calculate maximum amount with slippage applied
/// @param estimatedAmount The estimated amount
/// @return The maximum amount with slippage applied
function _calculateMaxWithSlippage(uint256 estimatedAmount) internal view returns (uint256) {
return estimatedAmount.mulDiv(BASIS_POINTS_FACTOR + slippageTolerance, BASIS_POINTS_FACTOR);
}

/// @notice Calculate minimum amount with slippage applied
/// @param estimatedAmount The estimated amount
/// @return The minimum amount with slippage applied
function _calculateMinWithSlippage(uint256 estimatedAmount) internal view returns (uint256) {
return estimatedAmount.mulDiv(BASIS_POINTS_FACTOR - slippageTolerance, BASIS_POINTS_FACTOR);
}

/// @notice Executes a sell order
/// @param asset The asset to sell
/// @param sharesAmount The amount of shares to sell
/// @param estimatedUnderlyingAmount The estimated underlying amount to receive
function _executeSell(address asset, uint256 sharesAmount, uint256 estimatedUnderlyingAmount) external onlySelf {
function _executeSell(
address asset,
uint256 sharesAmount,
uint256 estimatedUnderlyingAmount
) external onlySelf nonReentrant {
IExecutionAdapter adapter = executionAdapterOf[asset];
if (address(adapter) == address(0)) revert ErrorsLib.AdapterNotSet();

// Approve adapter to spend shares
IERC20(asset).forceApprove(address(adapter), sharesAmount);

// Execute sell through adapter, pull shares from this contract and push underlying assets to it.
uint256 executionUnderlyingAmount = adapter.sell(asset, sharesAmount, estimatedUnderlyingAmount);
uint256 executionUnderlyingAmount = adapter.sell(asset, sharesAmount);

// Validate slippage of trade is within tolerance.
uint256 minUnderlyingAmount = _calculateMinWithSlippage(estimatedUnderlyingAmount);
if (executionUnderlyingAmount < minUnderlyingAmount) {
revert ErrorsLib.SlippageExceeded(asset, executionUnderlyingAmount, minUnderlyingAmount);
}

// Clean up approval
IERC20(asset).forceApprove(address(adapter), 0);
Expand All @@ -778,19 +804,20 @@
/// @param asset The asset to buy
/// @param sharesAmount The amount of shares to buy
/// @param estimatedUnderlyingAmount The estimated underlying amount to spend
/// @dev The adapter handles slippage tolerance internally.
function _executeBuy(address asset, uint256 sharesAmount, uint256 estimatedUnderlyingAmount) external onlySelf {
function _executeBuy(
address asset,
uint256 sharesAmount,
uint256 estimatedUnderlyingAmount
) external onlySelf nonReentrant {
IExecutionAdapter adapter = executionAdapterOf[asset];
if (address(adapter) == address(0)) revert ErrorsLib.AdapterNotSet();

// Approve adapter to spend underlying assets
IERC20(underlyingAsset).forceApprove(
address(adapter),
estimatedUnderlyingAmount.mulDiv(BASIS_POINTS_FACTOR + slippageTolerance, BASIS_POINTS_FACTOR)
);
// Approve adapter to spend underlying assets with slippage tolerance.
// Slippage tolerance is enforced indirectly by capping the approval amount.
IERC20(underlyingAsset).forceApprove(address(adapter), _calculateMaxWithSlippage(estimatedUnderlyingAmount));

// Execute buy through adapter, pull underlying assets from this contract and push shares to it.
uint256 executionUnderlyingAmount = adapter.buy(asset, sharesAmount, estimatedUnderlyingAmount);
uint256 executionUnderlyingAmount = adapter.buy(asset, sharesAmount);

// Clean up approval
IERC20(underlyingAsset).forceApprove(address(adapter), 0);
Expand Down
190 changes: 190 additions & 0 deletions contracts/execution/ERC4626ExecutionAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import { IExecutionAdapter } from "../interfaces/IExecutionAdapter.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ErrorsLib } from "../libraries/ErrorsLib.sol";
import { IOrionConfig } from "../interfaces/IOrionConfig.sol";
import { ILiquidityOrchestrator } from "../interfaces/ILiquidityOrchestrator.sol";

/**
* @title ERC4626ExecutionAdapter
* @notice Execution adapter for ERC-4626 vaults with generic underlying asset.
* @author Orion Finance
* @dev Architecture:
* - Handles same-asset flows: protocolUnderlying=vaultUnderlying → vaultShares
* - Handles cross-asset flows: protocolUnderlying → ExecutionAdapter → vaultUnderlying → vaultShares
*
* @custom:security-contact security@orionfinance.ai
*/
contract ERC4626ExecutionAdapter is IExecutionAdapter {
using SafeERC20 for IERC20;

/// @notice Orion protocol configuration contract
IOrionConfig public immutable CONFIG;

/// @notice Protocol underlying asset
IERC20 public immutable UNDERLYING_ASSET;

/// @notice Liquidity orchestrator contract
ILiquidityOrchestrator public immutable LIQUIDITY_ORCHESTRATOR;

/**
* @notice Constructor
* @param configAddress OrionConfig contract address
*/
constructor(address configAddress) {
if (configAddress == address(0)) revert ErrorsLib.ZeroAddress();

CONFIG = IOrionConfig(configAddress);
UNDERLYING_ASSET = IERC20(CONFIG.underlyingAsset());
LIQUIDITY_ORCHESTRATOR = ILiquidityOrchestrator(CONFIG.liquidityOrchestrator());

if (address(LIQUIDITY_ORCHESTRATOR) == address(0)) revert ErrorsLib.ZeroAddress();
}

/// @notice Internal validation function that performs compatibility checks
/// @param asset The address of the asset to validate
function _validateExecutionAdapter(address asset) internal view {
// 1. Verify asset implements IERC4626
try IERC4626(asset).asset() returns (address vaultUnderlying) {
// 2. Verify registered vault decimals match config decimals
try IERC20Metadata(asset).decimals() returns (uint8 vaultDecimals) {
if (vaultDecimals != CONFIG.getTokenDecimals(asset)) {
revert ErrorsLib.InvalidAdapter(asset);
}
} catch {
revert ErrorsLib.InvalidAdapter(asset);
}

// 3. Verify underlying vault decimals match config decimals
// (vault underlying must be whitelisted in config)
try IERC20Metadata(vaultUnderlying).decimals() returns (uint8 vaultUnderlyingDecimals) {
if (vaultUnderlyingDecimals != CONFIG.getTokenDecimals(vaultUnderlying)) {
revert ErrorsLib.InvalidAdapter(asset);
}
} catch {
revert ErrorsLib.InvalidAdapter(asset);
}
} catch {
revert ErrorsLib.InvalidAdapter(asset);
}
}

/// @inheritdoc IExecutionAdapter
function validateExecutionAdapter(address asset) external view override {
_validateExecutionAdapter(asset);
}

/// @inheritdoc IExecutionAdapter
function previewBuy(address vaultAsset, uint256 sharesAmount) external returns (uint256 underlyingAmount) {
IERC4626 vault = IERC4626(vaultAsset);
address vaultUnderlying = vault.asset();
uint256 vaultUnderlyingNeeded = vault.previewMint(sharesAmount);

if (vaultUnderlying == address(UNDERLYING_ASSET)) {
return vaultUnderlyingNeeded;
}
IExecutionAdapter swapExecutor = IExecutionAdapter(
address(LIQUIDITY_ORCHESTRATOR.executionAdapterOf(vaultUnderlying))
);
return swapExecutor.previewBuy(vaultUnderlying, vaultUnderlyingNeeded);
}

/// @inheritdoc IExecutionAdapter
function sell(
address vaultAsset,
uint256 sharesAmount
) external override returns (uint256 receivedUnderlyingAmount) {
if (sharesAmount == 0) revert ErrorsLib.AmountMustBeGreaterThanZero(vaultAsset);
// Atomically validate order generation assumptions
_validateExecutionAdapter(vaultAsset);

IERC4626 vault = IERC4626(vaultAsset);
address vaultUnderlying = vault.asset();

if (vaultUnderlying == address(UNDERLYING_ASSET)) {
receivedUnderlyingAmount = vault.redeem(sharesAmount, msg.sender, msg.sender);
} else {
uint256 receivedVaultUnderlyingAmount = vault.redeem(sharesAmount, address(this), msg.sender);

IExecutionAdapter swapExecutor = IExecutionAdapter(
address(LIQUIDITY_ORCHESTRATOR.executionAdapterOf(vaultUnderlying))
);

IERC20(vaultUnderlying).forceApprove(address(swapExecutor), receivedVaultUnderlyingAmount);

receivedUnderlyingAmount = swapExecutor.sell(vaultUnderlying, receivedVaultUnderlyingAmount);

// Clean up approval
IERC20(vaultUnderlying).forceApprove(address(swapExecutor), 0);

UNDERLYING_ASSET.safeTransfer(msg.sender, receivedUnderlyingAmount);
}
}

/// @inheritdoc IExecutionAdapter
function buy(address vaultAsset, uint256 sharesAmount) external override returns (uint256 spentUnderlyingAmount) {

Check warning on line 130 in contracts/execution/ERC4626ExecutionAdapter.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Function body contains 58 lines but allowed no more than 50 lines
if (sharesAmount == 0) revert ErrorsLib.AmountMustBeGreaterThanZero(vaultAsset);
_validateExecutionAdapter(vaultAsset);

IERC4626 vault = IERC4626(vaultAsset);
address vaultUnderlying = vault.asset();
uint256 vaultUnderlyingNeeded = vault.previewMint(sharesAmount);

uint256 underlyingNeeded;
if (vaultUnderlying == address(UNDERLYING_ASSET)) {
underlyingNeeded = vaultUnderlyingNeeded;
} else {
underlyingNeeded = this.previewBuy(vaultAsset, sharesAmount);
}

// Pull previewed amount from the caller.
UNDERLYING_ASSET.safeTransferFrom(msg.sender, address(this), underlyingNeeded);

if (vaultUnderlying == address(UNDERLYING_ASSET)) {
// Approve vault to spend underlying assets
UNDERLYING_ASSET.forceApprove(vaultAsset, underlyingNeeded);

// Mint exact shares. Vault will pull the required underlying amount
// This guarantees sharesAmount shares are minted.
spentUnderlyingAmount = vault.mint(sharesAmount, address(this));
// Some ERC4626 implementations may leave dust in the adapter;
// we accept that, as target shares are minted.

// Clean up approval
UNDERLYING_ASSET.forceApprove(vaultAsset, 0);
} else {
IExecutionAdapter swapExecutor = IExecutionAdapter(
address(LIQUIDITY_ORCHESTRATOR.executionAdapterOf(vaultUnderlying))
);
// Approve swap executor to spend underlying assets
UNDERLYING_ASSET.forceApprove(address(swapExecutor), underlyingNeeded);

spentUnderlyingAmount = swapExecutor.buy(vaultUnderlying, vaultUnderlyingNeeded);
// Swap Executor may leave dust in the adapter, we accept that.

// Clean up approval
UNDERLYING_ASSET.forceApprove(address(swapExecutor), 0);

// Approve vault to spend vault underlying assets
IERC20(vaultUnderlying).forceApprove(vaultAsset, vaultUnderlyingNeeded);

// Mint exact shares. Vault will pull the required underlying amount
// This guarantees sharesAmount shares are minted.
// slither-disable-next-line unused-return
vault.mint(sharesAmount, address(this));
// Some ERC4626 implementations may leave dust in the adapter;
// we accept that, as target shares are minted.

// Clean up approval
IERC20(vaultUnderlying).forceApprove(vaultAsset, 0);
}

// Push all minted shares to the caller (LO)
IERC20(vaultAsset).safeTransfer(msg.sender, sharesAmount);
}
}
Loading
Loading