Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable examples on public networks #26

Merged
merged 3 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export KETTLE_PRIVKEY=
export KETTLE_RPC=
export L1_PRIVKEY=
export L1_RPC=
export BUILDER_URL=
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
out/
cache/
cache/
.env
4 changes: 2 additions & 2 deletions examples/app-ofa-private/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# 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.
Expand All @@ -10,7 +9,8 @@ User transactions are stored in the confidential datastore and only a small hint
Run `Suave` in development mode:

```
$ suave --suave.dev
$ cd ../..
$ docker-compose up
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch back to docker compose? which directory is ../.. supposed to be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we use docker compose to enable the Suave<>L1 integration. The ../.. directory is supposed to mean the root directory of suapp-examples.


Execute the deployment script:
Expand Down
85 changes: 73 additions & 12 deletions examples/app-ofa-private/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io"
Expand All @@ -12,13 +13,28 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/flashbots/suapp-examples/framework"
envconfig "github.com/sethvargo/go-envconfig"
)

type config struct {
BuilderURL string `env:"BUILDER_URL, default=local"`
}

func main() {
relayerURL := "0.0.0.0:1234"
go func() {
log.Fatal(http.ListenAndServe(relayerURL, &relayHandlerExample{}))
}()
var cfg config
if err := envconfig.Process(context.Background(), &cfg); err != nil {
log.Fatal(err)
}

if cfg.BuilderURL == "local" {
// '172.17.0.1' is the default docker host IP that a docker container can
// use to connect with a service running on the host machine.
cfg.BuilderURL = "http://172.17.0.1:1234"

go func() {
log.Fatal(http.ListenAndServe("0.0.0.0:1234", &relayHandlerExample{}))
}()
}

fr := framework.New()
contract := fr.Suave.DeployContract("ofa-private.sol/OFAPrivate.json")
Expand All @@ -29,6 +45,9 @@ func main() {
testAddr1 := framework.GeneratePrivKey()
testAddr2 := framework.GeneratePrivKey()

log.Printf("Test address 1: %s", testAddr1.Address().Hex())
log.Printf("Test address 2: %s", testAddr2.Address().Hex())

fundBalance := big.NewInt(100000000000000000)
if err := fr.L1.FundAccount(testAddr1.Address(), fundBalance); err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -64,8 +83,14 @@ func main() {
}
bundleBytes, _ := json.Marshal(bundle)

target, err := fr.L1.RPC().BlockNumber(context.Background())
if err != nil {
log.Fatal(err)
}
log.Printf("Latest goerli block: %d", target)

// new dataRecord inputs
receipt := contract.SendTransaction("newOrder", []interface{}{}, bundleBytes)
receipt := contract.SendTransaction("newOrder", []interface{}{target + 1}, bundleBytes)

hintEvent := &HintEvent{}
if err := hintEvent.Unpack(receipt.Logs[0]); err != nil {
Expand All @@ -83,8 +108,14 @@ func main() {
}
backRunBundleBytes, _ := json.Marshal(backRunBundle)

target, err = fr.L1.RPC().BlockNumber(context.Background())
if err != nil {
log.Fatal(err)
}
log.Printf("Latest goerli block: %d", target)

// backrun inputs
receipt = contract.SendTransaction("newMatch", []interface{}{hintEvent.DataRecordId}, backRunBundleBytes)
receipt = contract.SendTransaction("newMatch", []interface{}{hintEvent.DataRecordId, target + 1}, backRunBundleBytes)

matchEvent := &HintEvent{}
if err := matchEvent.Unpack(receipt.Logs[0]); err != nil {
Expand All @@ -93,17 +124,27 @@ func main() {

fmt.Println("Match event id", matchEvent.DataRecordId)

// Step 4. Emit the batch to the relayer
// Step 4. Emit the batch to the relayer and parse the output
fmt.Println("4. Emit batch")

contract.SendTransaction("emitMatchDataRecordAndHint", []interface{}{"http://172.17.0.1:1234", matchEvent.DataRecordId}, backRunBundleBytes)
receipt = contract.SendTransaction("emitMatchDataRecordAndHint", []interface{}{cfg.BuilderURL, matchEvent.DataRecordId}, backRunBundleBytes)
bundleHash, err := decodeBundleEmittedOutput(receipt)
if err != nil {
log.Fatal(err)
}

fmt.Println("Bundle hash", bundleHash)
}

var hintEventABI abi.Event
var (
hintEventABI abi.Event
bundleEmittedEvent abi.Event
)

func init() {
artifact, _ := framework.ReadArtifact("ofa-private.sol/OFAPrivate.json")
hintEventABI = artifact.Abi.Events["HintEvent"]
bundleEmittedEvent = artifact.Abi.Events["BundleEmitted"]
}

type HintEvent struct {
Expand All @@ -112,15 +153,34 @@ type HintEvent struct {
}

func (h *HintEvent) Unpack(log *types.Log) error {
unpacked, err := hintEventABI.Inputs.Unpack(log.Data)
res, err := hintEventABI.ParseLog(log)
if err != nil {
return err
}
h.DataRecordId, _ = unpacked[0].([16]byte)
h.Hint, _ = unpacked[1].([]byte)
h.DataRecordId, _ = res["id"].([16]byte)
h.Hint, _ = res["hint"].([]byte)
return nil
}

func decodeBundleEmittedOutput(receipt *types.Receipt) (string, error) {
bundleEmitted, _ := bundleEmittedEvent.ParseLog(receipt.Logs[0])
response, _ := bundleEmitted["bundleRawResponse"].(string)

log.Printf("mev_share response: %s", response)

var bundleResponse []struct {
Result struct {
BundleHash string
}
}

if err := json.Unmarshal([]byte(response), &bundleResponse); err != nil {
return "", err
}

return bundleResponse[0].Result.BundleHash, nil
}

type relayHandlerExample struct{}

func (rl *relayHandlerExample) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand All @@ -130,4 +190,5 @@ func (rl *relayHandlerExample) ServeHTTP(w http.ResponseWriter, r *http.Request)
}

fmt.Println(string(bodyBytes))
w.Write([]byte(`[{"id":1,"result":{"bundleHash":"0x8b3302e3ffe34149ba5b2e801f21d4227faf6c7860a4e03ace2b8a6bbac54f07"},"jsonrpc":"2.0"}]`))
}
42 changes: 33 additions & 9 deletions examples/app-ofa-private/ofa-private.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ contract OFAPrivate {

event HintEvent(Suave.DataId id, bytes hint);

event BundleEmitted(string bundleRawResponse);

// Internal function to save order details and generate a hint.
function saveOrder() internal view returns (HintOrder memory) {
function saveOrder(uint64 decryptionCondition) internal view returns (HintOrder memory) {
// Retrieve the bundle data from the confidential inputs
bytes memory bundleData = Suave.confidentialInputs();

Expand All @@ -29,7 +31,7 @@ contract OFAPrivate {
allowedList[1] = 0x0000000000000000000000000000000043200001;

// Store the bundle and the simulation results in the confidential datastore.
Suave.DataRecord memory dataRecord = Suave.newDataRecord(10, allowedList, allowedList, "");
Suave.DataRecord memory dataRecord = Suave.newDataRecord(decryptionCondition, allowedList, allowedList, "");
Suave.confidentialStore(dataRecord.id, "mevshare:v0:ethBundles", bundleData);
Suave.confidentialStore(dataRecord.id, "mevshare:v0:ethBundleSimResults", abi.encode(egp));

Expand All @@ -45,14 +47,18 @@ contract OFAPrivate {
}

// Function to create a new user order
function newOrder() external payable returns (bytes memory) {
HintOrder memory hintOrder = saveOrder();
function newOrder(uint64 decryptionCondition) external payable returns (bytes memory) {
HintOrder memory hintOrder = saveOrder(decryptionCondition);
return abi.encodeWithSelector(this.emitHint.selector, hintOrder);
}

// Function to match and backrun another dataRecord.
function newMatch(Suave.DataId shareDataRecordId) external payable returns (bytes memory) {
HintOrder memory hintOrder = saveOrder();
function newMatch(Suave.DataId shareDataRecordId, uint64 decryptionCondition)
external
payable
returns (bytes memory)
{
HintOrder memory hintOrder = saveOrder(decryptionCondition);

// Merge the dataRecords and store them in the confidential datastore.
// The 'fillMevShareBundle' precompile will use this information to send the bundles.
Expand All @@ -64,16 +70,34 @@ contract OFAPrivate {
return abi.encodeWithSelector(this.emitHint.selector, hintOrder);
}

function emitMatchDataRecordAndHintCallback() external payable {}
function emitMatchDataRecordAndHintCallback(string memory bundleRawResponse) external payable {
emit BundleEmitted(bundleRawResponse);
}

function emitMatchDataRecordAndHint(string memory builderUrl, Suave.DataId dataRecordId)
external
payable
returns (bytes memory)
{
bytes memory bundleData = Suave.fillMevShareBundle(dataRecordId);
Suave.submitBundleJsonRPC(builderUrl, "mev_sendBundle", bundleData);
bytes memory response = submitBundle(builderUrl, bundleData);

return abi.encodeWithSelector(this.emitMatchDataRecordAndHintCallback.selector, response);
}

function submitBundle(string memory builderUrl, bytes memory bundleData) internal view returns (bytes memory) {
// encode the jsonrpc request in JSON format.
bytes memory body =
abi.encodePacked('{"jsonrpc":"2.0","method":"mev_sendBundle","params":[', bundleData, '],"id":1}');

Suave.HttpRequest memory request;
request.url = builderUrl;
request.method = "POST";
request.body = body;
request.headers = new string[](1);
request.headers[0] = "Content-Type: application/json";
request.withFlashbotsSignature = true;

return abi.encodeWithSelector(this.emitMatchDataRecordAndHintCallback.selector);
return Suave.doHTTPRequest(request);
}
}