From 8a791819907dcb530045908a26c54c903553d7c4 Mon Sep 17 00:00:00 2001 From: andytudhope <13001517+andytudhope@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:27:42 +0200 Subject: [PATCH] CDS + External calls beginning --- docs/sidebars.js | 2 + docs/tutorials/build-suapp-webapp.mdx | 4 +- docs/tutorials/confidential-store.mdx | 162 ++++++++++++++++++++++++++ docs/tutorials/deploy-contracts.mdx | 12 +- docs/tutorials/external-call.mdx | 138 ++++++++++++++++++++++ docs/tutorials/onchain-offchain.mdx | 16 +-- 6 files changed, 318 insertions(+), 16 deletions(-) create mode 100644 docs/tutorials/confidential-store.mdx create mode 100644 docs/tutorials/external-call.mdx diff --git a/docs/sidebars.js b/docs/sidebars.js index aab00dd1..caacea95 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -38,6 +38,8 @@ module.exports = { 'tutorials/run-suave', 'tutorials/deploy-contracts', 'tutorials/onchain-offchain', + 'tutorials/confidential-store', + 'tutorials/external-call', 'tutorials/build-suapp-webapp', 'tutorials/confidential-compute-requests', 'tutorials/create-precompiles' diff --git a/docs/tutorials/build-suapp-webapp.mdx b/docs/tutorials/build-suapp-webapp.mdx index ecfb29b3..e662f824 100644 --- a/docs/tutorials/build-suapp-webapp.mdx +++ b/docs/tutorials/build-suapp-webapp.mdx @@ -1,6 +1,6 @@ --- -title: Building - Webapp -description: Build a webapp to interact with SUAPPs. +title: In the Browser +description: Build a web application to interact with SUAPPs. keywords: - application - build diff --git a/docs/tutorials/confidential-store.mdx b/docs/tutorials/confidential-store.mdx new file mode 100644 index 00000000..94a3121d --- /dev/null +++ b/docs/tutorials/confidential-store.mdx @@ -0,0 +1,162 @@ +--- +title: Confidential Store +description: Understanding the key primitives required to build great SUAPPs +keywords: + - contract + - deploy + - suave + - solidity + - confidential + - private + - store +--- + +# Using the Confidential Store + +If you've followed along from the [previous tutorial](./onchain-offchain), you'll have deployed a simple contract with `onchain` and `offchain` functions that use the [`SUAVE-STD` library](https://github.com/flashbots/suave-std) to emit logs onchain that come from offchain compute results. + +Now it's time to look at another key primitive often required to build powerful Suapps: the confidential data store. + +In particular, we'll consider how to store a private key confidentially, and then use that key to sign transactions intended for other chains. This pattern is useful when you want the results of your offchain computation to cause a transaction on another chain, and it finds application in everything from Uniswap v4 hooks to NFTs for concert tickets. + +## Import SUAVE-STD + +We're going to want to use more of the functionality offered by `SUAVE-STD` in order to both store our private key, and then retrieve it and use it to sign a transaction intended for another domain. + +Create a new file called `ConfidentialStore.sol` and begin by importing the supporting contracts and libraries we need: + +```solidity +// SPDX-License-Identifier: Unlicensed +pragma solidity ^0.8.8; + +import "suave-std/Suapp.sol"; +import "suave-std/Context.sol"; +import "suave-std/Transactions.sol"; +import "suave-std/suavelib/Suave.sol"; +``` + +If you look through each of these imports, you'll see that: + +1. `Suapp.sol` gives us the ability to easily emit logs from offchain computations onchain. +2. `Context.sol` allows any function to determine whether there are confidential inputs being passed along with the function call. We will use this to pass our private key to our Suapp without revealing what it is. +3. `Transactions.sol` helps decode/encode transactions from/to other domains. +4. `Suave.sol` contains all the precompiles which make up the MEVM that runs in each Kettle, along with the addresses they're deployed to. + +## Store Keys Confidentially + +Next, add the logic you'll need to store a private key confidentially with this Suapp: + +```solidity +contract ConfidentialStore is Suapp { + Suave.DataId signingKeyRecord; + string public PRIVATE_KEY = "ETH_L1_SIGNER"; + + // onchain-offchain pattern to register the new private key in Confidential Store + function updateKeyRecordOnchain(Suave.DataId _signingKeyRecord) public { + signingKeyRecord = _signingKeyRecord; + } + + function registerPrivateKeyOffchain() public returns (bytes memory) { + bytes memory keyData = Context.confidentialInputs(); + + address[] memory peekers = new address[](1); + peekers[0] = address(this); + + Suave.DataRecord memory record = Suave.newDataRecord(0, peekers, peekers, "private_key"); + Suave.confidentialStore(record.id, PRIVATE_KEY, keyData); + + return abi.encodeWithSelector(this.updateKeyRecordOnchain.selector, record.id); + } +} +``` + +The confidential store is a key value store, and the convention is to store "data records" as the values that are keyed by "data IDs". The SUAVE library helps abstract this so you can just call `Suave.DataId` etc. + +A `newDataRecord` expects four values: + +1. The "decryption condition" - this is an artifact which will be removed in later versions of `suave-geth`. + 1. Set to `0` for now. +2. The "allowed peekers" - this determines who can "get" data associated with the `DataId`. +3. The "allowed stores" - this determines who can "set" data associated with the `DataId`. + 1. In this example we set the `allowedPeekers` == `allowedStores` == an array of 1 address, which is set to `address(this)`. That is, only this contract can get the private key we're storing, or set it to something else. +4. The "data type" - a string which specifies the type of data being stored. + 1. In this case, it is set to `"private_key"`. + +We are also following the same pattern as previous tutorials, with an offchain function that does the heavy lifting (creating the data record for the private key we want to store), which then returns a callback to an onchain function that (rather than emitting an event) updates the `DataId` by which the private key may be fetched by this specific contract. + +Anyone can call this function (you may want to change that) and, if they pass in a private key in the `confidentialInputs` field of their Confidential Compute Request (CCR), then the Kettle which processes that CCR will set the private key in its store according to the logic above, all without revealing what that key is. + +So, let's compile the contract, deploy it, and send the CCR that will store a private key! + +```bash +forge build +``` +```bash +suave-geth spell deploy ConfidentialStore.sol:ConfidentialStore +``` + +If you built `suave-geth` from source, you may need to specify the whole path: + +```bash +.//build/bin/suave-geth spell deploy ConfidentialStore.sol:ConfidentialStore +``` + +**TODO**: Now we can send the private key as a confidential input when we call `registerPrivateKeyOffchain`: + +```bash +suave-geth spell conf-request 'registerPrivateKeyOffchain()' +``` + +You shouldn't see anything in the logs as you did in previous tutorials, as we're not emitting events from the `updateKeyRecordOnchain` function (thouh you can modify that yourself if you like). + +## Sign a Tx with your Private Key + +Let's now add the other important piece we need for this contract, which is using the private key we just stored to sign a transaction for another domain (in this Ethereum L1): + +```solidity +event TxnSignature(bytes32 r, bytes32 s); + +function onchain() public emitOffchainLogs {} + +function offchain() public returns (bytes memory) { + bytes memory signingKey = Suave.confidentialRetrieve(signingKeyRecord, PRIVATE_KEY); + + Transactions.EIP155Request memory txnWithToAddress = Transactions.EIP155Request({ + to: address(0x00000000000000000000000000000000DeaDBeef), + gas: 1000000, + gasPrice: 500, + value: 1, + nonce: 1, + data: bytes(""), + chainId: 1 + }); + + Transactions.EIP155 memory txn = Transactions.signTxn(txnWithToAddress, string(signingKey)); + emit TxnSignature(txn.r, txn.s); + + return abi.encodeWithSelector(this.onchain.selector); +} +``` + +By now, this onchain-offchain pattern should be becoming more familiar. Offchain, we do the heavy lifting of constructing the transaction object, which we then emit in the logs of an event onchain by returning a callback to the onchain function. + +The theory here is that any searcher or other service could listen to logs from `TxnSignature` events in this contract on SUAVE and submit them as part of their bundles to block builders for Ethereum L1. However, you can also use the Gateway pattern discussed in the next tutorial to call your preferred RPC provider yourself, such that you need not rely on these events being detected. + +Recompile and redeploy your contract, and call the offchain function to see this all in action: + +```bash +forge build +``` +```bash +suave-geth spell deploy ConfidentialStore.sol:ConfidentialStore +``` + +```bash +suave-geth spell conf-request 'offchain()' +``` + +You should see something like this printed to your terminal: + +**TODO** + +Congratulations! 💃 You've just begun to master the confidential store. The applications that can be built from this kind of foundation are extensive. We're excited to see what you build... \ No newline at end of file diff --git a/docs/tutorials/deploy-contracts.mdx b/docs/tutorials/deploy-contracts.mdx index 93a59f7e..aec670a7 100644 --- a/docs/tutorials/deploy-contracts.mdx +++ b/docs/tutorials/deploy-contracts.mdx @@ -44,7 +44,7 @@ You can then open the resulting files in whichever text editor you prefer. ## Onchain offchain -We need to adapt the empty contract in the `src` directory to start understanding the key differences between onchain and offchain computation. +Let's adapt the empty contract in the `src` directory to see the key differences between onchain and offchain computation. Begin by renaming `Contract.sol` to `MyFirstSuapp.sol` and deleting the `Contract.t.sol` file in the `test` directory. @@ -52,7 +52,7 @@ Then, you can write two simple functions called `onchain()` and `offchain()`: ```solidity // SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.13; +pragma solidity ^0.8.8; contract MyFirstSuapp { function onchain() public {} @@ -65,9 +65,9 @@ contract MyFirstSuapp { } ``` -All offchain functions should return a function selector to another function inside your SUAPP, such that the relevant outputs of offchain computation can result in the appropriate onchain state transitions. The Kettle which executes a user's call to `offchain()` will take the results of any offchain computation and wrap it in a "SUAVE transaction", using the specified function selector. That SUAVE transaction is broadcast onchain, which eventually results in the onchain component being executed too. +All offchain functions should return a function selector to another function inside your SUAPP, such that the relevant outputs of offchain computation result in onchain state transitions. The Kettle which executes a user's call to `offchain()` will take the results of any offchain computation and wrap it in a "SUAVE transaction", using the specified function selector. That SUAVE transaction is broadcast onchain, which eventually results in the onchain component being executed too. -Let's take a practical example. In an orderflow auction, users might send their transactions from other domains to a Suapp as confidential inputs. The Suapp can emit specific information about those transactions (without revealing everything) such that searchers listening for events on SUAVE chain can construct backruns. They can then submit their backruns to the same Suapp, which can merge the original transactions and the best backruns offchain (it can take a lot of compute to do this), before emitting the resulting bundle onchain for a block builder on the original domain to pick up and use. +Let's take a practical example. In an [orderflow auction](https://github.com/flashbots/suapp-examples/tree/main/examples/app-ofa-private), users might send their transactions from other domains to a Suapp as confidential inputs. The Suapp can emit specific information about those transactions (without revealing everything) such that searchers listening for events on SUAVE chain can construct backruns. They can then submit their backruns to the same Suapp, which can merge the original transactions and the best backruns offchain (it can take many computational steps do this), before emitting the resulting bundle onchain for a block builder on the original domain to pick up and use. There are many examples which use this onchain-offchain pattern to achieve results that are not otherwise possible in normal public blockchains. We'll be introducing them one by one through the course of these tutorials. @@ -95,7 +95,7 @@ There is a helper facility called `spell` in `suave-geth` which facilitates depl suave-geth spell deploy MyFirstSuapp.sol:MyFirstSuapp ``` -If you built from source, run +If you built from source, run: ```bash .//build/bin/suave-geth spell deploy MyFirstSuapp.sol:MyFirstSuapp @@ -113,4 +113,4 @@ INFO [03-26|09:59:54.515] Contract deployed address=0xFcd Congratulations! You just deployed your first contract to a local SUAVE network! 💃 -Take note of the address to which it has been deployed: you'll need it in the next tutorial, where we'll go over how to send confidential compute requests to your contracts, and how to extend the functions to emit information about offchain computations onchain. \ No newline at end of file +Take note of the address to which it has been deployed: you'll need it in the next tutorial, where we'll send confidential compute requests to your contracts, and extend the functions to emit information about offchain computations onchain. \ No newline at end of file diff --git a/docs/tutorials/external-call.mdx b/docs/tutorials/external-call.mdx new file mode 100644 index 00000000..56d8a380 --- /dev/null +++ b/docs/tutorials/external-call.mdx @@ -0,0 +1,138 @@ +--- +title: External Calls +description: Understanding the key primitives required to build great SUAPPs +keywords: + - contract + - deploy + - suave + - solidity + - http + - external + - data +--- + +# Making external calls + +If you've followed along from the [previous tutorial](./confidential-store), you'll have deployed a simple contract with `onchain` and `offchain` functions, and extended it so that you can store a private key from another domain and use it to sign transactions after executing some offchain, confidential computation using SUAVE. + +Building unique and powerful Suapps often requires one more key primitive (in addition to understanding on and offchain computations and how to use the confidential store). **Suapps can make arbitrary http requests to other domains, fetch data, and use that in their offchain computation**. + +Let's walk through how to do this, first fetching balance information about USDC on Ethereum L1, and then by making a request to Chat GPT's completion endpoint. + +## Fetching Balances from Ethereum + +Create a new contract in your `src` directory called `ExternalCall.sol`, and paste this into the file (making sure to update with your API key, or preferred RPC provider details): + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.8; + +import "suave-std/Suapp.sol"; +import "suave-std/Context.sol"; +import "suave-std/Gateway.sol"; + +interface ERC20 { + function balanceOf(address) external view returns (uint256); +} + +contract ExternalCall is Suapp { + function onchain() external payable emitOffchainLogs {} + + event Balance(uint256 balance); + + function offchain() external returns (bytes memory) { + string memory jsonRpc = Context.confidentialInputs(); + // targeting USDC contract on ETH L1 + Gateway gateway = new Gateway(jsonRpc, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + ERC20 token = ERC20(address(gateway)); + + // Fetching the balance for a Binance exchange account + uint256 balance = token.balanceOf(0xDFd5293D8e347dFe59E90eFd55b2956a1343963d); + emit Balance(balance); + + return abi.encodeWithSelector(this.onchain.selector); + } +} +``` + +Follow the by-now familiar pattern of recompiling, deploying, and calling the offchain function using our `spell` tool: + +```bash +forge build +``` +```bash +suave-geth spell deploy ExternalCall.sol:ExternalCall +``` +If you built `suave-geth` from source, you may need to specify the whole path: + +```bash +.//build/bin/suave-geth spell deploy ExternalCall.sol:ExternalCall +``` +```bash +suave-geth spell conf-request 'offchain()' **TODO** +``` + +You should see the balance of the a Binance exchange account (because why not?) emitted as a log in your console: + +**TODO** + +Fetching blockchain data from domains beyond SUAVE is as easy as that! We're incredibly hyped to see what you build with this primitive. However, blockchain balances and other data is not the only kind of data you can fetch and use in your Suapps... + +## Using Chat GPT in a smart contract on SUAVE + +:::info + +You will need your own ChatGPT API key for this section. + +::: + +Create a new contract in your `src` directory called `ChatContract.sol`, and paste this into the file: + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.8; + +import "suave-std/Suapp.sol"; +import "suave-std/Context.sol"; +import "suave-std/protocols/ChatGPT.sol"; + +contract ChatContract is Suapp { + function onchain() external payable emitOffchainLogs {} + + event Response(string message); + + function offchain() external returns (bytes memory) { + /* TODO: can Context.confidentialInputs() decode more than 1 value? If so, how? */ + string memory apiKey = Context.confidentialInputs(0); + string[] memory messages = Context.confidentialInputs(1); + ChatGPT chatGPT = new ChatGPT(apiKey); + + string memory response = chatGPT.complete(messages); + emit Response(response); + + return abi.encodeWithSelector(this.onchain.selector); + } +} +``` + +If you look under the hood at the [`ChatGPT.sol`](https://github.com/flashbots/suave-std/blob/main/src/protocols/ChatGPT.sol) file you are importing, you will see it uses the general-purpose `Suave.doHttpRequest()` precompile to achieve the magic of querying an LLM from within an otherwise-ordinary smart contract. + +Recompile, deploy, and call the offchain function using our `spell` tool: + +```bash +forge build +``` +```bash +suave-geth spell deploy ChatContract.sol:ChatContract +``` +```bash +suave-geth spell conf-request 'offchain()' **TODO** +``` + +You should see the response from ChatGPT printed in your console: + +**TODO** + +Congratulations! 💃 You now have all the tools you need to build powerful and unique Suapps which simply are not possible to build with other public blockchains. + +Feel free to post any Suapp you build on our [forum](https://collective.flashbots.net/c/suave/27) and we'll be happy to help you review interpolate, as well as invite you to our Developer Chat so you can see what others are building and share tips and best practices with the sharpest engineers we know. diff --git a/docs/tutorials/onchain-offchain.mdx b/docs/tutorials/onchain-offchain.mdx index 20d8f81a..32af49f0 100644 --- a/docs/tutorials/onchain-offchain.mdx +++ b/docs/tutorials/onchain-offchain.mdx @@ -1,5 +1,5 @@ --- -title: Building - Onchain Offchain +title: Onchain Offchain description: Understanding the key primitives required to build great SUAPPs keywords: - contract @@ -10,13 +10,13 @@ keywords: - offchain --- -# Onchain Offchain: SUAVE Basics +# Onchain Offchain Programming Model If you've followed along from the [previous tutorial](./deploy-contracts), you'll have deployed a simple contract which outlines two `onchain` and `offchain` functions for a Suapp on SUAVE. It should look like this: ```solidity // SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.13; +pragma solidity ^0.8.8; contract MyFirstSuapp { function onchain() public {} @@ -31,7 +31,7 @@ contract MyFirstSuapp { We'd now like to interact with our deployed contract. The `spell` tool we used to deploy the contract can also be used to send confidential compute requests. Using the address your contract was deployed to, you can use `spell` to call any function you like. -In this case, we'll get our contract to run "offchain" computation (even though we have yet to define exactly what that should be). From the root of your `suapp` directory we first createDocsByIdIndex, run: +In this case, we'll get our contract to run "offchain" computation (even though we have yet to define exactly what that should be). From the root of your `suapp` directory we first created, run: ```bash suave-geth spell conf-request 'offchain()' @@ -54,7 +54,7 @@ INFO [03-26|10:21:30.153] Waiting for the transaction to be mined... INFO [03-26|10:21:30.256] Transaction mined status=1 blockNum=6 ``` -As our `offchain()` function doesn't do anything yet, there isn't much to see here. That said, you have already deployed your first contract, and sent your first confidential request to it! We are well on the way to building our first functional SUAPP and understanding a new paradigm for public blockchains! +As our `offchain()` function doesn't do anything yet, there isn't much to see here. That said, you have already deployed your first contract, and sent your first confidential request to it! We are well on the way to building our first functional SUAPP and understanding a new paradigm for public blockchains... ## Try to Extend Your Contract @@ -65,7 +65,7 @@ So, let's try add an event and see if we can emit it from our `offchain()` funct ```solidity // SPDX-License-Identifier: Unlicense // SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.13; +pragma solidity ^0.8.8; contract MyFirstSuapp { event OffchainEvent(uint256 num); @@ -112,7 +112,7 @@ What's going on here? Well, we can't emit the results of offchain computation di If we want the relevant part of the results of our offchain computation to result in things happening onchain, we need to be a little more clever with how we write our contracts. -In order to make this easy, we maintain a [useful library called `SUAVE-STD`](https://github.com/flashbots/suave-std) which enables you to do anything from emitting logs (like we're trying to do in this tutorial), to making arbitrary http calls, using Chat GPT in your contracts, encoding JSON, RLP encoding transactions, and doing various other commonly useful things. +In order to make this easy, we maintain a [useful library called `SUAVE-STD`](https://github.com/flashbots/suave-std) which enables you to do anything from emitting logs (like we're trying to do in this tutorial), to making arbitrary http calls, using Chat GPT in your contracts, encoding and decoding JSON, RLP encoding/decoding transactions, and doing various other commonly useful things. First, you'll need to add and commit the changes you've made already before we can install `SUAVE-STD`: @@ -126,7 +126,7 @@ Now you can install our friendly helper library in your `suapp` directory: forge install flashbots/suave-std ``` -You can import the basic `Suapp.sol` contract from the `SUAVE-STD` library to help with emitting logs. This comes along with a modifier called `emitOffchainLogs` which will enable you to emit any logs emitted in offchain functions from the onchain function selected in the `return` statement. To see this in UserActivation, modify `MyFirstSuapp.sol` to look like this: +You can import the basic `Suapp.sol` contract from the `SUAVE-STD` library to help with emitting logs. This comes along with a modifier called `emitOffchainLogs` which will enable you to emit any logs specified in offchain functions from the onchain function selected in the `return` statement. To see this in action, modify `MyFirstSuapp.sol` to look like this: ```solidity // SPDX-License-Identifier: Unlicense