DeFi 套利機器人秘密技術。如何將所有內容整合到單個事務中
source : https://medium.com/coinmonks/defi-arbitrage-bots-secret-technique-how-to-fit-everything-into-single-transaction-ac9aa13df33f by Alexander Koval
套利機器人是複雜而有效的工具,可以為您帶來被動收入。隨著 DeFi 空間的增長,它們變得越來越受歡迎。但為了盈利,您應該遵循最佳實踐並避免典型錯誤。
通常 DeFi 交易機器人由以下組件組成:
- 監控流動性池並發現套利機會的鏈下腳本
- 鏈上合約 — — 包含套利邏輯的智能合約
- 熱錢包 — — 保留資產用於套利並獲利
想像一下,鏈下腳本在 DeFi 領域的某個地方發現了一個有利可圖的套利機會,您需要搶在競爭對手之前利用它。套利必須在單筆交易中完成,否則您將浪費大量時間,並且可能會輸給其他機器人。
典型的套利涉及使用 ETH 和 ERC-20 代幣進行操作,因此首先想到的想法是預先部署智能合約並用代幣提供資金,每當發現套利機會時,腳本就會觸發交易具體參數
這種方法很幼稚,並且有嚴重的缺點:
- 你必須預先部署你的合約並支付 Gas 費 — — 但是如果永遠找不到機會怎麼辦 — — 你會失去為部署而支付的 Gas 費 — — 這在 Ehtereum 主網中可能會很多
- 你必須提前用ERC-20 代幣為你的合約提供資金,然後讓我們持有它們一段時間 — — 這會帶來安全風險,因為你必須仔細審核你的合約,以確保沒有人可以破解它並竊取你的資產 — — 很多如果您的資金被鎖定在熱錢包中而不是合約中,則更容易只管理您的私鑰
- 一旦部署,任何人都可以讀取您的合約並竊取您的邏輯,以便將其重新用於他們自己的套利機器人,這些機器人將與您的套利機器人競爭
這是更好的策略
- 理想情況下,您希望將 ETH 和 ERC-20 代幣保留在熱錢包上,並將智能合約代碼保留在鏈外,直到找到套利機會
- 一旦發現套利機會,您的機器人必須部署智能合約,用 ETH 和 ERC-20 代幣為其提供資金,進行套利並將利潤轉移回熱錢包
- 所有這些操作都必須在單個事務中完成
請記住,部署會導致事務,這意味著所有邏輯都必須在構造函數內完成。我們可以在部署期間用 ETH 為合約提供資金,但轉移 ERC-20 代幣是一個棘手的部分。
轉移 ERC-20 有兩種方法:轉移和批准這兩種方法都需要向代幣合約進行額外交易,其中發送者是代幣持有者。但是我們只有一個已用於部署的交易,這意味著令牌傳輸必須在構造函數內完成,以便將我們的套利融入到單個交易中。那麼怎樣才能讓它成為可能呢?
該解決方案是新的EIP-2616代幣標準 — — 它包含使用數字簽名並允許無 Gas 代幣傳輸的許可功能。很多代幣已經支持這個標準。我第一次遇到它是在1Inch交易所進行交換時 — — 我很驚訝地看到簽名請求而不是批准交易,並開始進一步挖掘以發現支出批准的新方法。這裡還有一篇文章解釋它是如何工作的:https://www.quicknode.com/guides/ethereum-development/transactions/how-to-use-erc20-permit-approval/
EIP-2616讓我們在單筆交易中使用新的代幣轉移和套利策略:
- 熱錢包為許可功能準備簽名 — — 這是在鏈外完成的,不會創建交易
- 簽名作為參數傳遞給構造函數
- 在構造函數中,智能合約調用 Permit 函數,而不是TransferFrom函數並獲取代幣的所有權
- 執行套利邏輯
- 利潤轉回熱錢包
因此,熱錢包只需提交一筆交易 — — 具有適當參數的合約部署,一旦挖礦,利潤就會被拿走。
然而有一個問題 — — 讓我們看一下permit函數接口:
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
正如我們所看到的,我們需要提供支出者的地址 — 但在我們的例子中,支出者是一個尚未部署的合約!我們如何知道部署後合約將在哪個地址上?
幸運的是,合約地址是根據部署者的地址和部署者的隨機數預先確定的。這是 stackexchange 上的一個線程,解釋瞭如何在 EVM 中計算合約地址:https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-compulated/ 761#第761章
以下是在部署之前根據部署者的地址和隨機數預測合約地址的 JS 代碼:
const rlp = require("rlp");
const keccak = require("keccak");
async function predictContractAddress(deployer) {
const nonce = await ethers.provider.getTransactionCount(deployer.address);
const sender = deployer.address;
const input_arr = [sender, nonce];
const rlp_encoded = rlp.encode(input_arr);
const contract_address_long = keccak("keccak256")
.update(rlp_encoded)
.digest("hex");
const contract_address = contract_address_long.substring(24); //Trim the first 24 characters.
return contract_address;
}
現在我們有了準備支出批准簽名的所有數據,我們可以將其傳遞給構造函數。以下是在 Hardhat 上運行的腳本的完整代碼:
async function doArbitrage(deployer, value, tokenBalance, ...params) {
const chainId = (await ethers.provider.getNetwork()).chainId;
// set the domain parameters
const domain = {
name: await token.name(),
version: "1",
chainId: chainId,
verifyingContract: token.address
};
// set the Permit type parameters
const types = {
Permit: [{
name: "owner",
type: "address"
},
{
name: "spender",
type: "address"
},
{
name: "value",
type: "uint256"
},
{
name: "nonce",
type: "uint256"
},
{
name: "deadline",
type: "uint256"
},
],
};
const predictedAddress = await predictContractAddress(deployer);
console.log("Predicted address: " + predictedAddress);
const deadline = (await time.latest()) + 9999999;
const values = {
owner: deployer.address,
spender: predictedAddress,
value: tokenBalance,
nonce: await token.nonces(deployer.address),
deadline: deadline,
};
const signature = await deployer._signTypedData(domain, types, values);
const sig = ethers.utils.splitSignature(signature);
const arbitrageContract = await (await ethers.getContractFactory('Arbitrage', deployer)).deploy(...params, sig.v, sig.r, sig.s, { value });
console.log(`Actual address: ${arbitrageContract.address}`);
}
以及智能合約構造函數的代碼:
import "@openzeppelin/contracts/interfaces/IERC2612.sol";
import "@openzeppelin/contracts/utils/Address.sol";
contract Arbitrage {
using Address for address payable;
constructor (/* parameters for arbitrage */, address _token, uint256 tokenBalance, uint256 _deadline, uint8 v, bytes32 r, bytes32 s) payable {
IERC2612 token = IERC2612(_token);
token.permit(
msg.sender,
address(this),
tokenBalance,
_deadline,
v,
r,
s
);
token.transferFrom(msg.sender, address(this), tokenBalance);
// now contract has tokens and ETH and you can do your arbitrage here
// ...
token.transfer(msg.sender, token.balanceOf(address(this))); // send tokens back to hot wallet
payable(msg.sender).sendValue(address(this).balance); // send ETH back to hot wallet
}
}
並且不要忘記使用Flashbots RPC而不是Infura來保護您的套利交易免遭搶先交易
受到Damn Vulnerable DeFi上“Puppet”挑戰的啟發