-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Example for a private OFA application (#4)
* First approach * More * Add full example * Fix spacing * Add more comments * Rename * Address comments * Address more comments in README
- Loading branch information
Showing
8 changed files
with
344 additions
and
48 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
|
||
# Example Suapp for an OFA application with private transactions | ||
|
||
This example features an [Order flow auction](https://collective.flashbots.net/t/order-flow-auctions-and-centralisation-ii-order-flow-auctions/284) Suapp based on the [mev-share](https://github.com/flashbots/mev-share) protocol specification. | ||
|
||
User transactions are stored in the confidential datastore and only a small hint it is leaked outside the Kettle. Then, searchers bid in an order-flow auction for the right to execute against users’ private transactions. | ||
|
||
## How to use | ||
|
||
Run `Suave` in development mode: | ||
|
||
``` | ||
$ suave --suave.dev | ||
``` | ||
|
||
Execute the deployment script: | ||
|
||
``` | ||
$ go run main.go | ||
``` |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"math/big" | ||
"net/http" | ||
"net/http/httptest" | ||
|
||
"github.com/ethereum/go-ethereum/accounts/abi" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/flashbots/suapp-examples/framework" | ||
) | ||
|
||
func main() { | ||
fakeRelayer := httptest.NewServer(&relayHandlerExample{}) | ||
defer fakeRelayer.Close() | ||
|
||
fr := framework.NewFr() | ||
contract := fr.DeployContract("ofa-private.sol/OFAPrivate.json") | ||
|
||
// Step 1. Create and fund the accounts we are going to frontrun/backrun | ||
fmt.Println("1. Create and fund test accounts") | ||
|
||
testAddr1 := framework.GeneratePrivKey() | ||
testAddr2 := framework.GeneratePrivKey() | ||
|
||
fundBalance := big.NewInt(100000000) | ||
fr.FundAccount(testAddr1.Address(), fundBalance) | ||
|
||
targeAddr := testAddr1.Address() | ||
|
||
ethTxn1, _ := fr.SignTx(testAddr1, &types.LegacyTx{ | ||
To: &targeAddr, | ||
Value: big.NewInt(1000), | ||
Gas: 21000, | ||
GasPrice: big.NewInt(13), | ||
}) | ||
|
||
ethTxnBackrun, _ := fr.SignTx(testAddr2, &types.LegacyTx{ | ||
To: &targeAddr, | ||
Value: big.NewInt(1000), | ||
Gas: 21420, | ||
GasPrice: big.NewInt(13), | ||
}) | ||
|
||
// Step 2. Send the initial transaction | ||
fmt.Println("2. Send bid") | ||
|
||
refundPercent := 10 | ||
bundle := &types.SBundle{ | ||
Txs: types.Transactions{ethTxn1}, | ||
RevertingHashes: []common.Hash{}, | ||
RefundPercent: &refundPercent, | ||
} | ||
bundleBytes, _ := json.Marshal(bundle) | ||
|
||
// new bid inputs | ||
receipt := contract.SendTransaction("newOrder", []interface{}{}, bundleBytes) | ||
|
||
hintEvent := &HintEvent{} | ||
if err := hintEvent.Unpack(receipt.Logs[0]); err != nil { | ||
panic(err) | ||
} | ||
|
||
fmt.Println("Hint event id", hintEvent.BidId) | ||
|
||
// Step 3. Send the backrun transaction | ||
fmt.Println("3. Send backrun") | ||
|
||
backRunBundle := &types.SBundle{ | ||
Txs: types.Transactions{ethTxnBackrun}, | ||
RevertingHashes: []common.Hash{}, | ||
} | ||
backRunBundleBytes, _ := json.Marshal(backRunBundle) | ||
|
||
// backrun inputs | ||
receipt = contract.SendTransaction("newMatch", []interface{}{hintEvent.BidId}, backRunBundleBytes) | ||
|
||
matchEvent := &HintEvent{} | ||
if err := matchEvent.Unpack(receipt.Logs[0]); err != nil { | ||
panic(err) | ||
} | ||
|
||
fmt.Println("Match event id", matchEvent.BidId) | ||
|
||
// Step 4. Emit the batch to the relayer | ||
fmt.Println("Step 4. Emit batch") | ||
|
||
contract.SendTransaction("emitMatchBidAndHint", []interface{}{fakeRelayer.URL, matchEvent.BidId}, backRunBundleBytes) | ||
} | ||
|
||
var hintEventABI abi.Event | ||
|
||
func init() { | ||
artifact, _ := framework.ReadArtifact("ofa-private.sol/OFAPrivate.json") | ||
hintEventABI = artifact.Abi.Events["HintEvent"] | ||
} | ||
|
||
type HintEvent struct { | ||
BidId [16]byte | ||
Hint []byte | ||
} | ||
|
||
func (h *HintEvent) Unpack(log *types.Log) error { | ||
unpacked, err := hintEventABI.Inputs.Unpack(log.Data) | ||
if err != nil { | ||
return err | ||
} | ||
h.BidId = unpacked[0].([16]byte) | ||
h.Hint = unpacked[1].([]byte) | ||
return nil | ||
} | ||
|
||
type relayHandlerExample struct { | ||
} | ||
|
||
func (rl *relayHandlerExample) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
bodyBytes, err := io.ReadAll(r.Body) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
fmt.Println(string(bodyBytes)) | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.8; | ||
|
||
import "../../suave-geth/suave/sol/libraries/Suave.sol"; | ||
|
||
contract OFAPrivate { | ||
address[] public addressList = [0xC8df3686b4Afb2BB53e60EAe97EF043FE03Fb829]; | ||
|
||
// Struct to hold hint-related information for an order. | ||
struct HintOrder { | ||
Suave.BidId id; | ||
bytes hint; | ||
} | ||
|
||
event HintEvent ( | ||
Suave.BidId id, | ||
bytes hint | ||
); | ||
|
||
// Internal function to save order details and generate a hint. | ||
function saveOrder() internal view returns (HintOrder memory) { | ||
// Retrieve the bundle data from the confidential inputs | ||
bytes memory bundleData = Suave.confidentialInputs(); | ||
|
||
// Simulate the bundle and extract its score. | ||
uint64 egp = Suave.simulateBundle(bundleData); | ||
|
||
// Extract a hint about this bundle that is going to be leaked | ||
// to external applications. | ||
bytes memory hint = Suave.extractHint(bundleData); | ||
|
||
// Store the bundle and the simulation results in the confidential datastore. | ||
Suave.Bid memory bid = Suave.newBid(10, addressList, addressList, ""); | ||
Suave.confidentialStore(bid.id, "mevshare:v0:ethBundles", bundleData); | ||
Suave.confidentialStore(bid.id, "mevshare:v0:ethBundleSimResults", abi.encode(egp)); | ||
|
||
HintOrder memory hintOrder; | ||
hintOrder.id = bid.id; | ||
hintOrder.hint = hint; | ||
|
||
return hintOrder; | ||
} | ||
|
||
function emitHint(HintOrder memory order) public payable { | ||
emit HintEvent(order.id, order.hint); | ||
} | ||
|
||
// Function to create a new user order | ||
function newOrder() external payable returns (bytes memory) { | ||
HintOrder memory hintOrder = saveOrder(); | ||
return abi.encodeWithSelector(this.emitHint.selector, hintOrder); | ||
} | ||
|
||
// Function to match and backrun another bid. | ||
function newMatch(Suave.BidId shareBidId) external payable returns (bytes memory) { | ||
HintOrder memory hintOrder = saveOrder(); | ||
|
||
// Merge the bids and store them in the confidential datastore. | ||
// The 'fillMevShareBundle' precompile will use this information to send the bundles. | ||
Suave.BidId[] memory bids = new Suave.BidId[](2); | ||
bids[0] = shareBidId; | ||
bids[1] = hintOrder.id; | ||
Suave.confidentialStore(hintOrder.id, "mevshare:v0:mergedBids", abi.encode(bids)); | ||
|
||
return abi.encodeWithSelector(this.emitHint.selector, hintOrder); | ||
} | ||
|
||
function emitMatchBidAndHintCallback() external payable { | ||
} | ||
|
||
function emitMatchBidAndHint(string memory builderUrl, Suave.BidId bidId) external payable returns (bytes memory) { | ||
bytes memory bundleData = Suave.fillMevShareBundle(bidId); | ||
Suave.submitBundleJsonRPC(builderUrl, "mev_sendBundle", bundleData); | ||
|
||
return abi.encodeWithSelector(this.emitMatchBidAndHintCallback.selector); | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Oops, something went wrong.