Description:
Decentralized Finance (DeFi) protocol contract providing Swap, Factory functionality.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"TriangleFlashV3_V2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @notice Minimal ERC20 interface
interface IERC20 {
function balanceOf(address) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address dst, uint256 value) external returns (bool);
function transferFrom(address src, address dst, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function decimals() external view returns (uint8);
function symbol() external view returns (string memory);
}
/// @notice Uniswap V3 Router (Periphery) interface: exactInput(path,…)
interface ISwapRouterV3 {
struct ExactInputParams {
bytes path; // tokenIn | fee | tokenMid | fee | tokenOut ...
address recipient; // receiver of final output
uint256 deadline; // timestamp
uint256 amountIn; // amount of tokenIn to swap
uint256 amountOutMinimum; // slippage guard
}
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
}
/// @notice Minimal Uniswap V3 pool flash interface
interface IUniswapV3Pool {
function token0() external view returns (address);
function token1() external view returns (address);
/// @dev Borrow tokens; must repay + fees in callback
function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
}
/// @notice Uniswap V3 flash callback interface
interface IUniswapV3FlashCallback {
/// @dev Called by the pool after sending tokens; must repay amount + fee
function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external;
}
/// @notice Ownable (lightweight)
abstract contract Ownable {
event OwnershipTransferred(address indexed prev, address indexed next);
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
constructor() {owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);}
function transferOwnership(address next) external onlyOwner {
require(next != address(0), "zero");
emit OwnershipTransferred(owner, next);
owner = next;
}
}
/// @title TriangleFlashV3
/// @notice Flash-borrow a token (e.g., USDC), swap through a V3 path (USDC->A->B->USDC),
/// and repay pool within the same transaction. Reverts if output < amount+fee.
contract TriangleFlashV3 is IUniswapV3FlashCallback, Ownable {
error InvalidPool();
error InsufficientProfit(uint256 out, uint256 owed);
error ApproveFailed();
error RouterCallFailed();
error InvalidPathLength();
error PathMustStartWithBorrowedToken();
error PathMustEndWithBorrowedToken();
/// @notice Immutable Uniswap V3 router
ISwapRouterV3 public immutable router;
/// @dev Data we pass to the callback to validate and execute
struct FlashData {
address pool; // pool that called us (for validation)
address tokenBorrow; // token we borrowed (expected to be path start/end)
uint256 amountBorrow; // amount borrowed
uint256 minOut; // slippage minimum for the full path
bytes path; // encoded V3 path (must start AND end with tokenBorrow)
address profitTo; // where leftover profit is sent after repay (owner by default)
}
event TriangleExecuted(address indexed pool, address indexed tokenBorrow, uint256 amountIn, uint256 amountOut, uint256 amountOwed);
constructor(address _router) {
require(_router != address(0), "router=0");
router = ISwapRouterV3(_router);
}
// ========= User entry =========
/// @notice Execute a flash triangle in one tx.
/// @param pool Uniswap V3 pool to flash from (must contain tokenBorrow as token0 or token1)
/// @param tokenBorrow The token to borrow (e.g., USDC). path MUST start & end with this token.
/// @param amountBorrow Amount to flash-borrow (in tokenBorrow units)
/// @param path Encoded V3 path (tokenBorrow|fee|A|fee|B|fee|tokenBorrow)
/// @param minOut Minimum acceptable amountOut from router (slippage control)
/// @param profitTo Address to receive profit surplus after repay (if any)
function executeTriangleFlash(
address pool,
address tokenBorrow,
uint256 amountBorrow,
bytes calldata path,
uint256 minOut,
address profitTo
) external onlyOwner {
if (pool == address(0) || tokenBorrow == address(0)) revert InvalidPool();
if (profitTo == address(0)) profitTo = owner;
// Pool must contain tokenBorrow on either side
address t0 = IUniswapV3Pool(pool).token0();
address t1 = IUniswapV3Pool(pool).token1();
require(t0 == tokenBorrow || t1 == tokenBorrow, "pool !contains tokenBorrow");
// Path shape: 20 + 23*k (k >= 1) → at least 40 bytes total
uint256 len = path.length;
if (len < 40 || (len - 20) % 23 != 0) revert InvalidPathLength();
// Decode first & last token DIRECTLY from calldata
address start;
address end;
assembly {
// first 20 bytes at path.offset
start := shr(96, calldataload(path.offset))
// last 20 bytes at path.offset + len - 20
end := shr(96, calldataload(add(path.offset, sub(len, 20))))
}
if (start != tokenBorrow) revert PathMustStartWithBorrowedToken();
// keep this only if you truly require round-trip back to tokenBorrow:
if (end != tokenBorrow) revert PathMustEndWithBorrowedToken();
// pack callback data (calldata → memory is OK; we already validated above)
FlashData memory data = FlashData({
pool: pool,
tokenBorrow: tokenBorrow,
amountBorrow: amountBorrow,
minOut: minOut,
path: path,
profitTo: profitTo
});
// Borrow on the correct side
if (t0 == tokenBorrow) {
IUniswapV3Pool(pool).flash(address(this), amountBorrow, 0, abi.encode(data));
} else {
IUniswapV3Pool(pool).flash(address(this), 0, amountBorrow, abi.encode(data));
}
}
// ========= Flash callback =========
/// @notice Called by the pool after sending borrowed tokens. We must:
/// 1) swap through Router using the provided path (exactInput)
/// 2) repay amountBorrow + fee to the pool
/// 3) keep any profit (if any) and send to profitTo
error NotPool();
error SwapFailed(bytes reason);
event FlashStart(address pool, address token, uint256 amount);
event TriangleExecuted(address indexed pool, address indexed tokenBorrow, uint256 amountIn, uint256 amountOut, uint256 amountOwed, uint256 profit);
function uniswapV3FlashCallback(
uint256 fee0,
uint256 fee1,
bytes calldata dataBytes
) external {
FlashData memory data = abi.decode(dataBytes, (FlashData));
address t0 = IUniswapV3Pool(data.pool).token0();
address t1 = IUniswapV3Pool(data.pool).token1();
bool borrowedIsToken0 = (t0 == data.tokenBorrow);
uint256 fee = borrowedIsToken0 ? fee0 : fee1;
uint256 amountOwed = data.amountBorrow + fee;
// verify caller is the pool we flashed from
if (msg.sender != data.pool) revert NotPool();
emit FlashStart(data.pool, data.tokenBorrow, data.amountBorrow);
// Approve router to spend the borrowed token
if (!IERC20(data.tokenBorrow).approve(address(router), data.amountBorrow)) revert ApproveFailed();
// Execute multi-hop swap: tokenBorrow -> A -> B -> tokenBorrow
uint256 amountOut = router.exactInput(
ISwapRouterV3.ExactInputParams({
path: data.path,
recipient: address(this),
deadline: block.timestamp + 120,
amountIn: data.amountBorrow,
amountOutMinimum: data.minOut
})
);
if (amountOut < amountOwed) revert InsufficientProfit(amountOut, amountOwed);
IERC20(data.tokenBorrow).transfer(msg.sender, amountOwed);
uint256 bal = IERC20(data.tokenBorrow).balanceOf(address(this));
emit TriangleExecuted(data.pool, data.tokenBorrow, data.amountBorrow, amountOut, amountOwed);
}
// ========= Admin helpers =========
/// @notice Rescue any ERC20 stuck on the contract
function sweep(address token, address to, uint256 amount) external onlyOwner {
require(IERC20(token).transfer(to, amount), "sweep failed");
}
}"
}
},
"settings": {
"optimizer": {
"enabled": false,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": []
}
}}
Submitted on: 2025-10-30 21:27:24
Comments
Log in to comment.
No comments yet.