From c6665a97615462a059c617f4593aca8c24f19506 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 30 Dec 2023 15:54:00 -0800 Subject: [PATCH 1/4] added executeOnLoss boolean flag to submitFastBid that will enable searchers to execute their transactions even if they lose the auction --- .../FastLaneAuctionHandler.sol | 102 ++++++++++-------- .../interfaces/IFastLaneAuctionHandler.sol | 2 +- lib/openzeppelin-contracts | 2 +- lib/openzeppelin-contracts-upgradeable | 2 +- 4 files changed, 58 insertions(+), 50 deletions(-) diff --git a/contracts/auction-handler/FastLaneAuctionHandler.sol b/contracts/auction-handler/FastLaneAuctionHandler.sol index 20cf0f2..e5dae3f 100644 --- a/contracts/auction-handler/FastLaneAuctionHandler.sol +++ b/contracts/auction-handler/FastLaneAuctionHandler.sol @@ -80,6 +80,7 @@ struct PGAData { uint64 lowestGasPrice; uint64 lowestFastPrice; uint64 lowestTotalPrice; + bool paid; } interface ISearcherContract { @@ -214,24 +215,68 @@ contract FastLaneAuctionHandler is FastLaneAuctionHandlerEvents { /// @notice Submits a fast bid /// @dev Will not revert /// @param fastGasPrice Bonus gasPrice rate that Searcher commits to pay to validator for gas used by searcher's call + /// @param executeOnLoss Boolean flag that enables Searcher calls to execute even if they lost the auction. /// @param searcherToAddress Searcher contract address to be called on its `fastLaneCall` function. /// @param searcherCallData callData to be passed to `_searcherToAddress.fastLaneCall(fastPrice,msg.sender,callData)` function submitFastBid( - uint256 fastGasPrice, // Value commited to be paid at the end of execution + uint256 fastGasPrice, // surplus gasprice commited to be paid at the end of execution + bool executeOnLoss, // If true, execute even if searcher lost auction address searcherToAddress, bytes calldata searcherCallData - ) external payable checkPGA(fastGasPrice) onlyEOA nonReentrant { + ) external payable onlyEOA nonReentrant { if (searcherToAddress == address(this) || searcherToAddress == msg.sender) revert RelaySearcherWrongParams(); - // Use a try/catch pattern so that tx.gasprice and bidAmount can be saved to verify that - // proper transaction ordering is being followed. - try this.fastBidWrapper{value: msg.value}( - msg.sender, fastGasPrice, searcherToAddress, searcherCallData - ) returns (uint256 bidAmount) { - emit RelayFastBid(msg.sender, block.coinbase, true, bidAmount, searcherToAddress); - } catch { - emit RelayFastBid(msg.sender, block.coinbase, false, 0, searcherToAddress); + PGAData memory existing_bid = fulfilledPGAMap[block.number]; + uint256 lowestFastPrice = uint256(existing_bid.lowestFastPrice); + uint256 lowestGasPrice = uint256(existing_bid.lowestGasPrice); + uint256 lowestTotalPrice = uint256(existing_bid.lowestTotalPrice); + bool alreadyPaid = existing_bid.paid; + + // NOTE: These checks help mitigate the damage to searchers caused by relay error and adversarial validators by reverting + // early if the transactions are not sequenced pursuant to auction rules. + + // Do not execute if a fastBid tx with a lower gasPrice was executed prior to this tx in the same block. + // NOTE: This edge case should only be achieveable via validator manipulation or erratic searcher nonce management + if (lowestGasPrice != 0 && lowestGasPrice < tx.gasprice) { + emit RelayInvestigateOutcome(block.coinbase, msg.sender, block.number, lowestFastPrice, fastGasPrice, lowestGasPrice, tx.gasprice); + + // Do not execute if a fastBid tx with a lower bid amount was executed prior to this tx in the same block. + } else if (lowestTotalPrice != 0 && lowestTotalPrice <= fastGasPrice + tx.gasprice) { + emit RelayInvestigateOutcome(block.coinbase, msg.sender, block.number, lowestFastPrice, fastGasPrice, lowestGasPrice, tx.gasprice); + + // Execute the tx if there are no issues w/ ordering. + // Execute the tx if the searcher enabled executeOnLoss or if the searcher won + } else if (executeOnLoss || !alreadyPaid) { + + // Use a try/catch pattern so that tx.gasprice and bidAmount can be saved to verify that + // proper transaction ordering is being followed. + try this.fastBidWrapper{value: msg.value}( + msg.sender, fastGasPrice, searcherToAddress, searcherCallData + ) returns (uint256 bidAmount) { + + // Mark this auction as being complete to provide quicker reverts for subsequent searchers + fulfilledPGAMap[block.number] = PGAData({ + lowestGasPrice: uint64(tx.gasprice), + lowestFastPrice: uint64(fastGasPrice), + lowestTotalPrice: uint64(fastGasPrice + tx.gasprice), + paid: true + }); + + emit RelayFastBid(msg.sender, block.coinbase, true, bidAmount, searcherToAddress); + + } catch { + // Update the auction to provide quicker reverts for subsequent searchers + fulfilledPGAMap[block.number] = PGAData({ + lowestGasPrice: uint64(tx.gasprice), + lowestFastPrice: uint64(fastGasPrice), + lowestTotalPrice: uint64(fastGasPrice + tx.gasprice), + paid: alreadyPaid // carry forward any previous wins in the block + }); + + emit RelayFastBid(msg.sender, block.coinbase, false, 0, searcherToAddress); + + } } } @@ -732,41 +777,4 @@ contract FastLaneAuctionHandler is FastLaneAuctionHandlerEvents { } return false; } - - /// @notice Validates incoming PGA bid - /// @dev - /// @param fastGasPrice Amount committed to be repaid - modifier checkPGA(uint256 fastGasPrice) { - if (fastGasPrice == 0 || fastGasPrice > tx.gasprice) { - revert RelayAuctionInvalidBid(); - } - - PGAData memory existing_bid = fulfilledPGAMap[block.number]; - uint256 lowestFastPrice = uint256(existing_bid.lowestFastPrice); - uint256 lowestGasPrice = uint256(existing_bid.lowestGasPrice); - uint256 lowestTotalPrice = uint256(existing_bid.lowestTotalPrice); - - // NOTE: These checks help mitigate the damage to searchers caused by relay error and adversarial validators by reverting - // early if the transactions are not sequenced pursuant to auction rules. - - // Do not execute if a fastBid tx with a lower gasPrice was executed prior to this tx in the same block. - // NOTE: This edge case should only be achieveable via validator manipulation or erratic searcher nonce management - if (lowestGasPrice != 0 && lowestGasPrice < tx.gasprice) { - emit RelayInvestigateOutcome(block.coinbase, msg.sender, block.number, lowestFastPrice, fastGasPrice, lowestGasPrice, tx.gasprice); - - // Do not execute if a fastBid tx with a lower bid amount was executed prior to this tx in the same block. - } else if (lowestTotalPrice != 0 && lowestTotalPrice <= fastGasPrice + tx.gasprice) { - emit RelayInvestigateOutcome(block.coinbase, msg.sender, block.number, lowestFastPrice, fastGasPrice, lowestGasPrice, tx.gasprice); - - // Execute the tx if there are no issues w/ ordering. - } else { - _; - // Mark this auction as being complete to provide quicker reverts for subsequent searchers - fulfilledPGAMap[block.number] = PGAData({ - lowestGasPrice: uint64(tx.gasprice), - lowestFastPrice: uint64(fastGasPrice), - lowestTotalPrice: uint64(fastGasPrice + tx.gasprice) - }); - } - } } \ No newline at end of file diff --git a/contracts/interfaces/IFastLaneAuctionHandler.sol b/contracts/interfaces/IFastLaneAuctionHandler.sol index 455744f..ff634f7 100644 --- a/contracts/interfaces/IFastLaneAuctionHandler.sol +++ b/contracts/interfaces/IFastLaneAuctionHandler.sol @@ -85,7 +85,7 @@ interface IFastLaneAuctionHandler { address searcherToAddress, bytes memory searcherCallData ) external payable; - function submitFastBid(uint256 fastGasPrice, address searcherToAddress, bytes memory searcherCallData) + function submitFastBid(uint256 fastGasPrice, bool executeOnLoss, address searcherToAddress, bytes memory searcherCallData) external payable; function submitFlashBid( diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 0a25c19..3bd9ed3 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 0a25c1940ca220686588c4af3ec526f725fe2582 +Subproject commit 3bd9ed377e738a1cc66cca180a2a26426c63b8dc diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index 58fa0f8..b6becf8 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 58fa0f81c4036f1a3b616fdffad2fd27e5d5ce21 +Subproject commit b6becf82d734bc66fec9cf25a48d598356f3bf11 From ef47e4d5bdfdd5fc657ebf31526db54496a37cf2 Mon Sep 17 00:00:00 2001 From: jj1980a Date: Thu, 4 Jan 2024 09:30:31 +0800 Subject: [PATCH 2/4] bump solc to 0.8.20 --- contracts/FastLaneFactory.sol | 2 +- contracts/legacy/FastLaneLegacyAuction.sol | 2 +- foundry.toml | 2 +- script/legacy-script/UUPSDeploy.s.sol | 2 +- test/legacy-test/PFLDeploy.t.sol | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/FastLaneFactory.sol b/contracts/FastLaneFactory.sol index 0cc2705..410bc8d 100644 --- a/contracts/FastLaneFactory.sol +++ b/contracts/FastLaneFactory.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Unlicensed -pragma solidity 0.8.16; +pragma solidity ^0.8.16; import {FastLaneLegacyAuction} from "./legacy/FastLaneLegacyAuction.sol"; diff --git a/contracts/legacy/FastLaneLegacyAuction.sol b/contracts/legacy/FastLaneLegacyAuction.sol index f3f181d..fe33d57 100644 --- a/contracts/legacy/FastLaneLegacyAuction.sol +++ b/contracts/legacy/FastLaneLegacyAuction.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: Unlicensed -pragma solidity 0.8.16; +pragma solidity ^0.8.16; import "openzeppelin-contracts/contracts/utils/Address.sol"; import { SafeTransferLib, ERC20 } from "solmate/utils/SafeTransferLib.sol"; diff --git a/foundry.toml b/foundry.toml index 65c47be..60d8ed7 100644 --- a/foundry.toml +++ b/foundry.toml @@ -15,5 +15,5 @@ bytecode_hash = "none" names = true sizes = true gas_price = 60000000000 -solc_version = '0.8.16' +solc_version = '0.8.20' # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/script/legacy-script/UUPSDeploy.s.sol b/script/legacy-script/UUPSDeploy.s.sol index c4a113a..90fb7a2 100644 --- a/script/legacy-script/UUPSDeploy.s.sol +++ b/script/legacy-script/UUPSDeploy.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; +pragma solidity ^0.8.16; import "forge-std/Test.sol"; import "forge-std/Script.sol"; diff --git a/test/legacy-test/PFLDeploy.t.sol b/test/legacy-test/PFLDeploy.t.sol index d6e0276..6c92f68 100644 --- a/test/legacy-test/PFLDeploy.t.sol +++ b/test/legacy-test/PFLDeploy.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.16; +pragma solidity ^0.8.16; import "contracts/legacy/FastLaneLegacyAuction.sol"; From 66dc43539d9abf2eb199ccaf1900b3bbb8a17239 Mon Sep 17 00:00:00 2001 From: jj1980a Date: Thu, 4 Jan 2024 09:30:50 +0800 Subject: [PATCH 3/4] fix test --- test/PFL_AuctionHandler.t.sol | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/PFL_AuctionHandler.t.sol b/test/PFL_AuctionHandler.t.sol index 32ceee8..e1f5ce2 100644 --- a/test/PFL_AuctionHandler.t.sol +++ b/test/PFL_AuctionHandler.t.sol @@ -274,22 +274,21 @@ contract PFLAuctionHandlerTest is PFLHelper, FastLaneAuctionHandlerEvents { function testSubmitFastBid() public { vm.deal(SEARCHER_ADDRESS1, 150 ether); - vm.startPrank(SEARCHER_ADDRESS1); + vm.startPrank(SEARCHER_ADDRESS1, SEARCHER_ADDRESS1); SearcherContractExample SCE = new SearcherContractExample(); - SearcherRepayerOverpayerDouble SCEOverpay = new SearcherRepayerOverpayerDouble(); - bytes memory searcherCallData = abi.encodeWithSignature("doStuff(address,uint256)", vm.addr(12), 1337); + SCE.setPFLAuctionAddress(address(PFR)); - // Test all rejection cases first - vm.txGasPrice(10 gwei); - vm.expectRevert(FastLaneAuctionHandlerEvents.RelayAuctionInvalidBid.selector); - PFR.submitFastBid(20 gwei, address(SCE), searcherCallData); + bytes memory searcherCallData = abi.encodeWithSignature("doStuff(address,uint256)", vm.addr(12), 1337); - // Then make a successful bid with medium payment + // RelaySearcherWrongParams revert + vm.expectRevert(FastLaneAuctionHandlerEvents.RelaySearcherWrongParams.selector); + PFR.submitFastBid(20 gwei, false, address(PFR), searcherCallData); // searcherToAddress = PFR - // Make sure higher bids are rejected + vm.expectRevert(FastLaneAuctionHandlerEvents.RelaySearcherWrongParams.selector); + PFR.submitFastBid(20 gwei, false, SEARCHER_ADDRESS1, searcherCallData); // searcherToAddress = searcher's EOA - // And check if lower bids are accepted + vm.stopPrank(); } function testWrongSearcherRepay() public { From 5ff05791ee25f8c8ada6d6565f03c3a1c163ab7b Mon Sep 17 00:00:00 2001 From: jj1980a Date: Thu, 4 Jan 2024 09:39:20 +0800 Subject: [PATCH 4/4] comment deprecated function in legacy contract --- contracts/legacy/FastLaneLegacyAuction.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/legacy/FastLaneLegacyAuction.sol b/contracts/legacy/FastLaneLegacyAuction.sol index fe33d57..0eb1e42 100644 --- a/contracts/legacy/FastLaneLegacyAuction.sol +++ b/contracts/legacy/FastLaneLegacyAuction.sol @@ -174,7 +174,7 @@ contract FastLaneLegacyAuction is Initializable, OwnableUpgradeable , UUPSUpgrad } function initialize(address _newOwner) public initializer { - __Ownable_init(); + // __Ownable_init(); __UUPSUpgradeable_init(); _transferOwnership(_newOwner); }