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
21,527 changes: 21,527 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions packages/contracts/.openzeppelin/bsc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3207,6 +3207,16 @@
"types": {},
"namespaces": {}
}
},
"a90f76e2f25b5ad575c7f97f22eb3150a8ab109392b5b76c2ea15aa771948143": {
"address": "0x5896B1168C2558886Fa590a5F57667b88a012874",
"txHash": "0xbf5c444627d7e0487a8e7fdd843ac8140a302044efb3690121c56c3ea0ce7c20",
"layout": {
"solcVersion": "0.8.24",
"storage": [],
"types": {},
"namespaces": {}
}
}
}
}
10 changes: 10 additions & 0 deletions packages/contracts/.openzeppelin/unknown-33139.json
Original file line number Diff line number Diff line change
Expand Up @@ -3718,6 +3718,16 @@
"types": {},
"namespaces": {}
}
},
"a4bb7e99ebb0c50f0e36888069796552f90b193dc781b84ac7ef730098ad4122": {
"address": "0x838F8e08C723CA4323cacD07D57777B27eAB3bD4",
"txHash": "0x6368dded337ddd035e61c76b40e6404f62dbeff01144fcd54ef8a8c3589700f8",
"layout": {
"solcVersion": "0.8.24",
"storage": [],
"types": {},
"namespaces": {}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "./BorrowSwap_G3.sol";

contract BorrowSwap is BorrowSwap_G3 {
import "./BorrowSwap_G4.sol";

contract BorrowSwap is BorrowSwap_G4 {
constructor(
address _tellerV2,
address _swapRouter,
address _quoter
address _tellerV2,
address _swapAdapter
)
BorrowSwap_G3(
_tellerV2,
_swapRouter,
_quoter
BorrowSwap_G4(
_tellerV2,
_swapAdapter
)
{}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "../../../interfaces/ITellerV2.sol";
import "../../../interfaces/ITellerV2Storage.sol";
import "../../../interfaces/IMarketRegistry.sol";
import "../../../interfaces/ILenderCommitmentForwarder.sol";
import "../../../interfaces/ISmartCommitmentForwarder.sol";
import "../../../interfaces/ISwapAdapter.sol";

import "../../../libraries/uniswap/periphery/libraries/TransferHelper.sol";

/// @title BorrowSwap_G4
/// @notice Borrow from a lending pool and immediately swap the proceeds via a DEX.
/// Uses the ISwapAdapter pattern to support multiple DEX backends
/// (Uniswap V3, Algebra/Camelot V3, etc.) without changing this contract.
/// Swap paths are passed as arbitrary `bytes`, consistent with how
/// IPriceAdapter handles oracle routes in PoolsV3.
contract BorrowSwap_G4 {
using AddressUpgradeable for address;

/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ITellerV2 public immutable TELLER_V2;
ISwapAdapter public immutable SWAP_ADAPTER;

event BorrowSwapComplete(
address borrower,
uint256 loanId,
address token0,
uint256 amountIn,
uint256 amountOut
);

struct AcceptCommitmentArgs {
uint256 commitmentId;
address smartCommitmentAddress;
uint256 principalAmount;
uint256 collateralAmount;
uint256 collateralTokenId;
address collateralTokenAddress;
uint16 interestRate;
uint32 loanDuration;
bytes32[] merkleProof;
}

struct SwapArgs {
bytes path; // DEX-encoded swap path (e.g. tokenIn ++ fee ++ tokenOut for Uniswap V3)
uint160 amountOutMinimum;
}

/// @param _tellerV2 The address of the TellerV2 contract.
/// @param _swapAdapter The address of an ISwapAdapter (UniswapV3SwapAdapter, AlgebraSwapAdapter, etc.)
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address _tellerV2, address _swapAdapter) {
TELLER_V2 = ITellerV2(_tellerV2);
SWAP_ADAPTER = ISwapAdapter(_swapAdapter);
}

/// @notice Borrow funds from a lender commitment and immediately swap them.
/// @param _lenderCommitmentForwarder The commitment forwarder contract
/// @param _principalToken The token being borrowed (input to the swap)
/// @param _additionalInputAmount Extra principal tokens the borrower adds to the swap
/// @param _swapArgs Swap parameters (DEX-encoded path, slippage)
/// @param _acceptCommitmentArgs Loan commitment parameters
function borrowSwap(
address _lenderCommitmentForwarder,
address _principalToken,
uint256 _additionalInputAmount,
SwapArgs calldata _swapArgs,
AcceptCommitmentArgs calldata _acceptCommitmentArgs
) external {
address borrower = msg.sender;

if (_additionalInputAmount > 0) {
TransferHelper.safeTransferFrom(
_principalToken, borrower, address(this), _additionalInputAmount
);
}

// Accept commitment — locks collateral, receives principal to this contract
(uint256 newLoanId, uint256 acceptCommitmentAmount) = _acceptCommitment(
_lenderCommitmentForwarder,
borrower,
_principalToken,
_acceptCommitmentArgs
);

uint256 totalInputAmount = acceptCommitmentAmount + _additionalInputAmount;

// Approve the adapter, let it pull tokens and execute the swap
TransferHelper.safeApprove(_principalToken, address(SWAP_ADAPTER), totalInputAmount);

uint256 swapAmountOut = SWAP_ADAPTER.swap(
_swapArgs.path,
totalInputAmount,
_swapArgs.amountOutMinimum,
borrower
);

emit BorrowSwapComplete(
borrower,
newLoanId,
_principalToken,
totalInputAmount,
swapAmountOut
);
}

/// @notice Quote the expected output for an exact-input swap.
/// @dev Not view — DEX quoters use state-reverting simulation internally.
/// @param path DEX-encoded swap path
/// @param amountIn Amount of input token to quote
function quoteExactInput(
bytes calldata path,
uint256 amountIn
) external returns (uint256 amountOut) {
return SWAP_ADAPTER.quote(path, amountIn);
}

// =========================================================================
// Commitment acceptance (unchanged from G3)
// =========================================================================

function _acceptCommitment(
address lenderCommitmentForwarder,
address borrower,
address principalToken,
AcceptCommitmentArgs memory _commitmentArgs
)
internal
virtual
returns (uint256 bidId_, uint256 acceptCommitmentAmount_)
{
uint256 fundsBeforeAcceptCommitment = IERC20Upgradeable(principalToken)
.balanceOf(address(this));

if (_commitmentArgs.smartCommitmentAddress != address(0)) {
bytes memory responseData = address(lenderCommitmentForwarder)
.functionCall(
abi.encodePacked(
abi.encodeWithSelector(
ISmartCommitmentForwarder
.acceptSmartCommitmentWithRecipient
.selector,
_commitmentArgs.smartCommitmentAddress,
_commitmentArgs.principalAmount,
_commitmentArgs.collateralAmount,
_commitmentArgs.collateralTokenId,
_commitmentArgs.collateralTokenAddress,
address(this),
_commitmentArgs.interestRate,
_commitmentArgs.loanDuration
),
borrower
)
);

(bidId_) = abi.decode(responseData, (uint256));
} else {
bool usingMerkleProof = _commitmentArgs.merkleProof.length > 0;

if (usingMerkleProof) {
bytes memory responseData = address(lenderCommitmentForwarder)
.functionCall(
abi.encodePacked(
abi.encodeWithSelector(
ILenderCommitmentForwarder
.acceptCommitmentWithRecipientAndProof
.selector,
_commitmentArgs.commitmentId,
_commitmentArgs.principalAmount,
_commitmentArgs.collateralAmount,
_commitmentArgs.collateralTokenId,
_commitmentArgs.collateralTokenAddress,
address(this),
_commitmentArgs.interestRate,
_commitmentArgs.loanDuration,
_commitmentArgs.merkleProof
),
borrower
)
);

(bidId_) = abi.decode(responseData, (uint256));
} else {
bytes memory responseData = address(lenderCommitmentForwarder)
.functionCall(
abi.encodePacked(
abi.encodeWithSelector(
ILenderCommitmentForwarder
.acceptCommitmentWithRecipient
.selector,
_commitmentArgs.commitmentId,
_commitmentArgs.principalAmount,
_commitmentArgs.collateralAmount,
_commitmentArgs.collateralTokenId,
_commitmentArgs.collateralTokenAddress,
address(this),
_commitmentArgs.interestRate,
_commitmentArgs.loanDuration
),
borrower
)
);

(bidId_) = abi.decode(responseData, (uint256));
}
}

uint256 fundsAfterAcceptCommitment = IERC20Upgradeable(principalToken)
.balanceOf(address(this));
acceptCommitmentAmount_ =
fundsAfterAcceptCommitment -
fundsBeforeAcceptCommitment;
}

// =========================================================================
// Market helpers (unchanged from G3)
// =========================================================================

function getMarketIdForCommitment(
address _lenderCommitmentForwarder,
uint256 _commitmentId
) external view returns (uint256) {
return ILenderCommitmentForwarder(_lenderCommitmentForwarder)
.getCommitmentMarketId(_commitmentId);
}

function getMarketFeePct(uint256 _marketId) external view returns (uint16) {
address _marketRegistryAddress = ITellerV2Storage(address(TELLER_V2))
.marketRegistry();
return IMarketRegistry(_marketRegistryAddress).getMarketplaceFee(_marketId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../../../../interfaces/ISwapAdapter.sol";
import "../../../../libraries/uniswap/periphery/libraries/TransferHelper.sol";

/// @notice Algebra (Camelot V3) swap router — same ExactInputParams struct as Uniswap V3
/// but path encoding has NO fee bytes: just (tokenIn ++ tokenOut) per hop.
interface IAlgebraSwapRouter {
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}

function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
}

/// @notice Algebra quoter — uses quoteExactInput with fee-less paths,
/// and quoteExactInputSingle for single-hop without path encoding.
interface IAlgebraQuoter {
function quoteExactInput(bytes memory path, uint256 amountIn)
external
returns (uint256 amountOut, uint16[] memory fees);

function quoteExactInputSingle(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint160 limitSqrtPrice
) external returns (uint256 amountOut, uint16 fee);
}

/// @title AlgebraSwapAdapter
/// @notice ISwapAdapter implementation for Algebra-based DEXes (Camelot V3, etc.).
/// Caller provides a pre-encoded Algebra path: (tokenIn ++ tokenOut) per hop (no fee bytes).
contract AlgebraSwapAdapter is ISwapAdapter {

IAlgebraSwapRouter public immutable SWAP_ROUTER;
IAlgebraQuoter public immutable QUOTER;

/// @param _swapRouter Algebra SwapRouter address
/// @param _quoter Algebra Quoter address
constructor(address _swapRouter, address _quoter) {
SWAP_ROUTER = IAlgebraSwapRouter(_swapRouter);
QUOTER = IAlgebraQuoter(_quoter);
}

/// @inheritdoc ISwapAdapter
function swap(
bytes calldata path,
uint256 amountIn,
uint256 amountOutMinimum,
address recipient
) external override returns (uint256 amountOut) {
// Extract tokenIn from the first 20 bytes of the path
address tokenIn;
assembly { tokenIn := shr(96, calldataload(path.offset)) }

TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn);
TransferHelper.safeApprove(tokenIn, address(SWAP_ROUTER), amountIn);

IAlgebraSwapRouter.ExactInputParams memory params = IAlgebraSwapRouter.ExactInputParams({
path: path,
recipient: recipient,
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: amountOutMinimum
});

amountOut = SWAP_ROUTER.exactInput(params);
}

/// @inheritdoc ISwapAdapter
function quote(
bytes calldata path,
uint256 amountIn
) external override returns (uint256 amountOut) {
(amountOut, ) = QUOTER.quoteExactInput(path, amountIn);
}
}
Loading
Loading