Skip to content

Commit

Permalink
[WIP] Add/oracle example (#3200)
Browse files Browse the repository at this point in the history
* Add Verity Move Oracles dependencies and example module

* Add TODO comment to Readme.md

* Add Rooch Oracle example with detailed documentation
  • Loading branch information
xlassix authored Jan 16, 2025
1 parent 9a354e9 commit b5ea9e8
Show file tree
Hide file tree
Showing 3 changed files with 335 additions and 0 deletions.
20 changes: 20 additions & 0 deletions examples/oracke/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "verity-move-example"
version = "0.0.1"

[dependencies]
MoveStdlib = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/move-stdlib", rev = "main" }
MoveosStdlib = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/moveos-stdlib", rev = "main" }
RoochFramework = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/rooch-framework", rev = "main" }
VerityMoveOracles = { git = "https://github.com/usherlabs/verity-move-oracles.git", subdir = "rooch", rev = "feature/open-ai" } # change feature/open-ai to main once PR#15 is merged


[addresses]
verity = "0x0d6144b074dd19a9ff581abd5bf7815a39222c8b3ac68ce5938c9d9723544e08"
verity_oracle_example = "_"
std = "0x1"
moveos_std = "0x2"
rooch_framework = "0x3"

[dev-addresses]
verity_oracle_example = "0x43"
69 changes: 69 additions & 0 deletions examples/oracke/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Rooch Oracle Example

This example demonstrates how to use and interact with Oracles in the Rooch Network.

## Documentation

For detailed documentation on setting up and using Oracles in Rooch Network, including:

- Prerequisites
- Step-by-step setup instructions
- Account creation
- Contract deployment
- Environment configuration
- URL management
- Running the orchestrator
- Making oracle requests
- Managing escrow balance

Please refer to our comprehensive guide in [docs/ROOCH.md](https://github.com/usherlabs/verity-move-oracles/blob/main/docs/ROOCH.md).

## Quick Start

1. Follow the setup instructions in [docs/ROOCH.md](https://github.com/usherlabs/verity-move-oracles/blob/main/docs/ROOCH.md)
2. Deploy the oracle contracts
3. Configure your environment
4. Run the orchestrator
5. Make oracle requests

## Supported APIs

### Twitter/X API
- User Endpoint: `https://api.x.com/2/users/` and `https://api.x.com/2/tweets/`
```bash
# Example: Get user followers count
rooch move run --function <oracle_address>::example_caller::request_data \
--sender-account default \
--args 'string:https://api.x.com/2/users/by/username/elonmusk?user.fields=public_metrics' \
--args 'string:GET' \
--args 'string:{}' \
--args 'string:{}' \
--args 'string:.data.public_metrics.followers_count' \
--args 'address:<orchestrator_address>' \
--args 'u256:50000000'
```

### OpenAI API
- Chat Completions: `https://api.openai.com/v1/chat/completions`
```bash
# Example: Simple GPT request
rooch move run --function <oracle_address>::example_caller::request_data \
--sender-account default \
--args 'string:https://api.openai.com/v1/chat/completions' \
--args 'string:POST' \
--args 'string:{}' \
--args 'string:{
"model": "gpt-4",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}' \
--args 'string:.choices[].message.content' \
--args 'address:<orchestrator_address>' \
--args 'u256:50000000'
```

Note: Replace `<oracle_address>` and `<orchestrator_address>` with your actual deployed addresses.

## Support

For additional help or questions, please refer to the main documentation or open an issue in the [repository](https://github.com/usherlabs/verity-move-oracles.git).
246 changes: 246 additions & 0 deletions examples/oracke/sources/example.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// Copyright (c) Usher Labs
// SPDX-License-Identifier: LGPL-2.1

// ? This module is an example caller used to demonstrate how to deploy Contracts on Rooch that integrate with Verity Move Oracles.
// ? Please keep aware of the OPTIONAL section in this module.
module verity_oracle_example::example_caller {
use moveos_std::event;
use moveos_std::account;
use moveos_std::object::{ObjectID};
use std::option::{Self, Option};
use std::vector;
use std::string::String;
use verity::oracles::{Self as Oracles};
use rooch_framework::gas_coin::RGas;
use rooch_framework::account_coin_store;
#[test_only]
use verity::oracles;

struct GlobalParams has key {
pending_requests: vector<ObjectID>,
}

#[test_only]
public fun init_for_test(){
oracles::init_for_test();
init();
}


// ? ------ OPTIONAL ------
// ? This is totally OPTIONAL
struct RequestFulfilledEvent has copy, drop {
request_url: String,
request_method: String,
response: Option<String>,
}
// \ ------ OPTIONAL ------

// Initiate the module with an empty vector of pending requests
// Requests are managed in the caller to prevent other modules from impersonating the calling module, and spoofing new data.
fun init(){
// let params = account::borrow_mut_resource<GlobalParams>(@verity_test_foreign_module); // account::borrow_mut_resource in init throws an error on deployment
// params.pending_requests = vector::empty<ObjectID>();
let signer = moveos_std::signer::module_signer<GlobalParams>();
account::move_resource_to(&signer, GlobalParams { pending_requests: vector::empty<ObjectID>() });
}

public entry fun request_data(
from: &signer,
url: String,
method: String,
headers: String,
body: String,
pick: String,
oracle: address,
amount: u256
) {
let http_request = Oracles::build_request(url, method, headers, body);

// We're passing the address and function identifier of the recipient address. in this from <module_name>::<function_name>
// If you do not want to pay for the Oracle to notify your contract, you can pass in option::none() as the argument.
let payment = account_coin_store::withdraw<RGas>(from, amount);
let request_id = Oracles::new_request_with_payment(http_request, pick, oracle, Oracles::with_notify(@verity_test_foreign_module, b"example_caller::receive_data"),payment);
// let no_notify_request_id = Oracles::new_request(http_request, pick, oracle, Oracles::without_notify());
let params = account::borrow_mut_resource<GlobalParams>(@verity_test_foreign_module);
vector::push_back(&mut params.pending_requests, request_id);
}

// This notify function is called by the Oracle.
// ! It must not include parameters, or return arguments.
public entry fun receive_data() {
let params = account::borrow_mut_resource<GlobalParams>(@verity_test_foreign_module);
let pending_requests = params.pending_requests;

let i = 0;
while (i < vector::length(&pending_requests)) {
let request_id = vector::borrow(&pending_requests, i);
// Remove the fulfilled request from the pending_requests vector
// This ensures unfulfilled requests are retained in the vector
if (option::is_some(&Oracles::get_response(request_id))) {
vector::remove(&mut params.pending_requests, i);
// Decrement i to account for the removed element
if (i > 0) {
i = i - 1;
};

// ? ------ OPTIONAL ------
let request_url = Oracles::get_request_params_url(request_id);
let request_method = Oracles::get_request_params_method(request_id);
let response = Oracles::get_response(request_id);
// For each fulfilment, emit an event
event::emit(RequestFulfilledEvent {
request_url,
request_method,
response,
});
// \ ------ OPTIONAL ------
};

i = i + 1;
};
}

#[view]
public fun pending_requests_count(): u64 {
let params = account::borrow_resource<GlobalParams>(@verity_test_foreign_module);
vector::length(&params.pending_requests)
}
}

#[test_only]
module verity_oracle_example::test_foreign_module {
use moveos_std::signer;
use verity_test_foreign_module::example_caller::{Self, request_data, pending_requests_count};
use rooch_framework::gas_coin;
use verity::registry;
use std::vector;



#[test_only]
struct Test has key {}

#[test_only]
struct TestOrchestrator has key {}

#[test_only]
fun setup_test() {
example_caller::init_for_test();
}

#[test]
fun test_request_data_basic() {
setup_test();
let test_signer = moveos_std::signer::module_signer<Test>();
let test_orchestrator = moveos_std::signer::module_signer<TestOrchestrator>();



// Setup test parameters
let url = std::string::utf8(b"https://api.test.com");
let method = std::string::utf8(b"GET");
let headers = std::string::utf8(b"Content-Type: application/json");
let body = std::string::utf8(b"");
let pick = std::string::utf8(b"$.data");
let amount = 100000u256;

registry::add_supported_url(&test_orchestrator, url, 100, 0, 1, 0);


// Fund the test account
gas_coin::faucet_entry(&test_signer, amount);

// Make request
request_data(&test_signer, url, method, headers, body, pick, signer::address_of(&test_orchestrator), amount);

// Verify request was stored

assert!(pending_requests_count() == 1, 0);
}


#[test]
fun test_multiple_requests() {
setup_test();
let test_signer = moveos_std::signer::module_signer<Test>();
let test_orchestrator = moveos_std::signer::module_signer<TestOrchestrator>();


// Setup test parameters for multiple requests
let urls = vector[
std::string::utf8(b"https://api.test.com/2/test/id2"),
std::string::utf8(b"https://api.test.com/2/my_profile"),
std::string::utf8(b"https://api.test.com/2/test")
];
let method = std::string::utf8(b"GET");
let headers = std::string::utf8(b"Content-Type: application/json");
let body = std::string::utf8(b"");
let pick = std::string::utf8(b"$.data");
let amount = 100u256;

registry::add_supported_url(&test_orchestrator, std::string::utf8(b"https://api.test.com/2/"), 100, 0, 1, 0);


// Fund the test account with enough for multiple requests
let total_amount = amount * 4;
gas_coin::faucet_entry(&test_signer, total_amount);

// Make multiple requests
let i = 0;
while (i < vector::length(&urls)) {
let url = *vector::borrow(&urls, i);
request_data(&test_signer, url, method, headers, body, pick, signer::address_of(&test_orchestrator), amount);
i = i + 1;
};

// Verify all requests were stored
assert!(pending_requests_count() == 3, 2);

}

#[test]
#[expected_failure(abort_code = rooch_framework::coin_store::ErrorInsufficientBalance, location = rooch_framework::coin_store)] // Adjust abort code as needed
fun test_insufficient_funds() {
setup_test();
let test_signer = moveos_std::signer::module_signer<Test>();

let url = std::string::utf8(b"https://api.test.com");
let method = std::string::utf8(b"GET");
let headers = std::string::utf8(b"Content-Type: application/json");
let body = std::string::utf8(b"");
let pick = std::string::utf8(b"$.data");
let oracle = @0x1;
let amount = 100u256;



gas_coin::faucet_entry(&test_signer, amount/10);
request_data(&test_signer, url, method, headers, body, pick, oracle, amount);
}

#[test]
fun test_request_with_body() {
setup_test();
let test_signer = moveos_std::signer::module_signer<Test>();
let test_orchestrator = moveos_std::signer::module_signer<TestOrchestrator>();

let url = std::string::utf8(b"https://api.test.com/yud");
let method = std::string::utf8(b"POST");
let headers = std::string::utf8(b"Content-Type: application/json");
let body = std::string::utf8(b"{\"key\":\"value\"}");
let pick = std::string::utf8(b".data");
let oracle = signer::address_of(&test_orchestrator);
let amount = 1000u256;


gas_coin::faucet_entry(&test_signer, amount);


registry::add_supported_url(&test_orchestrator, std::string::utf8(b"https://api.test.com/"), 100, 0, 1, 0);
request_data(&test_signer, url, method, headers, body, pick, oracle, amount);

assert!(pending_requests_count() == 1, 4);

}
}

0 comments on commit b5ea9e8

Please sign in to comment.