Hiện tại phiên bản Uniswap V3 hỗ trợ hai kiểu giúp bạn cần thực hiện một khoản vay nhanh (Chi tiết bạn xem mã nguồn UniswapV3Pool.sol)
- Flashloan: Hỗ trợ vay trực tiếp một cách tường minh thông qua hàm flash()
- Flashswap: Hỗ trợ vay gián tiếp thông qua cơ chế swap trong hàm swap()
Có thể bạn quan tâm: Hướng dẫn triển khai Flashloan / Flashswap sử dụng MCL Flashloan, CREAM Finance Flashloan và PancakeSwap trên Binance Smart Chain BSC Testnet
Bây giờ chúng ta sẽ thử sử dụng cả hai tính năng này trên Uniswap V3 qua mạng lưới Goerli Testnet.
Mục lục
Một số điều kiện cần chuẩn bị trước
Trước khi bắt đầu bạn phải cài đặt MetaMask, tạo một account và xin ít gETH làm phí mạng lưới thông qua một trong các faucet sau:
- Goerli Faucet: Điều kiện phải tạo tài khoản bên Alchemy và yêu cầu ví phải có trên 0.001 ETH bên Ethereum Mainnet.
- Coinbase Faucet: Yêu cầu phải cài Wallet của Coinbase.
- Quicknode Faucet: Yêu cầu ví phải có trên 0.001 ETH trên Ethereum Mainnet
Bạn phải biết một số thông tin liên quan tới Uniswap V3 trên Goerli Testnet. Chi tiết thông tin bạn xem tại Uniswap Contract Deployments:
- UniswapV3Factory: 0x1F98431c8aD98523631AE4a59f267346ea31F984
- Multicall2: 0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696
- Quoter: 0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6 (Các hàm chỉ trả về amountIn hoặc amountOut)
- SwapRouter: 0xE592427A0AEce92De3Edee1F18E0157C05861564 (Chỉ dùng cho Uniswap V3)
- V3Migrator: 0xA5644E29708357803b5A882D272c41cC0dF92B34
- QuoterV2: 0x61fFE014bA17989E743c5F6cB21bF9697530B21e (Các hàm ngoài trả về amountIn/amountOut, còn trả về thêm thông tin như sqrtPriceX96AfterList, initializedTicksCrossedList, gasEstimate)
- SwapRouter02: 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 (Dùng cho cả Uniswap V2 và Uniswap V3)
- Permit2: 0x000000000022d473030f116ddee9f6b43ac78ba3
Hiện tại ta quan tâm nhất SwapRouter, một giao diện giúp swap các tài sản trên Uniswap V3 dễ dàng hơn. Xem các giao dịch ta sẽ lọc một số pool sau:
- UNI-WETH-500: 0x07A4f63f643fE39261140DF5E613b9469eccEC86
- UNI-WETH-3000: 0x4d1892f15B03db24b55E73F9801826a56d6f0755
- UNI-WETH-10000: 0xd2cD8bba98B51D18bA7C9bd2782E460aab5BBDe4
- WETH-ZETA-500: 0x1324c9366890a67df74c4cf96454b4f3b4d36a8c
- WETH-ZETA-3000: 0xAeCC9Ad3E70753092453D59FeC11113c32504AC3
- WETH-ZETA-10000: 0x3D807E94BF75ddEfbed21C9f3DeC1ab80c26F28D
- rETH-WETH-500: 0xD052E80d8dA5A5CbE690eB99D1Cf205da8E6c16f
- rETH-WETH-3000: 0xe90d9a3e765a221bc1a697a1a3b0bb2e8e8c5e78
- rETH-WETH-10000: 0x7e4eaA3d7684d5A49e1cf31D09875f8877Ae4Dd2
- rETH-UNI-10000: 0x19B026b4012354B16507cA4EfdC887370007751F
Viết contract sử dụng Flashloan trên Uniswap V3
Thực hiện một Flashloan đơn giản
Đầu tiên bạn nên tham khảo một ví dụ tại Uniswap V3 Flash Loan. Ngoài ra bạn có thể tham khảo bài viết Tutorial of Flash Swaps of Uniswap V3 và mã nguồn mở trên flash-swap-example.
Sau khi hiểu cơ chế rùi bạn có thể sửa lại theo nghiệp vụ mà bạn mong muốn. Ở đây tôi viết contract thực hiện các thao tác như sau:
- B1: Vay WETH ở pool rETH-WETH-10000
- B2: Dùng WETH lại thực hiện swap WETH-UNI-WETH qua các pool sau: [UNI-WETH-500, UNI-WETH-3000] (Tham khảo ví dụ swap trên Uniswap V3 Swap Examples)
- B3: Trả lại WETH vừa vay đồng thời chuyển lợi nhuận sang 1 ví khác.
Để đơn giản và mọi người đọc dễ hiểu, tôi sẽ fix code một số địa chỉ. Dưới là mã code của UniswapV3Flashloan.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IERC20 {
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
}
interface IUniswapV3Pool {
function flash(
address recipient,
uint amount0,
uint amount1,
bytes calldata data
) external;
}
interface ISwapRouter {
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
}
contract UniswapV3Flash {
IERC20 private constant WETH = IERC20(0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6);
address private constant UNI = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
address private constant borrowPool = 0x7e4eaA3d7684d5A49e1cf31D09875f8877Ae4Dd2;
address private constant router = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
function flashloan(uint amount, address recipient) external {
bytes memory data = abi.encode(amount, recipient);
IUniswapV3Pool(borrowPool).flash(address(this), 0, amount, data);
}
function uniswapV3FlashCallback(uint /*fee0*/, uint fee1, bytes calldata data) external {
require(msg.sender == borrowPool, "Not authorized");
// Decode input params
(uint amount, address recipient) = abi.decode(data, (uint, address));
// Check permission
if (WETH.allowance(address(this), router)<amount) {
WETH.approve(router, type(uint).max);
}
// Swap
ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
path: abi.encodePacked(address(WETH), uint24(500), UNI, uint24(3000), address(WETH)),
recipient: address(this),
deadline: block.timestamp,
amountIn: amount,
amountOutMinimum: 1
});
ISwapRouter(router).exactInput(params);
// Repay borrow
WETH.transfer(borrowPool, amount + fee1);
// Transfer profit to recipient
uint profit = WETH.balanceOf(address(this));
WETH.transfer(recipient, profit);
}
}
Triển khai ta được contract 0x22E925722261c0aECf15aEd2A0a381E8E66352eB. Thông thường dữ liệu amountOut thường ít hơn amountIn nên để chắc chắn giao dịch thành công, tôi chuyển trước vào contract 0.0001 WETH. Bây giờ chúng ta thực hiện vay 0.001 WETH và thực hiện swap bằng cách gọi hàm:
flashloan(1000000000000000, 0x26c51c000abDF0b3Ff54583606F2C839F6Ca3B96)
Ta được giao dịch thành công: 0x76095464ea4778fcf48bc6206e13a98d4997679d2b1d7fab31555bca9c9abbd1
Ta gọi hàm flashloan(1000000000000000, 0x26c51c000abDF0b3Ff54583606F2C839F6Ca3B96) với giá trị là 0.001
Kết hợp Flashloan và Multicall3
Thực tế nhiều khi bạn không chỉ phát hiện ra 1 cơ hội mà có thể phải hiện ra nhiều cơ hội 1 lúc. Lúc này ta muốn thực hiện đồng thời nhiều lệnh flashloan, cho dù 1 lệnh bất kỳ lỗi thì vẫn không ảnh hưởng tới các lệnh khác.
Trong trường hợp này chúng ta sẽ sử dụng cơ chế của Multicall3. Tôi đã tìm hiểu source code của Multicall3, đã hiểu nó và sử dụng nó vào trường hợp này.
Đây là code Smart Contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
}
interface IUniswapV3Pool {
function flash(
address recipient,
uint amount0,
uint amount1,
bytes calldata data
) external;
}
interface ISwapRouter {
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
}
contract ArbiMulticall {
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
function aggregate3(Call3[] memory calls) internal returns (Result[] memory returnData) {
uint256 length = calls.length;
returnData = new Result[](length);
Call3 memory calli;
for (uint256 i = 0; i < length;) {
Result memory result = returnData[i];
calli = calls[i];
(result.success, result.returnData) = calli.target.call(calli.callData);
assembly {
// Revert if the call fails and failure is not allowed
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
// set data offset
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
// set length of revert string
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
revert(0x00, 0x64)
}
}
unchecked { ++i; }
}
}
}
contract UniswapV3Flash is ArbiMulticall {
IERC20 private constant WETH = IERC20(0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6);
address private constant UNI = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
address private constant borrowPool = 0x7e4eaA3d7684d5A49e1cf31D09875f8877Ae4Dd2;
address private constant router = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
function flashloan(uint amount, address recipient) external {
bytes memory data = abi.encode(amount, recipient);
IUniswapV3Pool(borrowPool).flash(address(this), 0, amount, data);
}
function uniswapV3FlashCallback(uint /*fee0*/, uint fee1, bytes calldata data) external {
require(msg.sender == borrowPool, "Not authorized");
// Decode input params
(uint amount, address recipient) = abi.decode(data, (uint, address));
// Check permission
if (WETH.allowance(address(this), router)<amount) {
WETH.approve(router, type(uint).max);
}
// Swap
ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
path: abi.encodePacked(address(WETH), uint24(500), UNI, uint24(3000), address(WETH)),
recipient: address(this),
deadline: block.timestamp,
amountIn: amount,
amountOutMinimum: 1
});
ISwapRouter(router).exactInput(params);
// Repay borrow
WETH.transfer(borrowPool, amount + fee1);
// Transfer profit to recipient
uint profit = WETH.balanceOf(address(this));
WETH.transfer(recipient, profit);
}
function testCalls(uint amount, address recipient) external {
Call3[] memory call3 = new Call3[](5);
bytes memory data = abi.encodeWithSelector(this.flashloan.selector, amount, recipient);
for (uint256 i; i<5; i++) {
call3[i].target = address(this);
call3[i].allowFailure = true;
call3[i].callData = data;
}
aggregate3(call3);
}
}
Về cơ bản giống với Smart Contract ở phần trước, chỉ cài đặt thêm phần ArbiMulticall, và thêm hàm testCalls() để chạy liên tiếp 5 lần flashloan. Sau khi triển khai ta có địa chỉ sau: 0x4049bdfa494bB9440af5B1c6c7e68252cE20D1fD
Bây giờ ta thực hiện gọi hàm testCalls() để test:
- Lần 1 – 0.001 WETH: Chúng ta gọi hàm testCalls(1000000000000000, 0x26c51c000abDF0b3Ff54583606F2C839F6Ca3B96) => Giao dịch 0x35f2c7c7f7b5ab53174b4aee8ddd49258ba2170083af700d74a4d9f829bdc4d4 => Theo thông tin thì có 3 giao dịch nhỏ thành công trên tổng 5 giao dịch nhỏ. Sở dĩ 2 giao dịch nhỏ sau không thực hiện được do không đủ Gas.
- Lần 2 – 0.01 WETH: Chúng ta gọi hàm testCalls(10000000000000000, 0x26c51c000abDF0b3Ff54583606F2C839F6Ca3B96) và nhớ edit để tăng Gas Limit lên => Giao dịch 0x07be9373a7282d20b3ec30f24379fa86a58fbe5e2a6c94f5c8f29c0ec6546822 => Tất cả đều thành công.
- Lần 3 – 0.1 WETH: Chúng ta gọi hàm testCalls(100000000000000000, 0x26c51c000abDF0b3Ff54583606F2C839F6Ca3B96) và nhớ edit để tăng Gas Limit lên=> Giao dịch 0x7b256cd99ff85f1b8bc6ac0f4b30dcc1e84b0bd66915c6a1f171162c47f8ede8 => Tất cả các giao dịch nhỏ đều FAIL nhưng giao dịch tổng thể vẫn thành công.
Viết contract sử dụng Flashswap trên Uniswap V3
Hiểu về Flashswap trên Uniswap V3
Uniswap V3 cũng hỗ trợ cơ chế gọi callback như trong Uniswap V2, chi tiết bạn có thể xem hàm swap() trong UniswapV3Pool.sol
Flashswap có nhiều lợi thế trong một số trường hợp nhất định. Vì thế không phải ngẫu nhiên mà Uniswap hỗ trợ cả Flashloan và Flashswap. Bạn có thể tham khảo: Sử dụng Flashswap trên Pancake theo cách đặc biệt.
Trước khi sử dụng Flashswap, chung ta phải hiểu hàm swap():
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes data
) external override noDelegateCall returns (int256 amount0, int256 amount1)
Hàm này nhận các tham số:
- recipient: Địa chỉ người nhận. Tham số này giúp ta linh hoạt được tiền sẽ chuyển đi đâu.
- zeroForOne: Là true nếu swap từ token0 sang token1 và false nếu swap từ token1 sang token0
- amountSpecified: Thông tin amount sử dụng để swap, có thể dương hoặc âm
- amountSpecified>0: Trường hợp này là chính xác lượng đầu vào (exactInput=true) => Tức amountSpecified sẽ là amountIn
- amountSpecified<0: Trường này là chính xác lượng đầu ra => Tức amountSpecified sẽ là amountOut.
- sqrtPriceLimitX96: Giới hạn căn bậc 2 của giá lưu dạng Q64.96. Giá trị này phụ thuộc vào zeroForOne:
- zeroForOne=true => Giá sau hoán đổi không thể thấp hơn giá này
- zeroForOne=false => Giá sau hoán đổi không thể cao hơn giá này.
- data: Dữ liệu bất kỳ sử dụng để làm tham số khi gọi hàm callback.
Giá trị trả về:
- amount0: Sự thay đổi balance của token0 trong pool. Giá trị dương tức token0 cho vào pool, giá trị âm tức token0 được lấy ra khỏi pool.
- amount1: Sự thay đổi balance của token1. Giá trị dương tức token0 cho vào pool, giá trị âm tức token0 được lấy ra khỏi pool.
Hàm này luôn gọi callback vì thế ví thường không gọi được hàm này mà chỉ có contract đã cài đặt hàm callback mới gọi được hàm này. Đọc code thì ta cũng thấy rằng, pool sẽ gửi lại tokenOut cho ta trước và sau đó chúng ta phải gửi lại tokenIn vào lại pool trong hàm callback.
Thực hiện Flashswap đơn giản
Bây giờ tôi sẽ ứng dụng Flashswap để thực hiện swap WETH-UNI-WETH-rETH-WETH qua các pool [UNI-WETH-500, UNI-WETH-3000, rETH-WETH-3000, rETH-WETH-10000] mà không cần vốn. Để đơn giản và mọi người đọc dễ hiểu, tôi sẽ fix code một số địa chỉ.
Cách đơn giản nhất là chúng ta sẽ thực hiện vay ở pool thứ nhất và thực hiện swap trên các pool còn lại sử dụng SwapRouter. Chúng ta viết smart contract UniswapV3Flashswap.sol như sau:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IERC20 {
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
}
interface IUniswapV3Pool {
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
}
interface ISwapRouter {
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
}
contract UniswapV3Flashswap {
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
IERC20 private constant WETH = IERC20(0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6);
IERC20 private constant UNI = IERC20(0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984);
address private constant rETH = 0x178E141a0E3b34152f73Ff610437A7bf9B83267A;
address private constant firstPool = 0x07A4f63f643fE39261140DF5E613b9469eccEC86; // UNI-WETH-500
address private constant router = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
function flashswap(int256 amount, address recipient) external {
bytes memory data = abi.encode(amount, recipient);
IUniswapV3Pool(firstPool).swap(address(this), false, amount, MAX_SQRT_RATIO-1000, data);
}
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
require(msg.sender == firstPool, "Not authorized");
// Decode input params
(uint amount, address recipient) = abi.decode(data, (uint, address));
// Check permission
if (UNI.allowance(address(this), router)<amount) {
UNI.approve(router, type(uint).max);
}
// Swap
ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
path: abi.encodePacked(address(UNI), uint24(3000), address(WETH), uint24(3000), rETH, uint24(10000), address(WETH)),
recipient: address(this),
deadline: block.timestamp,
amountIn: (amount0Delta>=0?uint256(amount0Delta):uint256(-amount0Delta)),
amountOutMinimum: 1
});
ISwapRouter(router).exactInput(params);
// Repay borrow
WETH.transfer(firstPool, (amount1Delta>=0?uint256(amount1Delta):uint256(-amount1Delta)));
// Transfer profit to recipient
uint profit = WETH.balanceOf(address(this));
WETH.transfer(recipient, profit);
}
}
Triển khai ta được địa chỉ 0xf31231c796fF07fC340378586bD4AD30d53C383B. Thông thường dữ liệu amountOut thường ít hơn amountIn nên để đảm bảo giao dịch thành công, tôi chuyển trước vào contract 0.0005 WETH. Bây giờ chúng ta thực hiện swap 0.001 WETH và thực hiện swap bằng cách gọi hàm:
flashswap(1000000000000000, 0x26c51c000abDF0b3Ff54583606F2C839F6Ca3B96)
Ta được giao dịch thành công: 0x19ce6fb488f7298aa4880ac324776b9e2421d7b77cfef8e1df8ea319e40368c4 (Gas Used = 347,722)
Thực hiện Flashswap với mục tiêu tối ưu phí GAS
Lệnh Flashswap ở trên chúng ta sử dụng qua SwapRouter, một contract hỗ trợ sẵn của UniswapV3, việc này giúp code của chúng ta đơn giản hơn, nhưng chúng ta chấp nhận phí GAS cao hơn. Để tối ưu hơn ta tương tác trực tiếp tới UniswapV3Pool thay vì tương tác với SwapRouter. Như vậy chúng ta phải phải tự viết phần swap này bằng cách tham khảo mã nguồn của SwapRouter.
Bây giờ chúng ta viết UniswapV3FlashswapV2.sol như sau:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IERC20 {
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
}
interface IUniswapV3Pool {
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
function token0() external view returns (address);
function token1() external view returns (address);
}
contract UniswapV3FlashswapV2 {
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970341;
uint160 internal constant MIN_SQRT_RATIO = 4295128740;
address internal constant WETH = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6;
address[4] internal pools = [
0x07A4f63f643fE39261140DF5E613b9469eccEC86, // UNI-WETH-500
0x4d1892f15B03db24b55E73F9801826a56d6f0755, // UNI-WETH-3000
0xe90D9a3E765a221BC1A697a1a3b0Bb2E8E8C5E78, // rETH-WETH-3000
0x7e4eaA3d7684d5A49e1cf31D09875f8877Ae4Dd2 // rETH-WETH-10000
];
function flashswap(int256 amount, address recipient) external {
// Swap
IUniswapV3Pool pool = IUniswapV3Pool(pools[0]);
if (pool.token0()==WETH) {
bytes memory data = abi.encode(amount, pool.token0(), pool.token1(), uint8(0));
pool.swap(address(this), true, amount, MIN_SQRT_RATIO, data);
} else {
bytes memory data = abi.encode(amount, pool.token1(), pool.token0(), uint8(0));
pool.swap(address(this), false, amount, MAX_SQRT_RATIO, data);
}
// Transfer profit to recipient
uint profit = IERC20(WETH).balanceOf(address(this));
if (profit>0) IERC20(WETH).transfer(recipient, profit);
}
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
// Decode input params
(int256 amountIn, address tokenIn, address tokenOut, uint8 index) = abi.decode(data, (int256, address, address, uint8));
if (index<pools.length-1) {
// Swap next pool
uint8 nextIndex = index + 1;
IUniswapV3Pool pool = IUniswapV3Pool(pools[nextIndex]);
int256 nextAmountIn = (amount0Delta<0?-amount0Delta:-amount1Delta);
if (pool.token0()==tokenOut) {
bytes memory nextData = abi.encode(nextAmountIn, pool.token0(), pool.token1(), nextIndex);
pool.swap(address(this), true, nextAmountIn, MIN_SQRT_RATIO, nextData);
} else {
bytes memory nextData = abi.encode(nextAmountIn, pool.token1(), pool.token0(), nextIndex);
pool.swap(address(this), false, nextAmountIn, MAX_SQRT_RATIO, nextData);
}
}
// Repay
IERC20(tokenIn).transfer(pools[index], uint256(amountIn));
}
}
Triển khai ta được địa chỉ 0xFb8eb3eFa382F4d179eeFA805a8411BEE514C874. Thông thường dữ liệu amountOut thường ít hơn amountIn nên để đảm bảo giao dịch thành công, tôi chuyển trước vào contract 0.0005 WETH. Bây giờ chúng ta thực hiện swap 0.001 WETH và thực hiện swap bằng cách gọi hàm:
flashswap(1000000000000000, 0x26c51c000abDF0b3Ff54583606F2C839F6Ca3B96)
Ta được giao dịch thành công: 0x7684a558d309262fbed7b274fc8caf0b1c02d2ea5ad51b25054fb3fe46228836 (Gas Used = 313,136)
Với giao dịch mới này, Gas Used chúng ta sử dụng là 313,136 trong khi giao dịch trước thì Gas Used sử dụng là 347,722, như vậy tiết kiệm được 10%.
Sự khác nhau giữa Flashloan và Flashswap
Sự khác nhau giữa Flashloan và Flashswap như hình dưới:
Ở đây tôi muốn nói rõ hơn về Optimistic transfer. Optimistic transfer cho phép người dùng nhận các khoản vay hoặc hoán đổi không thế chấp và thực hiện giao dịch miễn là người dùng trả lại tài sản khi kết thúc giao dịch. Để hiểu rõ hơn bạn chúng ta xem một số mã ở dưới:
Quy trình tổng thể của Giao dịch chênh lệch giá (Dạng đơn giản)
Ví dụ xây dựng Bot thực hiện giao dịch chênh lệch giá cặp ETH-DAI giữa Uniswap và SushiSwap (Tham khảo: Research Summary: Building a Flash Loan Arbitrage Bot on Infura):
- B1: Bot thực hiện theo dõi các cặp ETH-DAI trên cả Uniswap và SushiSwap
- B2: Khi Bot phát hiện ra một cơ hội chênh lệch giá có lãi, nó sẽ gửi một giao dịch đến một hợp đồng. Tham khảo: flashloaner-contract
- B3: Trong một giao dịch, Smart Contract sẽ thực hiện:
- Sử dụng Flashswap để vay tài sản từ nhóm có giá thấp hơn
- Bán ngay tài sản trong nhóm giá cao hơn
- Hoàn trả khoản vay Flashswap và bỏ túi phần chênh lệch
2 Pingbacks