From 11f4230b703d5bf55114fc6940d9ab0f27327ec7 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 7 Dec 2023 15:09:01 +0100 Subject: [PATCH] Format e2e contracts and ci check (#118) * Format e2e contracts and ci check * Add helper method to format * Fix issue --- .github/workflows/checks.yml | 3 + Makefile | 3 + suave/e2e/contracts.go | 1 - suave/e2e/workflow_test.go | 8 +- suave/sol/libraries/SuaveAbi.sol | 19 - suave/sol/standard_peekers/bids.sol | 615 +++++++++++++++------------- 6 files changed, 334 insertions(+), 315 deletions(-) delete mode 100644 suave/sol/libraries/SuaveAbi.sol diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 538a74fcfb..e181856538 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -32,6 +32,9 @@ jobs: - name: Lint run: make lint + - name: Lint contracts + run: cd suave && forge fmt --check + - name: Build contracts run: cd suave && forge build diff --git a/Makefile b/Makefile index 470296bb4d..3d9a8f2307 100644 --- a/Makefile +++ b/Makefile @@ -54,3 +54,6 @@ devnet-up: devnet-down: docker-compose -f ./suave/devenv/docker-compose.yml down + +fmt-contracts: + cd suave && forge fmt diff --git a/suave/e2e/contracts.go b/suave/e2e/contracts.go index acafc71d57..ac03d0904e 100644 --- a/suave/e2e/contracts.go +++ b/suave/e2e/contracts.go @@ -18,7 +18,6 @@ var ( MevShareBundleSenderContract = newArtifact("bids.sol/MevShareBundleSenderContract.json") buildEthBlockContract = newArtifact("bids.sol/EthBlockBidContract.json") ethBlockBidSenderContract = newArtifact("bids.sol/EthBlockBidSenderContract.json") - suaveLibContract = newArtifact("SuaveAbi.sol/SuaveAbi.json") exampleCallSourceContract = newArtifact("example.sol/ExampleEthCallSource.json") exampleCallTargetContract = newArtifact("example.sol/ExampleEthCallTarget.json") ) diff --git a/suave/e2e/workflow_test.go b/suave/e2e/workflow_test.go index fd99e6c340..cd7b2ad1a4 100644 --- a/suave/e2e/workflow_test.go +++ b/suave/e2e/workflow_test.go @@ -735,7 +735,7 @@ func TestBlockBuildingPrecompiles(t *testing.T) { require.NoError(t, err) { // Test the bundle simulation precompile through eth_call - calldata, err := suaveLibContract.Abi.Methods["simulateBundle"].Inputs.Pack(bundleBytes) + calldata, err := artifacts.SuaveAbi.Methods["simulateBundle"].Inputs.Pack(bundleBytes) require.NoError(t, err) var simResult hexutil.Bytes @@ -785,7 +785,7 @@ func TestBlockBuildingPrecompiles(t *testing.T) { FeeRecipient: common.Address{0x42}, } - packed, err := suaveLibContract.Abi.Methods["buildEthBlock"].Inputs.Pack(payloadArgsTuple, bid.Id, "") + packed, err := artifacts.SuaveAbi.Methods["buildEthBlock"].Inputs.Pack(payloadArgsTuple, bid.Id, "") require.NoError(t, err) var simResult hexutil.Bytes @@ -799,7 +799,7 @@ func TestBlockBuildingPrecompiles(t *testing.T) { require.NotNil(t, simResult) - unpacked, err := suaveLibContract.Abi.Methods["buildEthBlock"].Outputs.Unpack(simResult) + unpacked, err := artifacts.SuaveAbi.Methods["buildEthBlock"].Outputs.Unpack(simResult) require.NoError(t, err) // TODO: test builder bid @@ -1446,7 +1446,7 @@ func requireNoRpcError(t *testing.T, rpcErr error) { require.NoError(t, rpcErr, decodedError) } - unpacked, err := suaveLibContract.Abi.Errors["PeekerReverted"].Inputs.Unpack(decodedError[4:]) + unpacked, err := artifacts.SuaveAbi.Errors["PeekerReverted"].Inputs.Unpack(decodedError[4:]) if err != nil { require.NoError(t, err, rpcErr.Error()) } else { diff --git a/suave/sol/libraries/SuaveAbi.sol b/suave/sol/libraries/SuaveAbi.sol deleted file mode 100644 index c2c552cc9c..0000000000 --- a/suave/sol/libraries/SuaveAbi.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.8.8; - -import {Suave} from "./Suave.sol"; - -contract SuaveAbi { - error PeekerReverted(address, bytes); - - function newBid(uint64 decryptionCondition, address[] memory allowedPeekers, address[] memory allowedStores, string memory BidType) external view returns (Suave.Bid memory) {} - function fetchBids(uint64 cond, string memory namespace) external view returns (Suave.Bid[] memory) {} - function confidentialStore(Suave.BidId bidId, string memory key, bytes memory data) external view {} - function confidentialRetrieve(Suave.BidId bidId, string memory key) external view returns (bytes memory) {} - function signEthTransaction(bytes memory txn, string memory chainId, string memory signingKey) external view returns (bytes memory) {} - function simulateBundle(bytes memory bundleData) external view returns (uint64) {} - function extractHint(bytes memory bundleData) external view returns (bytes memory) {} - function buildEthBlock(Suave.BuildBlockArgs memory blockArgs, Suave.BidId bid, string memory namespace) external view returns (bytes memory, bytes memory) {} - function submitEthBlockBidToRelay(string memory relayUrl, bytes memory builderBid) external view returns (bytes memory) {} - function fillMevShareBundle(Suave.BidId bidId) external view returns (bytes memory) {} - function submitBundleJsonRPC(string memory url, string memory method, bytes memory params) external view returns (bytes memory) {} -} diff --git a/suave/sol/standard_peekers/bids.sol b/suave/sol/standard_peekers/bids.sol index b57bd1755e..4c866a5a73 100644 --- a/suave/sol/standard_peekers/bids.sol +++ b/suave/sol/standard_peekers/bids.sol @@ -3,324 +3,357 @@ pragma solidity ^0.8.8; import "../libraries/Suave.sol"; contract AnyBidContract { + event BidEvent(Suave.BidId bidId, uint64 decryptionCondition, address[] allowedPeekers); - event BidEvent( - Suave.BidId bidId, - uint64 decryptionCondition, - address[] allowedPeekers - ); - - function fetchBidConfidentialBundleData() public returns (bytes memory) { - require(Suave.isConfidential()); - - bytes memory confidentialInputs = Suave.confidentialInputs(); - return abi.decode(confidentialInputs, (bytes)); - } - - // Bids to this contract should not be trusted! - function emitBid(Suave.Bid calldata bid) public { - emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); - } + function fetchBidConfidentialBundleData() public returns (bytes memory) { + require(Suave.isConfidential()); + + bytes memory confidentialInputs = Suave.confidentialInputs(); + return abi.decode(confidentialInputs, (bytes)); + } + + // Bids to this contract should not be trusted! + function emitBid(Suave.Bid calldata bid) public { + emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); + } } contract BundleBidContract is AnyBidContract { + function newBid(uint64 decryptionCondition, address[] memory bidAllowedPeekers, address[] memory bidAllowedStores) + external + payable + returns (bytes memory) + { + require(Suave.isConfidential()); - function newBid(uint64 decryptionCondition, address[] memory bidAllowedPeekers, address[] memory bidAllowedStores) external payable returns (bytes memory) { - require(Suave.isConfidential()); + bytes memory bundleData = this.fetchBidConfidentialBundleData(); - bytes memory bundleData = this.fetchBidConfidentialBundleData(); + uint64 egp = Suave.simulateBundle(bundleData); - uint64 egp = Suave.simulateBundle(bundleData); + Suave.Bid memory bid = + Suave.newBid(decryptionCondition, bidAllowedPeekers, bidAllowedStores, "default:v0:ethBundles"); - Suave.Bid memory bid = Suave.newBid(decryptionCondition, bidAllowedPeekers, bidAllowedStores, "default:v0:ethBundles"); + Suave.confidentialStore(bid.id, "default:v0:ethBundles", bundleData); + Suave.confidentialStore(bid.id, "default:v0:ethBundleSimResults", abi.encode(egp)); - Suave.confidentialStore(bid.id, "default:v0:ethBundles", bundleData); - Suave.confidentialStore(bid.id, "default:v0:ethBundleSimResults", abi.encode(egp)); + return emitAndReturn(bid, bundleData); + } - return emitAndReturn(bid, bundleData); - } - - function emitAndReturn(Suave.Bid memory bid, bytes memory) internal virtual returns (bytes memory) { - emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); - return bytes.concat(this.emitBid.selector, abi.encode(bid)); - } + function emitAndReturn(Suave.Bid memory bid, bytes memory) internal virtual returns (bytes memory) { + emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); + return bytes.concat(this.emitBid.selector, abi.encode(bid)); + } } contract EthBundleSenderContract is BundleBidContract { - string[] public builderUrls; - - constructor(string[] memory builderUrls_) { - builderUrls = builderUrls_; - } - - function emitAndReturn(Suave.Bid memory bid, bytes memory bundleData) internal virtual override returns (bytes memory) { - for (uint i = 0; i < builderUrls.length; i++) { - Suave.submitBundleJsonRPC(builderUrls[i], "eth_sendBundle", bundleData); - } - - return BundleBidContract.emitAndReturn(bid, bundleData); - } + string[] public builderUrls; + + constructor(string[] memory builderUrls_) { + builderUrls = builderUrls_; + } + + function emitAndReturn(Suave.Bid memory bid, bytes memory bundleData) + internal + virtual + override + returns (bytes memory) + { + for (uint256 i = 0; i < builderUrls.length; i++) { + Suave.submitBundleJsonRPC(builderUrls[i], "eth_sendBundle", bundleData); + } + + return BundleBidContract.emitAndReturn(bid, bundleData); + } } contract MevShareBidContract is AnyBidContract { - - event HintEvent( - Suave.BidId bidId, - bytes hint - ); - - event MatchEvent( - Suave.BidId matchBidId, - bytes matchHint - ); - - function newBid(uint64 decryptionCondition, address[] memory bidAllowedPeekers, address[] memory bidAllowedStores) external payable returns (bytes memory) { - // 0. check confidential execution - require(Suave.isConfidential()); - - // 1. fetch bundle data - bytes memory bundleData = this.fetchBidConfidentialBundleData(); - - // 2. sim bundle - uint64 egp = Suave.simulateBundle(bundleData); - - // 3. extract hint - bytes memory hint = Suave.extractHint(bundleData); - - // // 4. store bundle and sim results - Suave.Bid memory bid = Suave.newBid(decryptionCondition, bidAllowedPeekers, bidAllowedStores, "mevshare:v0:unmatchedBundles"); - Suave.confidentialStore(bid.id, "mevshare:v0:ethBundles", bundleData); - Suave.confidentialStore(bid.id, "mevshare:v0:ethBundleSimResults", abi.encode(egp)); - emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); - emit HintEvent(bid.id, hint); - - // // 5. return "callback" to emit hint onchain - return bytes.concat(this.emitBidAndHint.selector, abi.encode(bid, hint)); - } - - function emitBidAndHint(Suave.Bid calldata bid, bytes memory hint) public { - emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); - emit HintEvent(bid.id, hint); - } - - function newMatch(uint64 decryptionCondition, address[] memory bidAllowedPeekers, address[] memory bidAllowedStores, Suave.BidId shareBidId) external payable returns (bytes memory) { - // WARNING : this function will copy the original mev share bid - // into a new key with potentially different permsissions - - require(Suave.isConfidential()); - // 1. fetch confidential data - bytes memory matchBundleData = this.fetchBidConfidentialBundleData(); - - // 2. sim match alone for validity - uint64 egp = Suave.simulateBundle(matchBundleData); - - // 3. extract hint - bytes memory matchHint = Suave.extractHint(matchBundleData); - - Suave.Bid memory bid = Suave.newBid(decryptionCondition, bidAllowedPeekers, bidAllowedStores, "mevshare:v0:matchBids"); - Suave.confidentialStore(bid.id, "mevshare:v0:ethBundles", matchBundleData); - Suave.confidentialStore(bid.id, "mevshare:v0:ethBundleSimResults", abi.encode(0)); - - //4. merge bids - Suave.BidId[] memory bids = new Suave.BidId[](2); - bids[0] = shareBidId; - bids[1] = bid.id; - Suave.confidentialStore(bid.id, "mevshare:v0:mergedBids", abi.encode(bids)); - - return emitMatchBidAndHint(bid, matchHint); - } - - function emitMatchBidAndHint(Suave.Bid memory bid, bytes memory matchHint) internal virtual returns (bytes memory) { - emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); - emit MatchEvent(bid.id, matchHint); - - return bytes.concat(this.emitBid.selector, abi.encode(bid)); - } + event HintEvent(Suave.BidId bidId, bytes hint); + + event MatchEvent(Suave.BidId matchBidId, bytes matchHint); + + function newBid(uint64 decryptionCondition, address[] memory bidAllowedPeekers, address[] memory bidAllowedStores) + external + payable + returns (bytes memory) + { + // 0. check confidential execution + require(Suave.isConfidential()); + + // 1. fetch bundle data + bytes memory bundleData = this.fetchBidConfidentialBundleData(); + + // 2. sim bundle + uint64 egp = Suave.simulateBundle(bundleData); + + // 3. extract hint + bytes memory hint = Suave.extractHint(bundleData); + + // // 4. store bundle and sim results + Suave.Bid memory bid = + Suave.newBid(decryptionCondition, bidAllowedPeekers, bidAllowedStores, "mevshare:v0:unmatchedBundles"); + Suave.confidentialStore(bid.id, "mevshare:v0:ethBundles", bundleData); + Suave.confidentialStore(bid.id, "mevshare:v0:ethBundleSimResults", abi.encode(egp)); + emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); + emit HintEvent(bid.id, hint); + + // // 5. return "callback" to emit hint onchain + return bytes.concat(this.emitBidAndHint.selector, abi.encode(bid, hint)); + } + + function emitBidAndHint(Suave.Bid calldata bid, bytes memory hint) public { + emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); + emit HintEvent(bid.id, hint); + } + + function newMatch( + uint64 decryptionCondition, + address[] memory bidAllowedPeekers, + address[] memory bidAllowedStores, + Suave.BidId shareBidId + ) external payable returns (bytes memory) { + // WARNING : this function will copy the original mev share bid + // into a new key with potentially different permsissions + + require(Suave.isConfidential()); + // 1. fetch confidential data + bytes memory matchBundleData = this.fetchBidConfidentialBundleData(); + + // 2. sim match alone for validity + uint64 egp = Suave.simulateBundle(matchBundleData); + + // 3. extract hint + bytes memory matchHint = Suave.extractHint(matchBundleData); + + Suave.Bid memory bid = + Suave.newBid(decryptionCondition, bidAllowedPeekers, bidAllowedStores, "mevshare:v0:matchBids"); + Suave.confidentialStore(bid.id, "mevshare:v0:ethBundles", matchBundleData); + Suave.confidentialStore(bid.id, "mevshare:v0:ethBundleSimResults", abi.encode(0)); + + //4. merge bids + Suave.BidId[] memory bids = new Suave.BidId[](2); + bids[0] = shareBidId; + bids[1] = bid.id; + Suave.confidentialStore(bid.id, "mevshare:v0:mergedBids", abi.encode(bids)); + + return emitMatchBidAndHint(bid, matchHint); + } + + function emitMatchBidAndHint(Suave.Bid memory bid, bytes memory matchHint) + internal + virtual + returns (bytes memory) + { + emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); + emit MatchEvent(bid.id, matchHint); + + return bytes.concat(this.emitBid.selector, abi.encode(bid)); + } } contract MevShareBundleSenderContract is MevShareBidContract { - string[] public builderUrls; - - constructor(string[] memory builderUrls_) { - builderUrls = builderUrls_; - } - - function emitMatchBidAndHint(Suave.Bid memory bid, bytes memory matchHint) internal virtual override returns (bytes memory) { - bytes memory bundleData = Suave.fillMevShareBundle(bid.id); - for (uint i = 0; i < builderUrls.length; i++) { - Suave.submitBundleJsonRPC(builderUrls[i], "mev_sendBundle", bundleData); - } - - return MevShareBidContract.emitMatchBidAndHint(bid, matchHint); - } + string[] public builderUrls; + + constructor(string[] memory builderUrls_) { + builderUrls = builderUrls_; + } + + function emitMatchBidAndHint(Suave.Bid memory bid, bytes memory matchHint) + internal + virtual + override + returns (bytes memory) + { + bytes memory bundleData = Suave.fillMevShareBundle(bid.id); + for (uint256 i = 0; i < builderUrls.length; i++) { + Suave.submitBundleJsonRPC(builderUrls[i], "mev_sendBundle", bundleData); + } + + return MevShareBidContract.emitMatchBidAndHint(bid, matchHint); + } } /* Not tested or implemented on the precompile side */ struct EgpBidPair { - uint64 egp; // in wei, beware overflow - Suave.BidId bidId; + uint64 egp; // in wei, beware overflow + Suave.BidId bidId; } contract EthBlockBidContract is AnyBidContract { - - event BuilderBoostBidEvent( - Suave.BidId bidId, - bytes builderBid - ); - - function idsEqual(Suave.BidId _l, Suave.BidId _r) public pure returns (bool) { - bytes memory l = abi.encodePacked(_l); - bytes memory r = abi.encodePacked(_r); - for (uint i = 0; i < l.length; i++) { - if (bytes(l)[i] != r[i]) { - return false; - } - } - - return true; - } - - function buildMevShare(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight) public returns (bytes memory) { - require(Suave.isConfidential()); - - Suave.Bid[] memory allShareMatchBids = Suave.fetchBids(blockHeight, "mevshare:v0:matchBids"); - Suave.Bid[] memory allShareUserBids = Suave.fetchBids(blockHeight, "mevshare:v0:unmatchedBundles"); - - if (allShareUserBids.length == 0) { - revert Suave.PeekerReverted(address(this), "no bids"); - } - - Suave.Bid[] memory allBids = new Suave.Bid[](allShareUserBids.length); - for (uint i = 0; i < allShareUserBids.length; i++) { - // TODO: sort matches by egp first! - Suave.Bid memory bidToInsert = allShareUserBids[i]; // will be updated with the best match if any - for (uint j = 0; j < allShareMatchBids.length; j++) { - // TODO: should be done once at the start and sorted - Suave.BidId[] memory mergedBidIds = abi.decode(Suave.confidentialRetrieve(allShareMatchBids[j].id, "mevshare:v0:mergedBids"), (Suave.BidId[])); - if (idsEqual(mergedBidIds[0], allShareUserBids[i].id)) { - bidToInsert = allShareMatchBids[j]; - break; - } - } - allBids[i] = bidToInsert; - } - - EgpBidPair[] memory bidsByEGP = new EgpBidPair[](allBids.length); - for (uint i = 0; i < allBids.length; i++) { - bytes memory simResults = Suave.confidentialRetrieve(allBids[i].id, "mevshare:v0:ethBundleSimResults"); - uint64 egp = abi.decode(simResults, (uint64)); - bidsByEGP[i] = EgpBidPair(egp, allBids[i].id); - } - - // Bubble sort, cause why not - uint n = bidsByEGP.length; - for (uint i = 0; i < n - 1; i++) { - for (uint j = i + 1; j < n; j++) { - if (bidsByEGP[i].egp < bidsByEGP[j].egp) { - EgpBidPair memory temp = bidsByEGP[i]; - bidsByEGP[i] = bidsByEGP[j]; - bidsByEGP[j] = temp; - } - } - } - - Suave.BidId[] memory allBidIds = new Suave.BidId[](allBids.length); - for (uint i = 0; i < bidsByEGP.length; i++) { - allBidIds[i] = bidsByEGP[i].bidId; - } - - return buildAndEmit(blockArgs, blockHeight, allBidIds, "mevshare:v0"); - } - - function buildFromPool(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight) public returns (bytes memory) { - require(Suave.isConfidential()); - - Suave.Bid[] memory allBids = Suave.fetchBids(blockHeight, "default:v0:ethBundles"); - if (allBids.length == 0) { - revert Suave.PeekerReverted(address(this), "no bids"); - } - - EgpBidPair[] memory bidsByEGP = new EgpBidPair[](allBids.length); - for (uint i = 0; i < allBids.length; i++) { - bytes memory simResults = Suave.confidentialRetrieve(allBids[i].id, "default:v0:ethBundleSimResults"); - uint64 egp = abi.decode(simResults, (uint64)); - bidsByEGP[i] = EgpBidPair(egp, allBids[i].id); - } - - // Bubble sort, cause why not - uint n = bidsByEGP.length; - for (uint i = 0; i < n - 1; i++) { - for (uint j = i + 1; j < n; j++) { - if (bidsByEGP[i].egp < bidsByEGP[j].egp) { - EgpBidPair memory temp = bidsByEGP[i]; - bidsByEGP[i] = bidsByEGP[j]; - bidsByEGP[j] = temp; - } - } - } - - Suave.BidId[] memory allBidIds = new Suave.BidId[](allBids.length); - for (uint i = 0; i < bidsByEGP.length; i++) { - allBidIds[i] = bidsByEGP[i].bidId; - } - - return buildAndEmit(blockArgs, blockHeight, allBidIds, ""); - } - - function buildAndEmit(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight, Suave.BidId[] memory bids, string memory namespace) public virtual returns (bytes memory) { - require(Suave.isConfidential()); - - (Suave.Bid memory blockBid, bytes memory builderBid) = this.doBuild(blockArgs, blockHeight, bids, namespace); - - emit BuilderBoostBidEvent(blockBid.id, builderBid); - emit BidEvent(blockBid.id, blockBid.decryptionCondition, blockBid.allowedPeekers); - return bytes.concat(this.emitBuilderBidAndBid.selector, abi.encode(blockBid, builderBid)); - } - - function doBuild(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight, Suave.BidId[] memory bids, string memory namespace) public view returns (Suave.Bid memory, bytes memory) { - address[] memory allowedPeekers = new address[](2); - allowedPeekers[0] = address(this); - allowedPeekers[1] = Suave.BUILD_ETH_BLOCK; - - Suave.Bid memory blockBid = Suave.newBid(blockHeight, allowedPeekers, allowedPeekers, "default:v0:mergedBids"); - Suave.confidentialStore(blockBid.id, "default:v0:mergedBids", abi.encode(bids)); - - (bytes memory builderBid, bytes memory payload) = Suave.buildEthBlock(blockArgs, blockBid.id, namespace); - Suave.confidentialStore(blockBid.id, "default:v0:builderPayload", payload); // only through this.unlock - - return (blockBid, builderBid); - } - - function emitBuilderBidAndBid(Suave.Bid memory bid, bytes memory builderBid) public returns (Suave.Bid memory, bytes memory) { - emit BuilderBoostBidEvent(bid.id, builderBid); - emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); - return (bid, builderBid); - } - - function unlock(Suave.BidId bidId, bytes memory signedBlindedHeader) public view returns (bytes memory) { - require(Suave.isConfidential()); - - // TODO: verify the header is correct - // TODO: incorporate protocol name - bytes memory payload = Suave.confidentialRetrieve(bidId, "default:v0:builderPayload"); - return payload; - } + event BuilderBoostBidEvent(Suave.BidId bidId, bytes builderBid); + + function idsEqual(Suave.BidId _l, Suave.BidId _r) public pure returns (bool) { + bytes memory l = abi.encodePacked(_l); + bytes memory r = abi.encodePacked(_r); + for (uint256 i = 0; i < l.length; i++) { + if (bytes(l)[i] != r[i]) { + return false; + } + } + + return true; + } + + function buildMevShare(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight) public returns (bytes memory) { + require(Suave.isConfidential()); + + Suave.Bid[] memory allShareMatchBids = Suave.fetchBids(blockHeight, "mevshare:v0:matchBids"); + Suave.Bid[] memory allShareUserBids = Suave.fetchBids(blockHeight, "mevshare:v0:unmatchedBundles"); + + if (allShareUserBids.length == 0) { + revert Suave.PeekerReverted(address(this), "no bids"); + } + + Suave.Bid[] memory allBids = new Suave.Bid[](allShareUserBids.length); + for (uint256 i = 0; i < allShareUserBids.length; i++) { + // TODO: sort matches by egp first! + Suave.Bid memory bidToInsert = allShareUserBids[i]; // will be updated with the best match if any + for (uint256 j = 0; j < allShareMatchBids.length; j++) { + // TODO: should be done once at the start and sorted + Suave.BidId[] memory mergedBidIds = abi.decode( + Suave.confidentialRetrieve(allShareMatchBids[j].id, "mevshare:v0:mergedBids"), (Suave.BidId[]) + ); + if (idsEqual(mergedBidIds[0], allShareUserBids[i].id)) { + bidToInsert = allShareMatchBids[j]; + break; + } + } + allBids[i] = bidToInsert; + } + + EgpBidPair[] memory bidsByEGP = new EgpBidPair[](allBids.length); + for (uint256 i = 0; i < allBids.length; i++) { + bytes memory simResults = Suave.confidentialRetrieve(allBids[i].id, "mevshare:v0:ethBundleSimResults"); + uint64 egp = abi.decode(simResults, (uint64)); + bidsByEGP[i] = EgpBidPair(egp, allBids[i].id); + } + + // Bubble sort, cause why not + uint256 n = bidsByEGP.length; + for (uint256 i = 0; i < n - 1; i++) { + for (uint256 j = i + 1; j < n; j++) { + if (bidsByEGP[i].egp < bidsByEGP[j].egp) { + EgpBidPair memory temp = bidsByEGP[i]; + bidsByEGP[i] = bidsByEGP[j]; + bidsByEGP[j] = temp; + } + } + } + + Suave.BidId[] memory allBidIds = new Suave.BidId[](allBids.length); + for (uint256 i = 0; i < bidsByEGP.length; i++) { + allBidIds[i] = bidsByEGP[i].bidId; + } + + return buildAndEmit(blockArgs, blockHeight, allBidIds, "mevshare:v0"); + } + + function buildFromPool(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight) public returns (bytes memory) { + require(Suave.isConfidential()); + + Suave.Bid[] memory allBids = Suave.fetchBids(blockHeight, "default:v0:ethBundles"); + if (allBids.length == 0) { + revert Suave.PeekerReverted(address(this), "no bids"); + } + + EgpBidPair[] memory bidsByEGP = new EgpBidPair[](allBids.length); + for (uint256 i = 0; i < allBids.length; i++) { + bytes memory simResults = Suave.confidentialRetrieve(allBids[i].id, "default:v0:ethBundleSimResults"); + uint64 egp = abi.decode(simResults, (uint64)); + bidsByEGP[i] = EgpBidPair(egp, allBids[i].id); + } + + // Bubble sort, cause why not + uint256 n = bidsByEGP.length; + for (uint256 i = 0; i < n - 1; i++) { + for (uint256 j = i + 1; j < n; j++) { + if (bidsByEGP[i].egp < bidsByEGP[j].egp) { + EgpBidPair memory temp = bidsByEGP[i]; + bidsByEGP[i] = bidsByEGP[j]; + bidsByEGP[j] = temp; + } + } + } + + Suave.BidId[] memory allBidIds = new Suave.BidId[](allBids.length); + for (uint256 i = 0; i < bidsByEGP.length; i++) { + allBidIds[i] = bidsByEGP[i].bidId; + } + + return buildAndEmit(blockArgs, blockHeight, allBidIds, ""); + } + + function buildAndEmit( + Suave.BuildBlockArgs memory blockArgs, + uint64 blockHeight, + Suave.BidId[] memory bids, + string memory namespace + ) public virtual returns (bytes memory) { + require(Suave.isConfidential()); + + (Suave.Bid memory blockBid, bytes memory builderBid) = this.doBuild(blockArgs, blockHeight, bids, namespace); + + emit BuilderBoostBidEvent(blockBid.id, builderBid); + emit BidEvent(blockBid.id, blockBid.decryptionCondition, blockBid.allowedPeekers); + return bytes.concat(this.emitBuilderBidAndBid.selector, abi.encode(blockBid, builderBid)); + } + + function doBuild( + Suave.BuildBlockArgs memory blockArgs, + uint64 blockHeight, + Suave.BidId[] memory bids, + string memory namespace + ) public view returns (Suave.Bid memory, bytes memory) { + address[] memory allowedPeekers = new address[](2); + allowedPeekers[0] = address(this); + allowedPeekers[1] = Suave.BUILD_ETH_BLOCK; + + Suave.Bid memory blockBid = Suave.newBid(blockHeight, allowedPeekers, allowedPeekers, "default:v0:mergedBids"); + Suave.confidentialStore(blockBid.id, "default:v0:mergedBids", abi.encode(bids)); + + (bytes memory builderBid, bytes memory payload) = Suave.buildEthBlock(blockArgs, blockBid.id, namespace); + Suave.confidentialStore(blockBid.id, "default:v0:builderPayload", payload); // only through this.unlock + + return (blockBid, builderBid); + } + + function emitBuilderBidAndBid(Suave.Bid memory bid, bytes memory builderBid) + public + returns (Suave.Bid memory, bytes memory) + { + emit BuilderBoostBidEvent(bid.id, builderBid); + emit BidEvent(bid.id, bid.decryptionCondition, bid.allowedPeekers); + return (bid, builderBid); + } + + function unlock(Suave.BidId bidId, bytes memory signedBlindedHeader) public view returns (bytes memory) { + require(Suave.isConfidential()); + + // TODO: verify the header is correct + // TODO: incorporate protocol name + bytes memory payload = Suave.confidentialRetrieve(bidId, "default:v0:builderPayload"); + return payload; + } } contract EthBlockBidSenderContract is EthBlockBidContract { - string boostRelayUrl; - - constructor(string memory boostRelayUrl_) { - boostRelayUrl = boostRelayUrl_; - } - - function buildAndEmit(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight, Suave.BidId[] memory bids, string memory namespace) public virtual override returns (bytes memory) { - require(Suave.isConfidential()); - - (Suave.Bid memory blockBid, bytes memory builderBid) = this.doBuild(blockArgs, blockHeight, bids, namespace); - Suave.submitEthBlockBidToRelay(boostRelayUrl, builderBid); - - emit BidEvent(blockBid.id, blockBid.decryptionCondition, blockBid.allowedPeekers); - return bytes.concat(this.emitBid.selector, abi.encode(blockBid)); - } + string boostRelayUrl; + + constructor(string memory boostRelayUrl_) { + boostRelayUrl = boostRelayUrl_; + } + + function buildAndEmit( + Suave.BuildBlockArgs memory blockArgs, + uint64 blockHeight, + Suave.BidId[] memory bids, + string memory namespace + ) public virtual override returns (bytes memory) { + require(Suave.isConfidential()); + + (Suave.Bid memory blockBid, bytes memory builderBid) = this.doBuild(blockArgs, blockHeight, bids, namespace); + Suave.submitEthBlockBidToRelay(boostRelayUrl, builderBid); + + emit BidEvent(blockBid.id, blockBid.decryptionCondition, blockBid.allowedPeekers); + return bytes.concat(this.emitBid.selector, abi.encode(blockBid)); + } }