Về Flashloan thì chắc các bạn không xa lạ gì nữa. Nó là một khoản vay nhanh không cần thế chấp nhưng phải trả lại ở cuối xử lý trong cùng Block. Bài viết này tập trung vào hướng dẫn triển khai sử dụng Flashloan trên mạng lưới Binance Smart Chain BSC Testnet. Và sau đó chúng ta mở rộng tìm hiểu về Flashswap sử dụng PancakeSwap.
Có thể bạn quan tâm: Hướng dẫn triển khai Flashloan / Flashswap sử dụng Uniswap V3 trên Goerli Testnet
Mục lục
Triển khai Flashloan sử dụng MCL Flashloan
Gần đây Multiplier Finance đã cho ra mắt hệ thống Lending/Borrowing trên BSC có hỗ trợ Flashloan. Do đó chúng ta có thể sử dụng hệ thống này cho việc triển khai thử nghiệm Flashloan, đặc biệt MCL có hỗ trợ Testnet nên chúng ta triển khai trên BSC Testnet cho tiết kiệm. Một số tài nguyên bạn có thể tham khảo tại:
MCL đã cung cấp sẵn source code demo cho phần Flashloan, bạn xem tại địa chỉ: MCL-FlashloanDemo. Trong bài viết này tôi không tập trung vào việc hướng dẫn bạn build smart contract thế nào, mà yêu cầu bạn phải có kiến thức này rùi.
Ở đây chúng ta quan tâm tới tệp demo.sol, là contract demo sử dụng flashloan. Chúng ta có 1 số chú ý:
- Hàm khởi tạo chúng ta cần truyền 2 địa chỉ:
- Tham số addressesProvider: Địa chỉ LendingPool để vay flashloan
- Tham số defi: Là địa chỉ của ứng dụng chúng ta sử dụng để làm các nghiệp vụ sau khi vay. Ở đây có contract defi.sol là contract đơn giản để demo với nghiệp vụ nạp và rút tiền. Thực tế để kiếm được tiền, nghiệp vụ này phức tạp hơn nhiều.
- Hàm flashloanBnb() là hàm người dùng gọi để bắt đầu thực hiện nghiệp vụ Flashloan => Sau khi thực hiện vay, hàm executeOperation sẽ được gọi để thực hiện nghiệp vụ.
Bạn có thể sử dụng Truffle hoặc Remix IDE để triển khai. Nói chung sử dụng Remix IDE là đơn giản và tiện nhất. Các bước thực hiện như sau:
- B1: Triển khai contract defi.sol => Được địa chỉ contract: 0xb6b7c58ceda455347d03a16bfb4b73efd8f12b19.
- B2: Triển khai contract demo.sol với tham số khởi tạo:
_addressesProvider=0x78547CBf195Dc3D92C5847acD9E89aFB35430c0f (Lấy từ MCL Addresses)
_defi=0xb6b7c58ceda455347d03a16bfb4b73efd8f12b19 (Contract triển khai ở trên)
=> Được địa chỉ contract demo là: 0xc0dfdce817c6d4919f4f9a198d312a14221733ee - B3: Send một ít BNB vào contract demo ở trên, tầm 0.1 BNB là được.
- B4: Gọi hàm flashloanBnb với _amount=10000000000000000000 (Tương đương với 10 BNB), bạn sẽ thấy kết quả giao dịch: https://testnet.bscscan.com/tx/0xdce94d3585166080a77f9c0b4a3f279f16eeba24971b3208d80a7e7b05c0c0ab
Các bạn có thể không phải triển khai lại mà test trực tiếp trên contract Demo: 0xc0dfdce817c6d4919f4f9a198d312a14221733ee => Nhớ đẩy 1 ít BNB làm phí trả cho pool nếu contract không có BNB nhé.
Triển khai Flashloand sử dụng CREAM Finance Flashloan
CREAM Finance là nền tảng DEFI hỗ trợ trên nhiều network khác nhau trong đó có Binance Smart Chain (BSC). Cream Finance có hỗ trợ FlashLoan trên BSC, chi tiết xem tại: https://docs.cream.finance/v/binance-smart-chain/flash-loans và https://docs.cream.finance/flash-loans
CREAM Finance cũng có ví dụ về sử dụng Flashloan, chi tiết xem tại địa chỉ: https://github.com/CreamFi/flashloan-playground
Danh sách pool hỗ trợ Flashloan bạn xem tại: https://docs.cream.finance/v/binance-smart-chain/lending/lending-contract-address
Do không hỗ trợ testnet nên khá khó để thử nghiệm Flashloan trên Testnet BSC.
Triển khai Flashswap sử dụng Pancake
Sử dụng Flashswap trên Pancake theo cách thông thường
PancakeSwap không hỗ tường minh Flashloan, nhưng trong code của PancakePair.sol của PancakeSwap lại tiềm ẩn khả năng giúp user có thể vay nhanh được. Khi vay nhanh từ các nền tảng AMM như PancakeSwap này người ta gọi là Flashswap:
Bạn để ý đoạn code trên sẽ thấy:
- (1) PancakePair chuyển token0 và token1 sang địa chỉ to.
- (2) PancakePair gọi hàm pancakeCall(…) của địa chỉ to nếu điều kiện data.length>0 được thỏa mãn.
Như vậy nếu to là một contract có cài đặt hàm pancakeCall(…) thì trong hàm này bạn có thể thực hiện các nghiệp vụ gì đó trước khi trả lại cho token0 và token1 cho PancakePair kèm theo phí 0.3% (Tùy phiên bản) để đảm bảo phần check phía sau không bị lỗi => Đây chính là kẽ hở giúp bạn thực hiện Flashloan (Chính xác hơn gọi là Flashswap). Đặc biệt bạn có thể vay nhanh đồng thời token0, token1 hoặc chỉ vay nhanh 1 trong 2 token.
Bây giờ ta cài đặt đoạn mã nhỏ thực hiện vay nhanh WBNB trên môi trường testnet. Một số tham số thiết lập cố định trong Smart Contract để cho code đơn giản.
Đầu tiên ta tạo tệp Defi.sol với nghiệp vụ đơn giản:
pragma solidity ^0.5.0; interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
} interface IDefi { function withdraw(uint256 _amount) external ; } contract Defi is IDefi { IERC20 public wbnb = IERC20(0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd);
event WithdrawBnbEvent(address indexed _user, uint256 _amount);
function withdraw(uint256 _amount) external {
wbnb.transfer(msg.sender, _amount);
emit WithdrawBnbEvent(msg.sender, _amount);
}
}
Sau triển khai ta được địa chỉ của Defi: 0xa16cBa9874Eb8DA206CB588638da8A580e019754
Giờ tạo tệp Flashswap.sol được thực hiện vay và gọi tới contract Defi để thực hiện nghiệp vụ:
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.6.12;
interface Ipancake {
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
}
interface IBEP20 {
function mint(address account, uint256 amount) external;
function burn(address account, uint256 amount) external;
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount)
external
returns (bool);
function allowance(address owner, address spender)
external
view
returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
interface IDefi {
function withdraw(uint256 _amount) external ;
}
contract FlashSwap {
address public owner;
Ipancake public ipancake = Ipancake(0xF855E52ecc8b3b795Ac289f85F6Fd7A99883492b);
IBEP20 public wbnb = IBEP20(0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd);
uint256 public fee = 3;
IDefi public app = IDefi(0xa16cBa9874Eb8DA206CB588638da8A580e019754);
modifier onlyOwner() {
require(msg.sender == owner, '1');
_;
}
constructor() public {
owner = msg.sender;
}
function runApprove() public onlyOwner {
wbnb.approve(address(ipancake), uint256(-1));
}
function adminOut() public onlyOwner {
wbnb.transfer(owner, wbnb.balanceOf(address(this)));
}
function flashloan(uint amount) public onlyOwner {
ipancake.swap(0, amount, address(this), abi.encodePacked(uint256(-1)));
}
function pancakeCall(address sender, uint amount0, uint amount1, bytes calldata data) public {
// Do business actions here to use WBNB
wbnb.transfer(address(app), amount1);
app.withdraw(amount1);
// Send back WBNB to PancakePair
uint256 amount = amount1 + amount1*fee/1000;
wbnb.transfer(address(ipancake), amount);
}
}
Sau khi triển khai ta được địa chỉ của Flashswap là: 0x973c56E134631fe6eE04b5722AfA84E43a1C3D34
Bây giờ ta chạy thử, ta thực hiện các bước sau:
- B1: Chạy hàm runApprove() lần đầu tiên. Chỉ cần gọi 1 lần duy nhất.
- B2: Send ít nhất 0.03 WBNB vào contract để bù phần phí.
- B3: Ta thực hiện vay 10 WBNB bằng cách gọi hàm flashloan(10000000000000000000)
- B4: Kiểm tra giao dịch. Đây là giao dịch mà tôi thử: 0x35fdcf367e93077d8c8b42c2e47625fc5d291eae85a58e8526e7de2005d37c99
Sử dụng Flashswap trên Pancake theo cách đặc biệt
Theo như ví dụ trên chúng ta đang sử dụng theo cách vay token nào thì trả lại token đó, vay cả hai token thì trả lại cả hai token. Nhưng với Flashswap thì bạn có thể vay 1 token và trả lại bằng token còn lại. Đây là tính năng đặc biệt được nhiều bot Arbitrage tận dụng để giảm vốn sử dụng.
Ví dụ bạn tìm được một cơ hội Arbitrage trên PancakeSwap với 10 WBNB và bạn thư được 10.5 WBNB theo path như sau: WBNB-ETH-BUSD-USDT-WBNB. Như vậy để thực hiện được swap này và kiếm được lợi nhuận 0.5 BNB bạn phải bỏ vốn 10 WBNB. Trường hợp nếu không bỏ vốn thì ta phải vay WBNB từ nơi khác và phải mất thêm phí vay, đồng nghĩa với việc lợi nhuận giảm. Đây chính là trường hợp mà Flashswap tỏ ra lợi thế. Chúng ta tận dụng Flashswap trong trường hợp này như sau:
- B1: Thực hiện vay ETH ở pair đầu tiên WBNB-ETH
Giả sử theo tính toán của bạn thì pair WBNB-ETH thì 10 WBNB khi swap bạn nhận được 1.42 ETH
=> Ở pair đầu tiên này ta sẽ không thực hiện swap theo cách thông thường mà ta sẽ thực hiện vay 1.42 ETH ở pair WBNB-ETH - B2: Thực hiện swap bình thường ở các cặp tiếp theo
Sau khi có 1.42 ETH ta thực hiện swap bình thường theo path ETH-BUSD-USDT-WBNB và ta sẽ nhận được 10.5 WBNB. - B3: Trả lại WBNB cho pair đầu tiên
Bây giờ ta trả lại 10 WBNB cho pair đầu tiên WBNB-ETH
=> Đến đây chúng ta đã hoàn thành lệnh mà không cần phải có 10 WBNB vốn và chúng ta cũng không mất thêm phí gì cả. - B4: Lợi nhuận còn lại ta có thể để trực tiếp trên contract hoặc rút sang ví khác cho an toàn.
Bạn có thể mở rộng ra ứng dụng cho các trường hợp khác.
Bây giờ tôi sẽ thử chạy một ví dụ trên BSC Testnet để thực hiện mô phỏng quá trình vay 1 token và trả lại bằng 1 token khác.
Đầu tiên ta viết FlashSwap02.sol với nội dung như sau:
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.6.12;
interface IBEP20 {
function mint(address account, uint256 amount) external;
function burn(address account, uint256 amount) external;
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount)
external
returns (bool);
function allowance(address owner, address spender)
external
view
returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
interface IPancakePair {
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function token0() external view returns (address);
function token1() external view returns (address);
}
interface IPancakeFactory {
function getPair(address tokenA, address tokenB) external view returns (address pair);
}
interface IPancakeRouter {
function factory() external pure returns (address);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
}
contract FlashSwap02 {
address public owner;
IPancakeRouter public router = IPancakeRouter(0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3);
modifier onlyOwner() {
require(msg.sender == owner, 'Not only owner!');
_;
}
constructor() public {
owner = msg.sender;
}
function approve(address token) public onlyOwner {
IBEP20(token).approve(address(router), uint256(-1));
}
function withdraw(address token) public onlyOwner {
IBEP20(token).transfer(owner, IBEP20(token).balanceOf(address(this)));
}
function flashSwap(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) public onlyOwner {
require(path.length>2, "The path length must be greater than 2!");
require(path[0]==path[path.length-1], "Input and output tokens are same!");
IPancakePair pair = IPancakePair(IPancakeFactory(router.factory()).getPair(path[0], path[1]));
address[] memory newPath = getNewPath(path, 0, 1);
uint amountOut = router.getAmountsOut(amountIn, newPath)[1];
if (amountOut>0) amountOut = amountOut - 1;
bytes memory data = abi.encode(amountIn, amountOutMin, deadline, pair, to, path);
if (pair.token0()==path[0]) {
pair.swap(0, amountOut, address(this), data);
} else {
pair.swap(amountOut, 0, address(this), data);
}
}
function pancakeCall(address sender, uint amount0, uint amount1, bytes calldata data) public {
// Decode data
(uint amountIn, uint amountOutMin, uint deadline, address pair, address to, address[] memory path) = abi.decode(data, (uint, uint, uint, address, address, address[]));
// Swap
address[] memory newPath = getNewPath(path, 1, path.length-1);
if (IPancakePair(pair).token0()==path[0]) {
router.swapExactTokensForTokens(amount1, amountOutMin, newPath, address(this), deadline);
} else {
router.swapExactTokensForTokens(amount0, amountOutMin, newPath, address(this), deadline);
}
// Send back tokenIn to pair
IBEP20(path[0]).transfer(pair, amountIn);
// Send remaining asset
IBEP20 tokenOut = IBEP20(path[path.length - 1]);
tokenOut.transfer(to, tokenOut.balanceOf(address(this)));
}
function getNewPath(address[] memory path, uint fromIndex, uint toIndex) internal pure returns (address[] memory newPath) {
newPath = new address[](toIndex - fromIndex + 1);
for (uint idx=fromIndex; idx<=toIndex; idx++) {
newPath[idx-fromIndex] = path[idx];
}
}
}
Vì thực tế rất ít khi có cơ hội Arbitrage thực sự nên amountOut thường sẽ nhỏ hơn amountIn, do đó tôi sửa code một chút giúp bù lại phần tài sản thiếu hụt để đảm bảo giao dịch thành công. Tôi đã triển khai contract này trên Remix ta được contract có địa chỉ: 0xd1B0632a7F2190b68Ff49b0668a70D1D44c3F816
Bây giờ ta thử thực hiện swap với path là WBNB-ETH-BUSD-USDT-WBNB, theo địa chỉ sẽ là:
[“0xae13d989dac2f0debff460ac112a837c89baa7cd”, “0x8babbb98678facc7342735486c851abd7a0d17ca”, “0x78867BbEeF44f2326bF8DDd1941a4439382EF2A7”, “0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684”, “0xae13d989dac2f0debff460ac112a837c89baa7cd”]
Theo path này với 0.1 WBNB (amountIn) ta sẽ thu được 0.0971 WBNB (amountOut), để thực hiện swap này đúng ra ta phải có 0.1 WBNB có sẵn trong contract, nhưng theo cách này ta chỉ cần có 0.003 WBNB là đủ, đây là lượng để bù lại do amountOut nhỏ hơn amountIn. Bây giờ ta thử thực hiện flashSwap() theo các bước sau:
- B1: Gọi hàm approve(0x8babbb98678facc7342735486c851abd7a0d17ca) để cho phép PancakeRouter sử dụng ETH trong contract.
- B2: Gửi 0.003 WBNB tới địa chỉ contract 0xd1B0632a7F2190b68Ff49b0668a70D1D44c3F816 để bù phần chênh lệch khi swap.
- B3: Gọi hàm flashSwap() để thực hiện lệnh swap thông qua cơ chế vay:
flashSwap(100000000000000000, 1, [“0xae13d989dac2f0debff460ac112a837c89baa7cd”, “0x8babbb98678facc7342735486c851abd7a0d17ca”, “0x78867BbEeF44f2326bF8DDd1941a4439382EF2A7”, “0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684”, “0xae13d989dac2f0debff460ac112a837c89baa7cd”], 0xE42C439D836708f43F77a65C198A2d7f55b3f3f4, 1687115633)
Ta nhận được giao dịch thành công: 0x75fa89f1e9ba24fbd51111997d34eed0e7bb3bf6c670d86f8fd90ac28e747f6f
Phân tích giao dịch flashSwap trên BscScan Testnet, mục Tokens Transferred bạn sẽ thấy:
- B1: Contract vay 0.024885936984742241 ETH từ pair WBNB-ETH
- B2: Contract đổi từ ETH được 39.107066320319795487 BUSD trên pair ETH-BUSD
- B3: Tiếp tục đổi BUSD được 39.875176744520586697 USDT
- B4: Tiếp tục đổi USDT được 0.09710666639028922 WBNB chuyển về contract. Cộng thêm 0.003 WBNB ta chuyển trước vào contract trước đó thì lúc này contract có trên 0.1 WBNB.
- B5: Gửi trả lại 0.1 WBNB tới pair WBNB-ETH đã vay trước đó. Đúng như ta mong muốn, ban đầu ta vay ETH, bây giờ ta trả lại bằng WBNB. Thực chất của quá trình này là ta swap 0.1 WBNB thành 0.024885936984742241 ETH, nhưng với cách làm này thì ta không cần phải có 0.1 WBNB => Đúng kiểu tay không bắt giặc.
- B6: Cuối cùng để an toàn cho tài sản thì lợi nhuận ta chuyển tới 1 ví khác.
Các ứng dụng của Flashloan
Như vậy việc sử dụng Flashloan không còn là vấn đề nữa. Vấn đề bây giờ là làm sao sử dụng vốn từ Flashloan để kiếm lợi nhuận cho bạn.
Bạn có thể sử dụng Flashloan để:
- Arbitrage (Kinh doanh chênh lệch giá): Đây chính là phần các bạn nên đầu tư trí tuệ và thời gian để kiếm được lợi nhuận.
- Locational Arbitrage: Mua token từ 1 sàn DEX và bán nó ở một sàn khác để ăn chênh lệch giá.
- Triangular Arbitrage: Giao dịch liên quan đến ba token để ăn chênh lệch giá.
- Hoán đổi tài sản thế chấp: Tức là bạn đã thế chấp đồng A để vay đồng B rùi, và đồng B bạn đã sử dụng vào việc nào đó rồi. Nhưng giờ bạn muốn đổi đồng A rủi ro giá cao sang đồng tiền C khác an toàn hơn, trong khi bạn không còn đồng B => Flashloan sẽ rất hữu ích.
- Thanh lý tài sản: Bạn đã thế chấp đồng A để vay đồng B, và bạn mang đồng B đi đầu tư và đã mất hết. Bạn muốn rút phần tiền còn lại ra khỏi hệ thống, nhưng bạn ko có tiền để mua đồng B nữa => Flashloan sẽ hữu ích trong trường hợp này.
- Khai thác lỗ hổng nghiệp vụ: Từ khi ra đời Flashloan, các hacker đã khai thác tối đa để kiếm tiền bất hợp pháp từ các lỗ hổng nghiệp vụ hay chủ động thao túng tỉ giá. Vì thế có rất nhiều người trì trích về Flashloan và là nguyên nhân gây sụp đổ hệ thống DeFi. Mình đứng trên góc nhìn khác thì mình nhờ Flashloan mà lỗ hổng nghiệp vụ được phát hiện ra sớm, và các ứng dụng DeFi sẽ sớm sửa lỗi và hoàn thiện hơn. Bình thường có thể phải mất 10 năm để các ứng dụng DeFi đi vào hoạt động ổn đình, thì nhờ có Flashloan có thể chỉ cần 2-3 năm mà thôi.
Có thể bạn quan tâm: Hướng dẫn hack hợp đồng thông minh Smart Contract với Ethernaut CTF – Cách học bảo mật Smart Contract hiệu quả nhất
Nguồn: Tổng hợp
Luckyyyy
defi: Là địa chỉ của ứng dụng chúng ta sử dụng để làm các nghiệp vụ sau khi vay. Ở đây có contract defi.sol là contract đơn giản để demo với nghiệp vụ nạp và rút tiền. Thực tế để kiếm được tiền, nghiệp vụ này phức tạp hơn nhiều. Cho mình hỏi rõ là địa chỉ này lúc mình tạo metamask hay là hợp đồng thông minh vây bạn ?
Hợp đồng thông minh tạo token bình thường có đc không ? Mình không hiểu lắm
Online Kiếm Tiền
– B1. Đầu tiên bạn phải triển khai smart contract “defi.sol” trước nhé => Bạn sẽ được 1 địa chỉ contract.
– B2. Sau đó triển khai “demo.sol” => Tham số _defi chính là địa chỉ trong B1
Luckyyyy
Ad ơi vậy là phải tìm cod smartcontract bsc ? Ad có thể chia sẽ link cho mình với ạ ? Mình tìm thì chỉ cod để tạo token ?
Online Kiếm Tiền
Bài viết có link mà:
– MCL-FlashloanDemo: https://github.com/Multiplier-Finance/MCL-FlashloanDemo
MCL có sẵn example rùi. Chỉ triển khai rùi test thử thôi nhé!