From d29b33255d3f2c69039a6df4920d5b46ed9ad8d9 Mon Sep 17 00:00:00 2001 From: andytudhope <13001517+andytudhope@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:51:08 +0200 Subject: [PATCH 1/3] Need to compile contracts in order to run demo for tutorial --- examples/suave-web-demo/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/suave-web-demo/package.json b/examples/suave-web-demo/package.json index 053b298f..ad0d616a 100644 --- a/examples/suave-web-demo/package.json +++ b/examples/suave-web-demo/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "compile": "cd ../suave/contracts && forge install && forge build" }, "dependencies": { "viem": "workspace:*" From f2c2e76013da75a665d5dd66fe616628c8326560 Mon Sep 17 00:00:00 2001 From: andytudhope <13001517+andytudhope@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:00:54 +0200 Subject: [PATCH 2/3] goodbye to bids after #128 in suave-geth --- examples/suave-web-demo/src/main.ts | 6 +- examples/suave-web-demo/src/suave.ts | 26 +- examples/suave/{bids => bundles}/index.ts | 18 +- .../contracts/src/ConfidentialWithLogs.sol | 53 --- examples/suave/contracts/src/bids.sol | 359 ++++++++++++++++++ examples/suave/deployContracts.sh | 2 +- examples/suave/index.ts | 24 +- 7 files changed, 397 insertions(+), 91 deletions(-) rename examples/suave/{bids => bundles}/index.ts (80%) delete mode 100644 examples/suave/contracts/src/ConfidentialWithLogs.sol create mode 100644 examples/suave/contracts/src/bids.sol diff --git a/examples/suave-web-demo/src/main.ts b/examples/suave-web-demo/src/main.ts index 21fb9056..eaa78ec8 100644 --- a/examples/suave-web-demo/src/main.ts +++ b/examples/suave-web-demo/src/main.ts @@ -2,7 +2,7 @@ import './style.css' import viteLogo from '/vite.svg' import typescriptLogo from './typescript.svg' import flashbotsLogo from './flashbots_icon.svg' -import { setupConnectButton, setupDripFaucetButton, setupSendBidButton } from './suave' +import { setupConnectButton, setupDripFaucetButton, setupSendDataRecordButton } from './suave' import { Logo } from './components' import { custom, formatEther } from 'viem' import { getSuaveWallet, getSuaveProvider } from 'viem/chains/utils' @@ -53,9 +53,9 @@ setupConnectButton(document.querySelector('#connect')!, }) // setup other buttons once we've connected - setupSendBidButton(document.querySelector('#sendBid')!, suaveWallet, (txHash, err) => { + setupSendDataRecordButton(document.querySelector('#sendBid')!, suaveWallet, (txHash, err) => { if (err) { - console.error("error in setupSendBidButton", err) + console.error("error in setupSendDataRecordButton", err) alert(err.message + (err as any).data) } const suaveProvider = getSuaveProvider(custom(ethereum)) diff --git a/examples/suave-web-demo/src/suave.ts b/examples/suave-web-demo/src/suave.ts index 9bec1168..d54499fc 100644 --- a/examples/suave-web-demo/src/suave.ts +++ b/examples/suave-web-demo/src/suave.ts @@ -1,9 +1,9 @@ import { Address, Hex, createPublicClient, createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { suaveRigil, goerli } from "viem/chains" -import { MevShareBid } from "../../suave/bids" +import { MevShareRecord } from "../../suave/bundles" import { getSuaveWallet } from 'viem/chains/utils' -import BidContractDeployment from '../../suave/deployedAddress.json' +import MevShareContract from '../../suave/deployedAddress.json' // defaults for local suave-geth devnet: const KETTLE_ADDRESS: Address = "0xb5feafbdd752ad52afb7e1bd2e40432a485bbb7f" @@ -49,9 +49,9 @@ export function setupConnectButton(element: HTMLButtonElement, onConnect: (accou } } -export function setupSendBidButton(element: HTMLButtonElement, suaveWallet: any, onSendBid: (txHash: Hex, err?: any) => void) { - element.innerHTML = `send bid` - const sendBid = async (suaveWallet: any) => { +export function setupSendDataRecordButton(element: HTMLButtonElement, suaveWallet: any, onSendDataRecord: (txHash: Hex, err?: any) => void) { + element.innerHTML = `send data` + const sendDataRecord = async (suaveWallet: any) => { // create sample transaction; won't land onchain, but will pass payload validation const sampleTx = { type: "eip1559" as 'eip1559', @@ -66,26 +66,26 @@ export function setupSendBidButton(element: HTMLButtonElement, suaveWallet: any, const signedTx = await goerliWallet.signTransaction(sampleTx) console.log("signed goerli tx", signedTx) - // create bid & send ccr + // create data record & send ccr try { - const bid = new MevShareBid( + const dataRecord = new MevShareRecord( 1n + await goerliProvider.getBlockNumber(), signedTx, KETTLE_ADDRESS, - BidContractDeployment.address as Address, + MevShareContract.address as Address, suaveRigil.id ) - console.log(bid) - const ccr = bid.toConfidentialRequest() + console.log(dataRecord) + const ccr = dataRecord.toConfidentialRequest() const txHash = await suaveWallet.sendTransaction(ccr) console.log("sendResult", txHash) // callback with result - onSendBid(txHash) + onSendDataRecord(txHash) } catch (e) { - return onSendBid('0x', e) + return onSendDataRecord('0x', e) } } - element.addEventListener('click', () => sendBid(suaveWallet)) + element.addEventListener('click', () => sendDataRecord(suaveWallet)) } export function setupDripFaucetButton(element: HTMLButtonElement, account: Address, onFaucet: (txHash: Hex, err?: any) => void) { diff --git a/examples/suave/bids/index.ts b/examples/suave/bundles/index.ts similarity index 80% rename from examples/suave/bids/index.ts rename to examples/suave/bundles/index.ts index 7b8a98c3..41b9e4b1 100644 --- a/examples/suave/bids/index.ts +++ b/examples/suave/bundles/index.ts @@ -5,11 +5,11 @@ import { encodeFunctionData, toHex, } from 'viem' -import { suaveRigil } from '../../../src/chains' +import { suaveRigil } from 'viem/chains' import { SuaveTxTypes, TransactionRequestSuave } from '../../../src/chains/suave/types' -import MevShareBidContract from '../contracts/out/bids.sol/MevShareBidContract.json' +import MevShareContract from '../contracts/out/bids.sol/MevShareContract.json' -export interface MevShareBid { +export interface MevShareRecord { allowedPeekers: Address[] allowedStores: Address[] blockNumber: bigint @@ -19,8 +19,8 @@ export interface MevShareBid { chainId: number } -/** Helper class to create MEV-Share bids on SUAVE. */ -export class MevShareBid { +/** Helper class to create MEV-Share data records on SUAVE. */ +export class MevShareRecord { constructor( blockNumber: bigint, signedTx: Hex, @@ -40,10 +40,10 @@ export class MevShareBid { this.allowedStores = [] } - /** Encodes calldata to call the `newBid` function. */ - private newBidCalldata() { + /** Encodes calldata to call the `newTransaction` function. */ + private newCalldata() { return encodeFunctionData({ - abi: MevShareBidContract.abi, + abi: MevShareContract.abi, functionName: 'newBid', args: [this.blockNumber, this.allowedPeekers, this.allowedStores], }) @@ -64,7 +64,7 @@ export class MevShareBid { toConfidentialRequest(): TransactionRequestSuave { return { to: this.mevShareContract, - data: this.newBidCalldata(), + data: this.newCalldata(), type: '0x43', gas: 500000n, gasPrice: 1000000000n, diff --git a/examples/suave/contracts/src/ConfidentialWithLogs.sol b/examples/suave/contracts/src/ConfidentialWithLogs.sol deleted file mode 100644 index 9ed6b5a8..00000000 --- a/examples/suave/contracts/src/ConfidentialWithLogs.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "suave/libraries/Suave.sol"; - -contract ConfidentialWithLogs { - event SimResultEvent( - uint64 egp - ); - - event Test( - uint64 num - ); - - constructor() { - emit Test(1); - } - - fallback() external { - emit Test(2); - } - - function fetchBidConfidentialBundleData() public returns (bytes memory x) { - emit Test(101); - // require(Suave.isConfidential(), "not confidential"); - - // bytes memory confidentialInputs = Suave.confidentialInputs(); - // return abi.decode(confidentialInputs, (bytes)); - x = hex"deadbeef"; - } - - // note: this enables the result of the confidential compute request (CCR) - // to be emitted on chain - function emitSimResultEvent(uint64 egp) public { - emit SimResultEvent(egp); - } - - // note: because of confidential execution, - // you will not see your input as input to the function - function helloWorld() external returns (bytes memory) { - // 0. ensure confidential execution - // require(Suave.isConfidential(), "not confidential"); - - // 1. fetch bundle data - bytes memory bundleData = this.fetchBidConfidentialBundleData(); - - // 2. sim bundle and get effective gas price - uint64 effectiveGasPrice = Suave.simulateBundle(bundleData); - - // note: this enables the computation result to be emitted on chain - return bytes.concat(this.emitSimResultEvent.selector, abi.encode(effectiveGasPrice)); - } -} diff --git a/examples/suave/contracts/src/bids.sol b/examples/suave/contracts/src/bids.sol new file mode 100644 index 00000000..0503a899 --- /dev/null +++ b/examples/suave/contracts/src/bids.sol @@ -0,0 +1,359 @@ +pragma solidity ^0.8.8; + +import "../lib/suave-geth/suave/sol/libraries/Suave.sol"; + +contract AnyBidContract { + 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); + } +} + +contract BundleBidContract is AnyBidContract { + function newBid(uint64 decryptionCondition, address[] memory bidAllowedPeekers, address[] memory bidAllowedStores) + external + payable + returns (bytes memory) + { + require(Suave.isConfidential()); + + bytes memory bundleData = this.fetchBidConfidentialBundleData(); + + uint64 egp = Suave.simulateBundle(bundleData); + + 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)); + + 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)); + } +} + +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 (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)); + } +} + +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 (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; +} + +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 (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)); + } +} \ No newline at end of file diff --git a/examples/suave/deployContracts.sh b/examples/suave/deployContracts.sh index b398c852..aa1a08d5 100755 --- a/examples/suave/deployContracts.sh +++ b/examples/suave/deployContracts.sh @@ -4,7 +4,7 @@ FUNDED_PRV_KEY=0x91ab9a7e53c220e6210460b65a7a3bb2ca181412a8a7b43ff336b3df1737ce1 SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd $SCRIPT_DIR/contracts forge build -createOutput=$(forge create --legacy --private-key $FUNDED_PRV_KEY --chain-id 16813125 -r http://localhost:8545 lib/suave-geth/suave/sol/standard_peekers/bids.sol:MevShareBidContract) +createOutput=$(forge create --legacy --private-key $FUNDED_PRV_KEY --chain-id 16813125 -r http://localhost:8545 lib/suave-geth/suave/sol/standard_peekers/bids.sol:MevShareContract) deployedAddress=$(echo "$createOutput" | grep 'Deployed to:' | awk '{print $3}') echo "Deployed to: $deployedAddress" echo '{"address": ''"'$deployedAddress'"}' > $SCRIPT_DIR/deployedAddress.json diff --git a/examples/suave/index.ts b/examples/suave/index.ts index e7d102d9..af411f1e 100644 --- a/examples/suave/index.ts +++ b/examples/suave/index.ts @@ -2,10 +2,10 @@ import { sleep } from 'bun' import { http, Address, Hex, createPublicClient, formatEther, isHex } from 'viem' import { goerli, suaveRigil } from 'viem/chains' import { TransactionRequestSuave } from 'viem/chains/suave/types' -import { MevShareBid } from 'bids' +import { MevShareRecord } from 'bundles' import { SuaveProvider, SuaveWallet, getSuaveProvider, getSuaveWallet } from 'viem/chains/utils' import { HttpTransport } from 'viem' -import BidContractDeployment from './deployedAddress.json' +import MevShareContract from './deployedAddress.json' const failEnv = (name: string) => { throw new Error(`missing env var ${name}`) @@ -88,17 +88,17 @@ const fundAccount = async (wallet: Address, amount: bigint) => { * See the [README](./README.md) for instructions. */ async function testSuaveBids() { - if (!BidContractDeployment.address) { + if (!MevShareContract.address) { console.error( 'Need to run the DeployContracts script first. See ./README.md for instructions.', ) - failEnv('BID_CONTRACT_ADDRESS') + failEnv('MEV_SHARE_CONTRACT_ADDRESS') } - if (!isHex(BidContractDeployment.address)) { - console.error('BID_CONTRACT_ADDRESS is not a hex string') - failEnv('BID_CONTRACT_ADDRESS') + if (!isHex(MevShareContract.address)) { + console.error('MEV_SHARE_CONTRACT_ADDRESS is not a hex string') + failEnv('MEV_SHARE_CONTRACT_ADDRESS') } - const BID_CONTRACT_ADDRESS = BidContractDeployment.address as Hex + const MEV_SHARE_CONTRACT_ADDRESS = MevShareContract.address as Hex // fund our test wallet w/ 1 ETH const fundRes = await fundAccount( @@ -118,16 +118,16 @@ async function testSuaveBids() { } const signedTx = await wallet.signTransaction(testTx) - // create bid & send ccr + // create data record & send ccr const block = await goerliProvider.getBlockNumber() - const bid = new MevShareBid( + const dataRecord = new MevShareRecord( block + 1n, signedTx, KETTLE_ADDRESS, - BID_CONTRACT_ADDRESS, + MEV_SHARE_CONTRACT_ADDRESS, suaveRigil.id, ) - const ccr = bid.toConfidentialRequest() + const ccr = dataRecord.toConfidentialRequest() const ccrRes = await wallet.sendTransaction(ccr) console.log('ccrRes', ccrRes) From e57f79423df8c44b7556611d753200be332caf9a Mon Sep 17 00:00:00 2001 From: andytudhope <13001517+andytudhope@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:05:41 +0200 Subject: [PATCH 3/3] revert old work not intended for this PR --- examples/suave/bundles/index.ts | 2 +- .../contracts/src/ConfidentialWithLogs.sol | 53 +++ examples/suave/contracts/src/bids.sol | 359 ------------------ 3 files changed, 54 insertions(+), 360 deletions(-) create mode 100644 examples/suave/contracts/src/ConfidentialWithLogs.sol delete mode 100644 examples/suave/contracts/src/bids.sol diff --git a/examples/suave/bundles/index.ts b/examples/suave/bundles/index.ts index 41b9e4b1..3115ff47 100644 --- a/examples/suave/bundles/index.ts +++ b/examples/suave/bundles/index.ts @@ -5,7 +5,7 @@ import { encodeFunctionData, toHex, } from 'viem' -import { suaveRigil } from 'viem/chains' +import { suaveRigil } from '../../../src/chains' import { SuaveTxTypes, TransactionRequestSuave } from '../../../src/chains/suave/types' import MevShareContract from '../contracts/out/bids.sol/MevShareContract.json' diff --git a/examples/suave/contracts/src/ConfidentialWithLogs.sol b/examples/suave/contracts/src/ConfidentialWithLogs.sol new file mode 100644 index 00000000..9a743abd --- /dev/null +++ b/examples/suave/contracts/src/ConfidentialWithLogs.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "suave/libraries/Suave.sol"; + +contract ConfidentialWithLogs { + event SimResultEvent( + uint64 egp + ); + + event Test( + uint64 num + ); + + constructor() { + emit Test(1); + } + + fallback() external { + emit Test(2); + } + + function fetchBidConfidentialBundleData() public returns (bytes memory x) { + emit Test(101); + // require(Suave.isConfidential(), "not confidential"); + + // bytes memory confidentialInputs = Suave.confidentialInputs(); + // return abi.decode(confidentialInputs, (bytes)); + x = hex"deadbeef"; + } + + // note: this enables the result of the confidential compute request (CCR) + // to be emitted on chain + function emitSimResultEvent(uint64 egp) public { + emit SimResultEvent(egp); + } + + // note: because of confidential execution, + // you will not see your input as input to the function + function helloWorld() external returns (bytes memory) { + // 0. ensure confidential execution + // require(Suave.isConfidential(), "not confidential"); + + // 1. fetch bundle data + bytes memory bundleData = this.fetchBidConfidentialBundleData(); + + // 2. sim bundle and get effective gas price + uint64 effectiveGasPrice = Suave.simulateBundle(bundleData); + + // note: this enables the computation result to be emitted on chain + return bytes.concat(this.emitSimResultEvent.selector, abi.encode(effectiveGasPrice)); + } +} \ No newline at end of file diff --git a/examples/suave/contracts/src/bids.sol b/examples/suave/contracts/src/bids.sol deleted file mode 100644 index 0503a899..00000000 --- a/examples/suave/contracts/src/bids.sol +++ /dev/null @@ -1,359 +0,0 @@ -pragma solidity ^0.8.8; - -import "../lib/suave-geth/suave/sol/libraries/Suave.sol"; - -contract AnyBidContract { - 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); - } -} - -contract BundleBidContract is AnyBidContract { - function newBid(uint64 decryptionCondition, address[] memory bidAllowedPeekers, address[] memory bidAllowedStores) - external - payable - returns (bytes memory) - { - require(Suave.isConfidential()); - - bytes memory bundleData = this.fetchBidConfidentialBundleData(); - - uint64 egp = Suave.simulateBundle(bundleData); - - 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)); - - 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)); - } -} - -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 (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)); - } -} - -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 (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; -} - -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 (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)); - } -} \ No newline at end of file