Skip to content

Commit

Permalink
Example for a private OFA application (#4)
Browse files Browse the repository at this point in the history
* First approach

* More

* Add full example

* Fix spacing

* Add more comments

* Rename

* Address comments

* Address more comments in README
  • Loading branch information
ferranbt authored Nov 17, 2023
1 parent 71a82ec commit 2c17a27
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 48 deletions.
20 changes: 20 additions & 0 deletions examples/app-ofa-private/README.md
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
```
127 changes: 127 additions & 0 deletions examples/app-ofa-private/main.go
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))
}
77 changes: 77 additions & 0 deletions examples/app-ofa-private/ofa-private.sol
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);
}
}
4 changes: 3 additions & 1 deletion examples/mevm-confidential-store/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import (
)

func main() {
framework.DeployAndTransact("confidential-store.sol/ConfidentialStore.json", "example")
fr := framework.New()
fr.DeployContract("confidential-store.sol/ConfidentialStore.json").
SendTransaction("example", nil, nil)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ library UniswapV3 {
}

contract ExternalUniswapV3Quote {
function example(UniswapV3.ExactOutputSingleParams memory params) external payable {
function callback() external payable {
}

function example(UniswapV3.ExactOutputSingleParams memory params) external payable returns (bytes memory) {
UniswapV3.exactOutputSingle(params);

return abi.encodeWithSelector(this.callback.selector);
}
}
4 changes: 3 additions & 1 deletion examples/mevm-is-confidential/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import (
)

func main() {
framework.DeployAndTransact("is-confidential.sol/IsConfidential.json", "example")
fr := framework.New()
fr.DeployContract("is-confidential.sol/IsConfidential.json").
SendTransaction("example", nil, nil)
}
4 changes: 3 additions & 1 deletion examples/onchain-callback/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import (
)

func main() {
framework.DeployAndTransact("onchain-callback.sol/OnChainCallback.json", "example")
fr := framework.New()
fr.DeployContract("onchain-callback.sol/OnChainCallback.json").
SendTransaction("example", nil, nil)
}
Loading

0 comments on commit 2c17a27

Please sign in to comment.