# Oracle Script Course # Introduction ## Who is this course for? This course is designed for anyone who is interested in learning how to create and deploy an oracle script onto BandChain. It is recommended that those who are attempting this course is able to code in [Rust](https://www.rust-lang.org/) and has completed the Data Source course (add link here) and BandChain course. ## Recommended reading material While not explicitly required, it is recommended to read up on the following before starting the course: - [owasm](https://docs.bandchain.org/whitepaper/oracle-webassembly.html) - [obi](https://docs.bandchain.org/technical-specifications/owasm.html) ## Course structure ### Chapter 1 - Oracle script overview In this section, you'll learn about what an oracle script is and how it works ### Chapter 2 - How to create an oracle script This section will teach you all the necessary skills for you to be able create your own oracle script. ### Chapter 3 - How to deploy and edit an oracle script You'll learn how to deploy and edit an oracle script on BandChain in this section. ## Course objective At the end of this course, you'll be able to: - Understand how an oracle script works - Create a working oracle script - Deploy and edit an oracle script on BandChain # Chapter 1 - Oracle script overview ## What is an oracle script? An oracle script is a turing-complete executable program that is at the heart of BandChain. It encodes a set of raw data requests, which is then sent to the data sources, and aggregates the raw data returned from the data sources into a final report. When an oracle script is called and executed, a data proof is also produced. This proof is a Merkle proof that shows the existence of the final result of the data request on BandChain. In addition to the actual result of the request, the proof also contains information on the request parameters such as the script hash, the time of execution and the number of validators that responded. This proof can then be used by any smart contract on other blockchains to verify that the existence of the data on BandChain and to retrieve the results stored. ## How does an oracle script work? Below is an overview of how the data is retrieved when a data request to an oracle script is called ![](https://i.imgur.com/qtzW3lD.png) However, inside an Oracle script, it's workflow can be broken down into two main steps: - Preparation phase - Execution phase ### Preparation phase During the preparation phase, a function `prepare()`, which should be defined in the oracle script will be called. This function should contain the logic to determine which data sources are required for its execution. It should also implement an Oracle Environment Interface(OEI) to communicate with BandChain to send out a request to BandChain's validators to retrieve the results from the required data sources. ### Execution phase During the execution phase, a function`execute()`, which should be defined in the oracle script will be called. This function can contain any set of logic to determine what to do with the data that was requested during the preparation phase and needs to return an output that will be packaged and sent back to BandChain. <!-- questionaire What is the purpose of an oracle script's execution phase? a. To find the median of the data source's values b. To verify the data retrieved from the data sources are valid c. To modify the data in a predetermined way and return an output to be posted on BandChain [Answer] d. To send out requests to retrieve data from specified data sources --> # How to create an oracle script ## Writing the oracle script In the last chapter, we discussed how an oracle script works and what sort of code we need to implement to create one. Luckily, Band Protocol provides a Owasm Library, [`owasm-kit`](https://docs.rs/owasm-kit/0.3.0/owasm_kit/index.html#), which contains the `oei` module, which defines a set of functions used by the OEI and the `ext` module, which is an extension module that contains convenient wrapper functions that allow the user to calculate the mean, median and majority results from the validator's reported results amongst other things. While an oracle script can be any type of WebAssembly binary, in this course, we'll only be detailing how to write an oracle script in Rust using the modules provided in [`owasm-kit`](https://docs.rs/owasm-kit/0.2.0/owasm_kit/index.html#) and [`obi`](https://docs.rs/obi/0.0.2/obi/) ## Inputs and outputs When creating an oracle script, we need to define the input and output types that the oracle script will recieve and output. We also need to be able to serialize and deserialize the data when interacting with BandChain using [Oracle Binary Encoding (OBI)](https://docs.bandchain.org/technical-specifications/obi.html). When creating an oracle script, the oracle script's input and output needs to be defined. As the oracle script interacts with BandChain, we also need to be able to serialize and deserialize the data coming into and out of BandChain. In order to serialize and deserialize the data we can use OBI. BandChain's Rust implementation for [OBI](https://docs.rs/obi/0.0.2/obi/) contains [derive macros](https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros) `OBIEncode` and `OBIDecode` which can be derived by the output and input structs respectively in order to implement the required methods that will be used by the `prepare_entry_point` and `execute_entry_point` macros. These macros which will later be explained in more detail. The example below shows an input and outputs struct of an oracle script that takes `quote_currency` as a `String` and outputs its `price` as `u64`. ```rust // The input struct derives the OBIDecode and OBISchema trait in order to derive // the serialized data coming in from BandChain #[derive(OBIDecode, OBISchema)] struct Input { quote_currency: String, } // The output struct derives the OBIEncode and OBISchema trait in order to // serialize the data that is to be sent back to BandChain #[derive(OBIEncode, OBISchema)] struct Output { price: u64 } ``` ## Preparation phase Now that we've defined our inputs and outputs, we need to instruct our oracle script to call the data source we want to get the data required. In order to do this, we can implement a function called `prepare_impl` which will utilize the function `ask_external_data()`, which is contained in [`owasm-kit::oei`](https://docs.rs/owasm-kit/0.2.0/owasm_kit/oei/index.html), to send out a request to the specified data sources to retrieve the data required. The parameters of `ask_external_data()` are: | Parameters | Type | Description | | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------ | | `external_id` | `i64` | A unique identifier set within the oracle script which is used to identify a certain request when asking for the results | | `data_source_id` | `i64` | The ID of the data source to call | | `calldata` | `&[u8]` | The calldata to be sent to the data source in bytes | In the example below, we show how to call a data_source with id 519 using the inputs specified above: ```rust const DATA_SOURCE_ID: i64 = 519; const EXTERNAL_ID: i64 = 0; fn prepare_impl(input: Input) { oei::ask_external_data(EXTERNAL_ID, DATA_SOURCE_ID, input.quote_currency.as_bytes()); } prepare_entry_point!(prepare_impl); ``` Note: The external ID for each data source call should be unique In the previous chapter, we mentioned that the function called `prepare()` is called. As our function is called `prepare_impl`, we can use a convenient macro `prepare_entry_point`, which is a part of `owasm-kit`, to create a function called `prepare()` for us. ## Execution phase Now that the request to retrieve the data from the specified data sources has been sent, we can await the results and process it. To get the results, we can call `load_input()`, which is a part of [`owasm-kit::ext`](https://docs.rs/owasm-kit/0.3.0/owasm_kit/ext/index.html) to get the raw input. Band also provides in `owasm-kit` the following preprocessing functions for ease of use: `load_average()`, `load_majority()`, `load_median_integer()` and `load_median_float()`. In the example below, we show how to retrieve and process the data loaded as a median. ```rust fn execute_impl(_input: Input) -> Output { Output { price: ext::load_median_integer::<u64>(EXTERNAL_ID).unwrap(), } } execute_entry_point!(prepare_impl); ``` Similarly to `prepare_entry_point`, we can use the macro `execute_entry_point` which is also a part of `owasm-kit`, to create a function called `execute()` for us. ## Example Below is an example that shows a complete oracle script that retrieves CoinDesk's Bitcoin Price Index. ```rust use obi::{OBIDecode, OBIEncode, OBISchema}; use owasm_kit::{execute_entry_point, prepare_entry_point, oei, ext}; const DATA_SOURCE_ID: i64 = 519; const EXTERNAL_ID: i64 = 0; #[derive(OBIDecode, OBISchema)] struct Input { quote_currency: String, } #[derive(OBIEncode, OBISchema)] struct Output { price: u64 } fn prepare_impl(input: Input) { oei::ask_external_data(EXTERNAL_ID, DATA_SOURCE_ID, input.quote_currency.as_bytes()); } fn execute_impl(_input: Input) -> Output { Output { price: ext::load_median_integer::<u64>(EXTERNAL_ID).unwrap(), } } prepare_entry_point!(prepare_impl); execute_entry_point!(execute_impl); ``` ## Schema Now that the oracle script is defined, we need to define the oracle script's OBI schema as well. BandChain's OBI schema is structured based similarly to the psuedo-code below: ``` {input}/{output} ``` where the following types are supported by obi: - Boolean - e.g. `Input{x: bool}` := `{x:bool}/{}` - Unsigned Integers: `u8`, `u16`, `u32`, `u64`, `u128`, `u256` - e.g. `Input{x: u8}` := `{x:u8}/{}` - Signed Integers: `i8`, `i16`, `i32`, `i64`, `i128`, `i256` - e.g. `Input{x: i8}` := `{x:i8}/{}` - Strings - `Input{x: String}` := `{x:string}/{}` - Bytes - `Input{x: &[u8]}` := `{x:bytes}/{}` - Vectors - `Input{x: vec<u8>}` := `{x:[u8]}/{}` - Structs - `Input{x: vec<u8>, y: String}` := `{x:[u8],y:string}/{}` For example, using the example code above, the schema would be defined as: ``` {quote_currency:string}/{price:u64} ``` ## Challenge Here's a challenge to help test your understanding of the knowledge acquired in the past 3 chapters: Using the following data source ID's: [486](https://laozi-testnet6.cosmoscan.io/data-source/486), [487](https://laozi-testnet6.cosmoscan.io/data-source/487) and [488](https://laozi-testnet6.cosmoscan.io/data-source/488), create an oracle script that queries the given IDs and finds and returns the median result. The given data source ID contain the following inputs: | Field | Type | Description | | --------- | ---- | ----------------------------------------- | | base | str | The base currency of the market to query | | quote | str | The quote currency of the market to query | | timestamp | int | The opening timestamp of market candle | and returns the closing price of the specified candle. The oracle script should take the following input: | Field | Type | Description | | ---------- | ---- | ----------------------------------------- | | base | str | The base currency of the market to query | | quote | str | The quote currency of the market to query | | timestamp | int | The opening timestamp of market candle | | multiplier | int | Value to multiple the price result by | and provide the median prices of the specified candles, where price should be of type `u64` An example of the input and output the oracle script should have can be seen below: ```rust #[derive(OBIDecode, OBISchema)] struct Input { base: String, quote: String, timestamp: u64, multiplier: u64, } #[derive(OBIEncode, OBISchema)] struct Output { price: u64, } ``` An example oracle script that implements these three data sources can be found [here](https://laozi-testnet6.cosmoscan.io/oracle-script/319) and an example request can be seen [here](https://laozi-testnet6.cosmoscan.io/request/34805) and [here](https://laozi-testnet6.cosmoscan.io/request/34807). The example inputs and expected outputs for testing can be found below: Input: | base | quote | timestamp | multiplier | | ---- | ----- | ---------- | ---------- | | BTC | USDT | 1662508800 | 100000000 | | ETH | USDT | 1633824000 | 100000000 | Output: | price | | ------------- | | 1929400000000 | | 341422000000 | # Chapter 3 - How to deploy and edit an oracle script ## Deploy tools In order to deploy or edit an oracle script, either our online IDE: [Band Builder](https://band-builder.netlify.app/) or our SDK's [pyband](https://github.com/bandprotocol/pyband) and [bandchain.js](https://github.com/bandprotocol/bandchain.js) can be used. ## Messages ### Deploy message In order to deploy an oracle script, we need to create `MsgCreateOracleScript` message and broadcast the message to BandChain. `MsgCreateOracleScript` contains 7 parameters: | Parameter | Type | Description | | --------------- | ------ | -------------------------------------------------------------------------- | | Name | string | The name of the oracle script to be deployed | | Description | string | A description of the oracle script | | Schema | bytes | The oracle script's OBI schema | | Source Code URL | string | An optional absolute URI to the oracle script's source code | | Code | string | The oracle script's WebAssembly binary code. Can be raw or gzip compressed | | Owner | string | The address of owner for the oracle script to be deployed | | Sender | string | The address of the message sender | ### Edit message After an oracle script is deployed, its parameters can be updated if need be. To update an oracle script, we can broadcast a `MsgEditOracleScript` message to BandChain. `MsgEditOracleScript` contains 8 parameter: | Parameter | Type | Description string | | --------------- | ------ | -------------------------------------------------------------------------- | | OracleScriptID | int64 | The data source identifier | | Name | string | The name of the oracle script to be deployed | | Description | string | A description of the oracle script | | Schema | bytes | The oracle script's OBI schema | | Source Code URL | string | An optional absolute URI to the oracle script's source code | | Code | string | The oracle script's WebAssembly binary code. Can be raw or gzip compressed | | Owner | string | The address of owner for the oracle script to be deployed | | Sender | string | The address of the message sender | where the following field can be set as `["do-not-modify]` in their respective types if no changes are required from the current value: - `name`: `"[do-not-modify]"` - `description`: `"[do-not-modify"]"` - `schema`: `"[stringot-modify]"` - `source_code_url`: `"[do-not-modify]"` - `code`: `b"[do-not-modify]"` In order to broadcast these messages, our online IDE, [Band Builder](https://band-builder.netlify.app/) can be used to both write the data source and deploy it. Alternatively, our SDK's [bandchain.js](https://github.com/bandprotocol/bandchain.js) and [pyband](https://github.com/bandprotocol/pyband) can also be used. Examples on how to form a transaction to send messages on pyband can be found [here](https://docs.bandchain.org/client-library/pyband/transaction.html#example-use-case) and an example for bandchain.js can be found [here](https://docs.bandchain.org/client-library/bandchain.js/transaction.html#gettxdata-signature-publickey-signmode). <!-- questionaire What message needs to be sent to initialize an oracle script on BandChain? a. MsgDeployOracleScript b. MsgInitOracleScript c. MsgCreateOracleScript [Answer] d. None of the above --> # Chapter 4 - Conclusion Congratulations on finishing the primer course on BandChain's oracle scripts. With your newly acquired knowledge of how to create data sources and oracle scripts, you should be able to fully utilize BandChain to request any sort of data you'd like!