-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add failing test for builder. * Deploy contract and call example method stub. * Pass confidential payload & decryption condition to builder contract. * WIP. Port TestBlockBuildingContract from suave-geth. * Use funded account. * Port framework calls to suapp-examples framework. Remove unneeded calls to ProgressChain. * Send bundle record to SUAVE builder. * Fix insufficient gas per block. * Clean up. * Remove unused MEVShare method. * Remove deprecated comment. * Remove unused contract addresses. * Add L1Enabled field to config struct. * Pass framework.WithL1() option. * Use latest version of suave-geth when testing. * fix private key config in example framework (#42) * Upgrade to suave-geth v0.1.3 * Rename example to build-eth-block. Add README.md. --------- Co-authored-by: brock smedley <[email protected]>
- Loading branch information
1 parent
bf4b505
commit 1316518
Showing
8 changed files
with
860 additions
and
45 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Example Ethereum L1 Block Builder SUAPP | ||
|
||
This example demonstrates a simple block building contract that receives bundles and returns an Ethereum L1 block. | ||
|
||
## How to use | ||
|
||
Start the `suave-geth` development environment | ||
|
||
``` | ||
$ make devnet-up # from suave-geth root directory | ||
``` | ||
|
||
Execute the deployment script: | ||
|
||
``` | ||
$ go run main.go | ||
``` | ||
|
||
Expected output: | ||
|
||
``` | ||
2024/02/29 14:59:09 Test address 1: 0x675d92a306187fBC280f8Dd98465770FBAEFf8Ab | ||
2024/02/29 14:59:09 funding account 0x675d92a306187fBC280f8Dd98465770FBAEFf8Ab with 100000000000000000 | ||
2024/02/29 14:59:09 funder 0xB5fEAfbDD752ad52Afb7e1bD2E40432A485bBB7F 115792089237316195423570985008687907853269984665640564039457584007913129639927 | ||
2024/02/29 14:59:09 transaction hash: 0x29e67f56dfd1a01ab210dcad889eba7a99028ec1bf2206b66d8054efc14e6fda | ||
2024/02/29 14:59:09 deployed contract at 0xd594760B2A36467ec7F0267382564772D7b0b73c | ||
2024/02/29 14:59:09 deployed contract at 0x8f21Fdd6B4f4CacD33151777A46c122797c8BF17 | ||
2024/02/29 14:59:09 transaction hash: 0x99a95bc20ea3e8c9d8a2ac21943c1c7a51599b57e4254a48f3773f923d881f2b | ||
2024/02/29 14:59:09 transaction hash: 0xbf9ff92a229c76f59ed7d2be06297763b796c390d725fb1863e199cdb9cff1eb | ||
``` |
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,201 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.8; | ||
|
||
import "suave-std/suavelib/Suave.sol"; | ||
|
||
contract AnyBundleContract { | ||
event DataRecordEvent(Suave.DataId dataId, uint64 decryptionCondition, address[] allowedPeekers); | ||
|
||
function fetchConfidentialBundleData() public returns (bytes memory) { | ||
require(Suave.isConfidential()); | ||
|
||
bytes memory confidentialInputs = Suave.confidentialInputs(); | ||
return abi.decode(confidentialInputs, (bytes)); | ||
} | ||
|
||
function emitDataRecord(Suave.DataRecord calldata dataRecord) public { | ||
emit DataRecordEvent(dataRecord.id, dataRecord.decryptionCondition, dataRecord.allowedPeekers); | ||
} | ||
} | ||
|
||
contract BundleContract is AnyBundleContract { | ||
function newBundle( | ||
uint64 decryptionCondition, | ||
address[] memory dataAllowedPeekers, | ||
address[] memory dataAllowedStores | ||
) external payable returns (bytes memory) { | ||
require(Suave.isConfidential()); | ||
|
||
bytes memory bundleData = this.fetchConfidentialBundleData(); | ||
|
||
uint64 egp = Suave.simulateBundle(bundleData); | ||
|
||
Suave.DataRecord memory dataRecord = | ||
Suave.newDataRecord(decryptionCondition, dataAllowedPeekers, dataAllowedStores, "default:v0:ethBundles"); | ||
|
||
Suave.confidentialStore(dataRecord.id, "default:v0:ethBundles", bundleData); | ||
Suave.confidentialStore(dataRecord.id, "default:v0:ethBundleSimResults", abi.encode(egp)); | ||
|
||
return emitAndReturn(dataRecord, bundleData); | ||
} | ||
|
||
function emitAndReturn(Suave.DataRecord memory dataRecord, bytes memory) internal virtual returns (bytes memory) { | ||
emit DataRecordEvent(dataRecord.id, dataRecord.decryptionCondition, dataRecord.allowedPeekers); | ||
return bytes.concat(this.emitDataRecord.selector, abi.encode(dataRecord)); | ||
} | ||
} | ||
|
||
contract EthBundleSenderContract is BundleContract { | ||
string[] public builderUrls; | ||
|
||
constructor(string[] memory builderUrls_) { | ||
builderUrls = builderUrls_; | ||
} | ||
|
||
function emitAndReturn(Suave.DataRecord memory dataRecord, 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 BundleContract.emitAndReturn(dataRecord, bundleData); | ||
} | ||
} | ||
|
||
struct EgpRecordPair { | ||
uint64 egp; // in wei, beware overflow | ||
Suave.DataId dataId; | ||
} | ||
|
||
contract EthBlockContract is AnyBundleContract { | ||
event BuilderBoostBidEvent(Suave.DataId dataId, bytes builderBid); | ||
|
||
function idsEqual(Suave.DataId _l, Suave.DataId _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 buildFromPool(Suave.BuildBlockArgs memory blockArgs, uint64 blockHeight) public returns (bytes memory) { | ||
require(Suave.isConfidential()); | ||
|
||
Suave.DataRecord[] memory allRecords = Suave.fetchDataRecords(blockHeight, "default:v0:ethBundles"); | ||
if (allRecords.length == 0) { | ||
revert Suave.PeekerReverted(address(this), "no data records"); | ||
} | ||
|
||
EgpRecordPair[] memory bidsByEGP = new EgpRecordPair[](allRecords.length); | ||
for (uint256 i = 0; i < allRecords.length; i++) { | ||
bytes memory simResults = Suave.confidentialRetrieve(allRecords[i].id, "default:v0:ethBundleSimResults"); | ||
uint64 egp = abi.decode(simResults, (uint64)); | ||
bidsByEGP[i] = EgpRecordPair(egp, allRecords[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) { | ||
EgpRecordPair memory temp = bidsByEGP[i]; | ||
bidsByEGP[i] = bidsByEGP[j]; | ||
bidsByEGP[j] = temp; | ||
} | ||
} | ||
} | ||
|
||
Suave.DataId[] memory alldataIds = new Suave.DataId[](allRecords.length); | ||
for (uint256 i = 0; i < bidsByEGP.length; i++) { | ||
alldataIds[i] = bidsByEGP[i].dataId; | ||
} | ||
|
||
return buildAndEmit(blockArgs, blockHeight, alldataIds, ""); | ||
} | ||
|
||
function buildAndEmit( | ||
Suave.BuildBlockArgs memory blockArgs, | ||
uint64 blockHeight, | ||
Suave.DataId[] memory records, | ||
string memory namespace | ||
) public virtual returns (bytes memory) { | ||
require(Suave.isConfidential()); | ||
|
||
(Suave.DataRecord memory blockBid, bytes memory builderBid) = | ||
this.doBuild(blockArgs, blockHeight, records, namespace); | ||
|
||
emit BuilderBoostBidEvent(blockBid.id, builderBid); | ||
emit DataRecordEvent(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.DataId[] memory records, | ||
string memory namespace | ||
) public returns (Suave.DataRecord memory, bytes memory) { | ||
address[] memory allowedPeekers = new address[](2); | ||
allowedPeekers[0] = address(this); | ||
allowedPeekers[1] = Suave.BUILD_ETH_BLOCK; | ||
|
||
Suave.DataRecord memory blockBid = | ||
Suave.newDataRecord(blockHeight, allowedPeekers, allowedPeekers, "default:v0:mergedDataRecords"); | ||
Suave.confidentialStore(blockBid.id, "default:v0:mergedDataRecords", abi.encode(records)); | ||
|
||
(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.DataRecord memory dataRecord, bytes memory builderBid) | ||
public | ||
returns (Suave.DataRecord memory, bytes memory) | ||
{ | ||
emit BuilderBoostBidEvent(dataRecord.id, builderBid); | ||
emit DataRecordEvent(dataRecord.id, dataRecord.decryptionCondition, dataRecord.allowedPeekers); | ||
return (dataRecord, builderBid); | ||
} | ||
|
||
function unlock(Suave.DataId dataId, bytes memory signedBlindedHeader) public returns (bytes memory) { | ||
require(Suave.isConfidential()); | ||
|
||
// TODO: verify the header is correct | ||
// TODO: incorporate protocol name | ||
bytes memory payload = Suave.confidentialRetrieve(dataId, "default:v0:builderPayload"); | ||
return payload; | ||
} | ||
} | ||
|
||
contract EthBlockBidSenderContract is EthBlockContract { | ||
string boostRelayUrl; | ||
|
||
constructor(string memory boostRelayUrl_) { | ||
boostRelayUrl = boostRelayUrl_; | ||
} | ||
|
||
function buildAndEmit( | ||
Suave.BuildBlockArgs memory blockArgs, | ||
uint64 blockHeight, | ||
Suave.DataId[] memory dataRecords, | ||
string memory namespace | ||
) public virtual override returns (bytes memory) { | ||
require(Suave.isConfidential()); | ||
|
||
(Suave.DataRecord memory blockDataRecord, bytes memory builderBid) = | ||
this.doBuild(blockArgs, blockHeight, dataRecords, namespace); | ||
Suave.submitEthBlockToRelay(boostRelayUrl, builderBid); | ||
|
||
emit DataRecordEvent(blockDataRecord.id, blockDataRecord.decryptionCondition, blockDataRecord.allowedPeekers); | ||
return bytes.concat(this.emitDataRecord.selector, abi.encode(blockDataRecord)); | ||
} | ||
} |
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,89 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"log" | ||
"math/big" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
|
||
"github.com/flashbots/suapp-examples/framework" | ||
) | ||
|
||
var buildEthBlockAddress = common.HexToAddress("0x42100001") | ||
|
||
func main() { | ||
fr := framework.New(framework.WithL1()) | ||
|
||
testAddr1 := framework.GeneratePrivKey() | ||
log.Printf("Test address 1: %s", testAddr1.Address().Hex()) | ||
|
||
fundBalance := big.NewInt(100000000000000000) | ||
maybe(fr.L1.FundAccount(testAddr1.Address(), fundBalance)) | ||
|
||
targeAddr := testAddr1.Address() | ||
tx, err := fr.L1.SignTx(testAddr1, &types.LegacyTx{ | ||
To: &targeAddr, | ||
Value: big.NewInt(1000), | ||
Gas: 21000, | ||
GasPrice: big.NewInt(6701898710), | ||
}) | ||
maybe(err) | ||
|
||
bundle := &types.SBundle{ | ||
Txs: types.Transactions{tx}, | ||
RevertingHashes: []common.Hash{}, | ||
} | ||
bundleBytes, err := json.Marshal(bundle) | ||
maybe(err) | ||
|
||
bundleContract := fr.Suave.DeployContract("builder.sol/BundleContract.json") | ||
ethBlockContract := fr.Suave.DeployContract("builder.sol/EthBlockContract.json") | ||
|
||
targetBlock := currentBlock(fr).Time() | ||
|
||
{ // Send a bundle to the builder | ||
decryptionCondition := targetBlock + 1 | ||
allowedPeekers := []common.Address{ | ||
buildEthBlockAddress, | ||
bundleContract.Address(), | ||
ethBlockContract.Address()} | ||
allowedStores := []common.Address{} | ||
newBundleArgs := []any{ | ||
decryptionCondition, | ||
allowedPeekers, | ||
allowedStores} | ||
|
||
confidentialDataBytes, err := bundleContract.Abi.Methods["fetchConfidentialBundleData"].Outputs.Pack(bundleBytes) | ||
maybe(err) | ||
|
||
_ = bundleContract.SendTransaction("newBundle", newBundleArgs, confidentialDataBytes) | ||
} | ||
|
||
{ // Signal to the builder that it's time to build a new block | ||
payloadArgsTuple := types.BuildBlockArgs{ | ||
ProposerPubkey: []byte{0x42}, | ||
Timestamp: targetBlock + 12, // ethHead + uint64(12), | ||
FeeRecipient: common.Address{0x42}, | ||
} | ||
|
||
_ = ethBlockContract.SendTransaction("buildFromPool", []any{payloadArgsTuple, targetBlock + 1}, nil) | ||
maybe(err) | ||
} | ||
} | ||
|
||
func currentBlock(fr *framework.Framework) *types.Block { | ||
n, err := fr.L1.RPC().BlockNumber(context.TODO()) | ||
maybe(err) | ||
b, err := fr.L1.RPC().BlockByNumber(context.TODO(), new(big.Int).SetUint64(n)) | ||
maybe(err) | ||
return b | ||
} | ||
|
||
func maybe(err error) { | ||
if err != nil { | ||
panic(err) | ||
} | ||
} |
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.