Một phân tích ngắn gọn về quá trình chụp Synthetix MEV chi tiết
Nguồn | bertcmiller.com
Tác giả |Robert Miller
hình ảnh
KALEB trước đây đã rò rỉ tin tức quan trọng về những thay đổi chính sách tại Synthetix, liên quan đến hàng trăm nghìn đô la. Chia sẻ loại tin tức này trong hang ổ của kẻ điều hành rô-bốt này giống như ném thịt đỏ cho sư tử, và xem nhanh hợp đồng sẽ thấy một khoản tiền hấp dẫn trong đó.
tiêu đề cấp đầu tiên
Bước một: Xác định phạm vi cơ hội
Tôi không phải là chuyên gia về Synthetix, vì vậy bước đầu tiên là hiểu những gì tôi làm. Đặc biệt:
Tôi tìm thấy hợp đồng có liên quan
Tôi đã đọc qua tổng quan về chức năng của chúng trên blog Sythetix và tìm kiếm tất cả các tài liệu
Tôi tự tin rằng tôi hiểu những thay đổi quản trị sắp tới
Tôi xem xét các chức năng và tìm kiếm những chức năng có vẻ phù hợp
Tóm tắt về giai đoạn công việc này là Synthetix đã thử nghiệm sử dụng ETH làm tài sản thế chấp để đúc sUSD và sETH. Bạn có thể ký gửi ETH trong hợp đồng và đúc các tài sản này, miễn là bạn lưu ý rằng giá trị tài sản thế chấp của bạn không giảm xuống dưới một mức nhất định của khoản vay của bạn.
Tuy nhiên, một năm sau, giao thức đã bỏ phiếu kết thúc thử nghiệm. Làm thế nào họ có thể làm điều đó khi có hàng triệu khoản nợ tồn đọng? Vâng, bạn có thể làm cho bất kỳ vị trí phải trả. Trên thực tế, sau một thời gian cảnh báo kéo dài, khoản vay chuyển từ trạng thái an toàn trong một khối sang việc bất kỳ ai cũng có thể hoàn trả, bất kể giá trị của tài sản thế chấp là bao nhiêu! Điều này kích hoạt một giao dịch từ"pDAO"Địa chỉ các giao dịch được gửi đến nhóm giao dịch công khai.
tiêu đề cấp đầu tiên
Bước hai: Biết cơ hội
Bây giờ tôi đã hiểu cơ học cơ bản và có một số chức năng mà tôi nghĩ là có liên quan. Sau đó, tôi đi sâu vào những chức năng mà tôi sẽ gọi, dữ liệu nào tôi cần và cách tạo dữ liệu đó.
Tôi cần hai chức năng:
setLoanLiquidationOpen(): Chỉ có thể được gọi bởi chủ sở hữu hợp đồng (pDAO). Các khoản hoàn trả được cho phép đối với các khoản vay chưa thanh toán.
LiquidateUnclosedLoan(): Yêu cầu ID khoản vay và địa chỉ tài khoản, đồng thời hoàn trả khoản vay. Có thể được sử dụng bởi bất kỳ ai sau khi setLoanLiquidationOpen() đã được gọi.
Lưu ý rằng có các chức năng khác, nhưng tôi nhanh chóng phát hiện ra rằng chúng không liên quan. Bây giờ, tôi cần tìm ra cách tôi chọn khoản vay nào để trả và tôi cần bao nhiêu sETH/sUSD để làm như vậy. Hàm sau kích hoạt hầu hết những thứ cần thiết để hoạt động:
openLoanIDsByAccount(): trả về tất cả các ID khoản vay mở liên quan đến một tài khoản
getLoanInformation(): trả về dữ liệu khoản vay cho một ID và chủ sở hữu nhất định
Tuy nhiên, có hai khó khăn tiềm ẩn. Trước hết, các hợp đồng này không cho biết địa chỉ nào có dư nợ nên gây khó khăn cho tôi. Trong vòng vài phút, tôi đã tìm ra một giải pháp thay thế, đó là tải xuống tất cả các giao dịch của bạn với các hợp đồng này trên Etherscan và sử dụng Excel để tạo danh sách các tài khoản duy nhất đã tương tác với các hợp đồng này. Từ đó, tôi đã xây dựng đường ống dẫn sau:
Sử dụng getLoans để tìm hiểu xem có khoản nợ nào tại địa chỉ này hay không và nếu có, hãy ghi lại ID khoản vay của họ
Sử dụng getLoanInformation để tìm hiểu giá trị của tài sản thế chấp hỗ trợ cho khoản vay, họ đã phát hành bao nhiêu sUSD/sETH và khoản vay của họ có lãi suất bao nhiêu
Sắp xếp theo số tiền cho vay, liệt kê khoản vay lớn nhất trước và lưu cấu trúc dữ liệu vào tệp json.
Khó khăn thứ hai là số lượng ETH thế chấp có thể được hoàn trả cho khoản vay không được biết ngay lập tức. Vì có chức năng dư nợ nên bạn có thể ước tính sơ bộ, nhưng tôi cần số liệu chính xác hơn. Để làm điều này, tôi đã nghiên cứu mã hoàn trả khoản vay và tìm ra cách các con số được tạo ra.
Tất cả các dữ liệu trên có thể được lấy hoặc tính toán trên chuỗi. Nhưng làm vậy thì hao xăng quá. Vì tôi sẽ cạnh tranh với những người khác để đạt được hiệu quả sử dụng gas của hợp đồng, điều cực kỳ quan trọng đối với tôi là phải chuyển càng nhiều logic ra khỏi chuỗi càng tốt để giảm thiểu mức tiêu thụ gas.
Sau một vài lần lặp lại, tôi biết lượng dữ liệu tối thiểu mà tôi cần để thoát khỏi chuỗi là một số biến mà tôi có thể giải quyết ngoài chuỗi để thông báo những gì tôi đã nhập trên hợp đồng thông minh. Tuy nhiên, chức năng để lấy thông tin này rất phức tạp và việc truy vấn tất cả các khoản vay mất nhiều thời gian hơn là xử lý một khối. Điều này là không khả thi. Để giải quyết vấn đề này, tôi đã viết một hợp đồng thông minh ngắn tổng hợp nhiều yêu cầu dữ liệu lại với nhau, giúp tăng tốc hơn 10 lần. Đây là một trong những chức năng đó:
function batchGetLoanInformation(address[ ] calldata _addresses, uint256[ ] calldata _loanIDs, address _contractAddress) external view returns ( uint256[ ] memory, uint256[ ] memory){ uint256[ ] memory totalRepayment = new uint256[ ](_addresses.length); uint256[ ] memory totalCollateralLiquidated = new uint256[ ](_addresses.length); for (uint i = 0; i < _addresses.length; i++){ uint loanAmount; uint accruedInterest; (,, loanAmount,,,, accruedInterest, ) = collateralContract(_contractAddress).getLoan(_addresses[i], _loanIDs[i]); totalRepayment[i] = loanAmount + accruedInterest; totalCollateralLiquidated[i] = getCollateralAmountSUSD(sUSD, totalRepayment[i], COLLATERAL); } return (totalRepayment, totalCollateralLiquidated);}
Vui lòng xem rawsAssetsOracle.sol do EmGithub cung cấp
tóm tắt:
tóm tắt:Giai đoạn này là về việc hiểu rõ hơn về cơ hội và thu thập tất cả dữ liệu cần thiết để thực hiện nó một cách hiệu quả.Bạn cần cố gắng hết sức để di chuyển mọi thứ ra khỏi chuỗi để giảm mức tiêu thụ gas.tiêu đề cấp đầu tiên
tiêu đề phụ
Chiến lược cho vay chớp nhoáng
Bạn có thể cần một hợp đồng chuyên dụng để rút MEV. Tôi đã viết một hợp đồng và môi trường thử nghiệm từ rất sớm và tôi có thể sử dụng môi trường này để hiểu rõ hơn về hợp đồng và đảm bảo dữ liệu chính xác. Điều này được thực hiện gần như đồng thời với bước thứ hai và thứ tư.
Tôi biết mình cần kiếm được hàng triệu đô la bằng sUSD/sETH, vì vậy các khoản vay chớp nhoáng là điều bắt buộc. Ngoài ra, tôi sẽ đốt những tài sản tổng hợp này nhưng lấy lại ETH đã đặt cược. Sau một hồi suy nghĩ, tôi nhận ra rằng dù sao thì tôi cũng sẽ đổi ETH lấy các tài sản khác, nhưng tôi có thể chọn thực hiện việc đó trước hoặc sau khi hoàn trả. Có hai con đường có thể:
Tùy chọn 1 Tùy chọn 1: Cho vay ETH qua Khoản vay Flash -> Trao đổi USDC -> Trao đổi sUSD -> Trả khoản vay sUSD -> Nhận ETH -> Trả khoản vay Flash ETH
Tùy chọn 2 Tùy chọn 2: Vay sUSD thông qua flash loan -> hoàn trả khoản vay sUSD -> nhận ETH -> quy đổi USDC -> quy đổi sUSD -> hoàn trả khoản vay flash sUSD
tối ưu hóa khí
tối ưu hóa khí
Bạn có thể tìm thấy hợp đồng đầy đủ của tôi tại đây, nhưng đây là phần sau khi tôi nhận được ETH từ dYdX và hoàn trả khoản vay sUSD:
//This is the function called by dydx after giving us the loanfunction callFunction(address sender, Account.Info memory accountInfo, bytes memory data) external { // Use chi tokens uint256 gasStart = gasleft(); // Let the executor or the dYdX contract call this function // probably fine to restrict to dYdX require(msg.sender == executor || msg.sender == address(soloMargin)); // Decode the passed variables from the data object ( address[] memory sUSDAddresses, uint256[] memory sUSDLoanIDs, uint256 wethEstimate, uint256 usdcEstimate, uint256 ethToCoinbase ) = abi.decode(data, ( address[], uint256[], uint256, uint256, uint256 )); // Swap WETH for USDC on uniswap v3 uniswapRouter.exactOutputSingle( ISwapRouter.ExactOutputSingleParams( address(WETH), // address tokenIn; usdcTokenAddress, // address tokenOut; 3000, // uint24 fee; address(this), // address recipient; 10**18, // uint256 deadline; usdcEstimate, // uint256 amountOut; wethEstimate, // uint256 amountInMaximum; 0 // uint160 sqrtPriceLimitX96; ) ); // Swap USDC for sUSD on Curve curvePoolSUSD.exchange_underlying( 1, // usdc 3, // sUSD usdcEstimate, // usdc input 1); // min sUSD, generally not advisible to make a trade with a min amount out of 1, but its fine here I think because the overall risk of getting rekt is low // Liquidate the loans for (uint256 i = 0; i < sUSDAddresses.length; i++) { sUSDLoansAddress.liquidateUnclosedLoan(sUSDAddresses[i], sUSDLoanIDs[i]); } // We got back ETH but must pay dYdX in WETH, so deposit our whole balance sans what is paid to miners WETH.deposit{value: address(this).balance - ethToCoinbase}(); // Pay the miner block.coinbase.transfer(ethToCoinbase); // Use for chi tokens uint256 gasSpent = 21000 + gasStart - gasleft() + (16 * msg.data.length); CHI.freeFromUpTo(owner, (gasSpent + 14154) / 41947);}
Vui lòng xem rawdYdXLiquidator.sol do EmGithub cung cấp
Tôi dành nhiều thời gian để cố gắng giảm thiểu mức tiêu thụ xăng của mình. Rất nhiều lựa chọn thiết kế của tôi dựa trên điều này. Về chiến lược của hợp đồng này, có một số điểm cần lưu ý
Thay vì gửi nhiều giao dịch mua lại và trao đổi riêng biệt, tôi chọn gộp nhiều khoản hoàn trả vào một giao dịch, điều này cho phép chi phí gas cố định của tôi được phân bổ cho nhiều khoản hoàn trả, nhờ đó cải thiện khả năng cạnh tranh của gói giao dịch của tôi .
Tôi cần chuyển đổi ETH sang USDC sang sUSD một cách tốt nhất và cần quyết định có nên giao dịch trên Uniswap v3 có chức năng chính xácInput hay chính xácOutput hay không. Bất kể tôi làm gì, sẽ có sự trượt giá ở đâu đó, vì vậy tôi đã chọn chức năng precisionOutput để tránh gọi hàm balanceOf.
Có một sự đánh đổi giữa tính chính xác và hiệu quả gas của các giao dịch này. Việc thiếu độ chính xác cũng không sao miễn là tôi có thể trả các khoản vay nhanh của mình và vì tôi đang cạnh tranh về hiệu quả sử dụng gas nên tôi đã chọn tối ưu hóa cho điều đó.
Ngoài ra còn có một số điều "chiến thuật" cần lưu ý:
Phê duyệt cho mọi thứ được chuyển tiếp vào chức năng xây dựng hợp đồng của tôi. Bằng cách này, tôi có thể thanh toán chi phí hoạt động tại thời điểm triển khai và giảm lượng gas sử dụng để thực thi.
Tôi không đốt mã thông báo gas từ tài khoản của mình mà từ hợp đồng của mình, cũng để cải thiện hiệu quả sử dụng gas.
Tất cả các tên chức năng đều được chỉ định và ký tự đầu của bộ chọn chức năng là 0x và việc sử dụng bộ chọn chức năng cũng có thể giảm nhẹ việc sử dụng gas.
Công cụ sửa đổi chức năng tiêu thụ nhiều gas hơn một chút so với việc thêm trực tiếp các câu lệnh yêu cầu.
Ngoài ra còn có một số cách hợp đồng này có thể được tối ưu hóa, chẳng hạn như sử dụng phí gas thay vì chuyển coinbase.
tóm tắt:
tóm tắt:tiêu đề cấp đầu tiên
tiêu đề phụ
Tính kinh tế của việc hoàn trả MEV và tối ưu hóa giá khí đốt
Được trang bị một hợp đồng được thiết kế tốt và sự hiểu biết chắc chắn về cơ hội, tôi cần tinh chỉnh chiến lược của mình để hiện thực hóa nó. Tóm lại, ứng dụng khách MEV-Geth của Flashbot có thể điều hành một cuộc đấu giá một cách hiệu quả trong đó gói có giá gas cao nhất sẽ thắng và được đóng gói trên chuỗi. Thực tế quan trọng này có nghĩa là tôi cần tối đa hóa giá gas của gói giao dịch của mình, chứ không phải tổng số ETH mà tôi phải trả.
hình ảnh
Lưu ý rằng kết quả này hơi ngạc nhiên - chỉ trả 4 (trong số 30) khoản vay sUSD đầu tiên là tiết kiệm xăng nhất. Mỗi khoản vay sau đó sẽ tạo ra nhiều lợi nhuận tổng thể hơn, nhưng sẽ làm giảm giá gas của gói giao dịch của tôi và làm cho nó kém cạnh tranh hơn. Nếu ai đó cố gắng trả khoản vay 10 sUSD đầu tiên trong một lần, mức tiêu thụ gas của họ sẽ kém hiệu quả hơn gần 30%!
Do có ít khoản vay sETH chưa thanh toán, nên tốt nhất là chỉ thực hiện các khoản vay sUSD và không kết hợp sUSD và sETH vào một giao dịch. Do đó, sẽ có ít phần thưởng tiềm năng hơn và số tiền trả cho những người khai thác ít hơn, khiến họ tương đối kém hiệu quả về gas. Tôi không thể không mỉm cười khi nhìn thấy những khám phá này. Nếu những người khác tham lam và trả hết toàn bộ khoản vay cùng một lúc, hoặc lười biếng và trả từng phần, thì tôi thắng.
tiêu đề phụ
Đấu giá Flashbots và chiến lược đặt hàng theo gói của tôi
Xin lặp lại tình huống: Tôi muốn vừa cạnh tranh về hiệu quả sử dụng khí đốt vừa tối đa hóa lợi nhuận của mình cho mỗi lần trả nợ. Chiến lược tốt nhất là gửi một số khoản hoàn trả cho mỗi gói, trong một số gói. Các gói này được đánh giá riêng lẻ trong phiên đấu giá Flashbots. Tuy nhiên, mọi giao dịch đều phụ thuộc vào giao dịch từ pDAO, khiến cho khoản vay có thể được trả bởi bất kỳ ai.
Nếu giao dịch pDAO không có trong gói, gói sẽ không thành công. Nhưng nếu tôi có các giao dịch pDAO trong mỗi gói, thì chỉ một gói sẽ thành công. Nghĩa là, sau khi gói giao dịch được gói thành công, tất cả các gói khác đều không hợp lệ vì chúng sẽ cố gắng gói gấp đôi giao dịch pDAO. Do đó, tôi cần tìm cách chỉ gửi các giao dịch pDAO trong gói giao dịch đầu tiên của mình, nhưng đồng thời đảm bảo rằng các gói giao dịch khác của tôi không bị lỗi và bị loại bỏ vì chúng không có giao dịch pDAO.
Giải pháp nằm ở sự tinh tế của phiên đấu giá Flashbots. Flashbots đã thực hiện hai vòng mô phỏng trước khi công cụ tìm kiếm bắt đầu sử dụng "thủ thuật" trong các cuộc đấu giá để giảm phí khai thác sau khi gộp giao dịch. Đầu tiên, tất cả các gói giao dịch được mô phỏng riêng lẻ, giá gas của chúng được lấy và kiểm tra lỗi. Trong vòng thứ hai, các gói thành công được sắp xếp theo giá gas và mô phỏng được chạy lại để tìm các gói không nhất quán, đảm bảo rằng không có gói nào có giá gas thấp hơn dự kiến. Trừ khi bạn muốn, có thể bạn sẽ không bao giờ có gói giao dịch mà giá xăng giảm sau khi hợp nhất.
Tôi nhận ra rằng tôi có thể làm ngược lại với người tìm kiếm ở trên: thay vì các gói giao dịch của tôi trả ít xăng hơn dự kiến, họ sẽ trả nhiều tiền hơn trong vòng mô phỏng thứ hai. Để thực hiện việc này, tôi sẽ gói giao dịch pDAO trong gói đầu tiên như dự kiến, nhưng thực hiện kiểm tra bổ sung đối với các gói còn lại. Các gói giao dịch này sẽ suy ra "vòng" mà chúng sẽ được mô phỏng và sau đó thay đổi cách thực hiện của chúng cho phù hợp. Nếu họ đang ở "vòng một", họ sẽ không trả bất kỳ khoản vay nào - bởi vì họ sẽ thất bại nếu cố gắng - và sau đó trả tiền cho những người khai thác để có được giá gas cao để vượt qua vòng mô phỏng đầu tiên.
Sau khi vượt qua vòng mô phỏng đầu tiên, các gói giao dịch này sẽ theo sau gói có giao dịch pDAO trong vòng mô phỏng thứ hai. Tại thời điểm này, họ có thể trả nợ thành công. Ngoài ra, giá gas cho các gói này sẽ cao hơn dự kiến của phiên đấu giá chứ không thấp hơn, vì vậy việc thay đổi cách triển khai không phải là vấn đề ở đây.
tóm tắt:
tóm tắt:tiêu đề cấp đầu tiên
Bước năm: Thực hiện
Bây giờ tôi có dữ liệu, hợp đồng và kế hoạch mà tôi có thể bắt đầu thực hiện. Về cơ bản, tôi cần xây dựng nhiều gói giao dịch như vậy: một mặt, tôi có thể thực hiện kế hoạch của mình ở trên và mặt khác, tôi cần lắng nghe các giao dịch liên quan đến Synthetix trong nhóm giao dịch để theo dõi đuôi. Tại thời điểm này, nó chủ yếu là vấn đề triển khai.
Đầu tiên, tôi đã sử dụng Blocknative để lắng nghe tài khoản pDAO cho các giao dịch liên quan. Tôi có bất kỳ giao dịch nào được gửi từ tài khoản pDAO được gửi đến bot của tôi.
Sau đó, tôi chạy hai tập lệnh lắng nghe (một cho sETH và sUSD) cùng lúc để lấy dữ liệu từ chuỗi và đưa ra chiến lược gói giao dịch tối ưu (ví dụ: trả 3 khoản vay sETH đầu tiên trước, khoản vay nhanh X ETH, và Thực hiện tương tự cho 2 lần cuối cùng, v.v.) và tạo dữ liệu mà hợp đồng của tôi cần. Tôi cần phải làm điều này mỗi khối, trong trường hợp giá thay đổi hoặc ai đó đóng khoản vay, thay đổi chiến lược tối ưu. Những kết quả này được lưu cục bộ.
Cuối cùng, tôi có một tập lệnh thực thi nhận các giao dịch đang chờ xử lý gửi đến bot của tôi, tải kết quả của chiến lược gói tối ưu từ tập lệnh nghe của tôi, tự động tạo các gói và gửi chúng đến Flashbot.
tiêu đề cấp đầu tiên
Bước sáu: Thời điểm đã đến
Thật thú vị, ai đó đã cố gắng lừa bot thất bại sớm bằng cách gửi một giao dịch đến hợp đồng có liên quan. Tôi không chắc liệu điều này có xảy ra với bot của người khác hay không, nhưng bot của tôi đã không được dỗ dành thành công.
Vài giờ sau, pDAO đã gửi giao dịch thực. Sau nhiều tuần nghiên cứu và chuẩn bị, tôi biết thời điểm đã đến. Về phía tôi, mọi thứ đang diễn ra tốt đẹp: tập lệnh trình nghe của tôi chạy tốt, các giao dịch được nhận, các gói giao dịch được xây dựng và cam kết.
.....và rồi tai nạn xảy ra. Không có khối Flashbot nào được khai thác cho nhiều khối liên tiếp. Tôi không chỉ mất cơ hội mà còn không có trình thu thập dữ liệu Flashbot nào giành được cơ hội. Không có Flashbot nào chặn ở đầu khối, một bot nhóm giao dịch đầy tham vọng bước vào và lấy tất cả các khoản vay có lãi.
Dù thua nhưng tôi nghĩ cách tiếp cận của mình vẫn đúng. Điểm mạnh của tôi nằm ở chiến lược và phát hiện ra các cơ hội mới, không phải ở PGA (Đấu giá Gas Tốt nhất). Vì vậy, sử dụng Flashbot đã cho tôi cơ hội chiến thắng cao nhất. Với việc áp dụng rộng rãi Flashbots, thật không may khi có nhiều khối không phải Flashbots liên tiếp.
MEV đôi khi được coi là lĩnh vực của những siêu lập trình viên khó nắm bắt, nhưng nó không nhất thiết phải như vậy. Nó có thể vui vẻ và thú vị. Và các quy tắc của trò chơi, nếu bạn đang tìm kiếm chúng, có thể nói là cởi mở. Bài viết này nói về quá trình tôi tìm hiểu luật chơi của các trò chơi mà tôi chơi, từ đó tôi nghĩ ra một chiến lược và cuối cùng là thực hiện chiến lược đó. Mặc dù thua nhưng tôi đã học được rất nhiều điều và vui vẻ trong suốt chặng đường. Tôi hy vọng bạn cũng có thể, và hy vọng bạn sẽ tham gia cùng tôi trong vòng trò chơi tiếp theo.
Liên kết gốc: https://bertcmiller.com/2021/09/05/mev-synthetix.html


