diff --git a/src/data-feeds/aptos/.gitignore b/src/data-feeds/aptos/.gitignore new file mode 100644 index 0000000..243f545 --- /dev/null +++ b/src/data-feeds/aptos/.gitignore @@ -0,0 +1,2 @@ +.aptos/ +build/ \ No newline at end of file diff --git a/src/data-feeds/aptos/Move.toml b/src/data-feeds/aptos/Move.toml new file mode 100644 index 0000000..eac2b92 --- /dev/null +++ b/src/data-feeds/aptos/Move.toml @@ -0,0 +1,17 @@ +[package] +name = "ChainlinkLocal" +version = "0.0.1" + +[addresses] +move_stdlib = "0x1" +aptos_std = "0x1" +chainlink_local = "_" +sender = "_" + +[dev-addresses] +chainlink_local = "0x777" +sender = "0xb0b" + +[dependencies] +AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework", rev = "main" } +MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib", rev = "main" } diff --git a/src/data-feeds/aptos/README.md b/src/data-feeds/aptos/README.md new file mode 100644 index 0000000..7cd58c0 --- /dev/null +++ b/src/data-feeds/aptos/README.md @@ -0,0 +1,251 @@ +## Chainlink Local Data Feeds on Aptos Testnet + +This repository contains two mock contracts that simulate the behavior of the +Chainlink Local Data Feeds on the Aptos testnet: + +- `mock_data_feeds_registry.move` +- `mock_data_feeds_router.move` + +The `ChainlinkLocal` package is deployed on the Aptos testnet at the following +address: + +- `0xf22a4370fa80c7f3cd6815fe976d4b00e7b4c228d7c4b4f310b330b08eca5dea` + +To download it, run the following command: + +``` +aptos move download --account 0xf22a4370fa80c7f3cd6815fe976d4b00e7b4c228d7c4b4f310b330b08eca5dea --package ChainlinkLocal +``` + +### Tutorial + +This guide explains how to use Chainlink Data Feeds with your Move smart +contracts on Aptos testnet and test them locally using Chainlink Local. You will +use the [Aptos CLI](https://aptos.dev/en/build/cli) to compile, publish, and +interact with your contract. + +#### Requirements + +Make sure you have the Aptos CLI installed. You can run `aptos help` in your +terminal to verify if the CLI is correctly installed. + +#### Step 1: Create a new project + +Create a new directory for your project and navigate to it in your terminal + +``` +mkdir aptos-data-feeds-local && cd aptos-data-feeds-local +``` + +#### Step 2: Create a new testnet account + +Run the following command in your terminal to create a new account on testnet: + +``` +aptos init --network=testnet --assume-yes +``` + +#### Step 3: Create a new Move project + +Run the following command in your terminal to create a new Move project: + +``` +aptos move init --name aptos-data-feeds-local +``` + +#### Step 4: Update Move.toml file + +Update your Move.toml file to include the required dependencies and addresses: + +```toml +[package] +name = "my-app" +version = "1.0.0" +authors = [] + +[addresses] +sender = "" +chainlink_local = "0xf22a4370fa80c7f3cd6815fe976d4b00e7b4c228d7c4b4f310b330b08eca5dea" +move_stdlib = "0x1" +aptos_std = "0x1" + +[dev-addresses] + +[dependencies] +AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework", rev = "main" } +MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib", rev = "main" } +ChainlinkLocal = { local = "./ChainlinkLocal" } +``` + +#### Step 5: Download the ChainlinkLocal package + +Run the following command in your terminal to download the ChainlinkLocal +package: + +``` +aptos move download --account 0xf22a4370fa80c7f3cd6815fe976d4b00e7b4c228d7c4b4f310b330b08eca5dea --package ChainlinkLocal +``` + +#### Step 6: Create a consumer smart contract + +Create a new file named `MyOracleContractTest.move` in the `sources` directory +with the following content: + +```rust +module sender::MyOracleContractTest { + use std::vector; + use std::signer; + + // use data_feeds::router::get_benchmarks; + // use data_feeds::registry::{Benchmark, get_benchmark_value, get_benchmark_timestamp}; + use chainlink_local::mock_data_feeds_router::get_benchmarks; + use chainlink_local::mock_data_feeds_registry::{Benchmark, get_benchmark_value, get_benchmark_timestamp}; + + use move_stdlib::option::{Option, some, none}; + + struct PriceData has copy, key, store, drop { + /// The price value with 18 decimal places of precision + price: u256, + /// Unix timestamp in seconds + timestamp: u256, + } + + // Function to fetch and store the price data for a given feed ID + public entry fun fetch_price(account: &signer, feed_id: vector) acquires PriceData { + let feed_ids = vector[feed_id]; // Use the passed feed_id + let billing_data = vector[]; + let benchmarks: vector = get_benchmarks(account, feed_ids, billing_data); + let benchmark = vector::pop_back(&mut benchmarks); + let price: u256 = get_benchmark_value(&benchmark); + let timestamp: u256 = get_benchmark_timestamp(&benchmark); + + // Check if PriceData exists and update it + if (exists(signer::address_of(account))) { + let data = borrow_global_mut(signer::address_of(account)); + data.price = price; + data.timestamp = timestamp; + } else { + // If PriceData does not exist, create a new one + move_to(account, PriceData { price, timestamp }); + } + } + + // View function to get the stored price data + #[view] + public fun get_price_data(account_address: address): Option acquires PriceData { + if (exists(account_address)) { + let data = borrow_global(account_address); + some(*data) + } else { + none() + } + } + + // Added for testing purposes + + public fun get_price(data: &PriceData): u256 { + data.price + } + + public fun get_timestamp(data: &PriceData): u256 { + data.timestamp + } +} +``` + +#### Step 7: Compile the contract + +Run the following command in your terminal to compile the contract: + +``` +aptos move compile +``` + +#### Step 8: Create a test + +Create a new file named `MyOracleContractTest_tests.move` in the `tests` +directory with the following content: + +```rust +#[test_only] +module sender::MyOracleContractTest_tests { + use sender::MyOracleContractTest; + use std::string; + use move_stdlib::option; + use aptos_framework::account; + + use chainlink_local::mock_data_feeds_registry; + use chainlink_local::mock_data_feeds_router; + + const FEED_ID_ETH_USD: vector = b"ETH/USD"; + const MOCK_PRICE: u256 = 1800000000000000000000; // 1800 ETH/USD with 18 decimals + const MOCK_TIMESTAMP: u256 = 1678912345; + + #[test(publisher = @chainlink_local, sender = @sender)] + fun test_smoke( + publisher: &signer, + sender: &signer + ) { + // Initialize the publisher account + account::create_account_for_test(@chainlink_local); + + // Initialize Chainlink Local + mock_data_feeds_registry::initialize(publisher); + mock_data_feeds_router::initialize(publisher); + + // Add ETH/USD feed + let feed_ids = vector[FEED_ID_ETH_USD]; + let descriptions = vector[string::utf8(FEED_ID_ETH_USD)]; + let config_id = b"MOCK_CONFIG"; + mock_data_feeds_registry::add_feeds(feed_ids, descriptions, config_id); + + // Set mock price + mock_data_feeds_registry::set_mock_price( + FEED_ID_ETH_USD, + MOCK_PRICE, + MOCK_TIMESTAMP, + b"" // Empty report for this test + ); + + // Fetch price data from the mock data feed + MyOracleContractTest::fetch_price(sender, FEED_ID_ETH_USD); + + // Asserts + let price_data_opt = MyOracleContractTest::get_price_data(@sender); + assert!(option::is_some(&price_data_opt), 1); + + let price_data = option::borrow(&price_data_opt); + std::debug::print(price_data); // Visualize the PriceData struct + assert!(MyOracleContractTest::get_price(price_data) == MOCK_PRICE, 2); + assert!(MyOracleContractTest::get_timestamp(price_data) == MOCK_TIMESTAMP, 3); + } +} +``` + +#### Step 9: Run the test + +Run the following command in your terminal to run the test: + +``` +aptos move test +``` + +You will see the similar test result in your terminal: + +``` +INCLUDING DEPENDENCY AptosFramework +INCLUDING DEPENDENCY AptosStdlib +INCLUDING DEPENDENCY ChainlinkLocal +INCLUDING DEPENDENCY MoveStdlib +BUILDING my-app +Running Move unit tests +[debug] 0x1d07336b83f427515ff6dae6fe79079cb67333795b480e1ce488d80f8c729f77::MyOracleContractTest::PriceData { + price: 1800000000000000000000, + timestamp: 1678912345 +} +[ PASS ] 0x1d07336b83f427515ff6dae6fe79079cb67333795b480e1ce488d80f8c729f77::MyOracleContractTest_tests::test_smoke +Test result: OK. Total tests: 1; passed: 1; failed: 0 +{ + "Result": "Success" +} +``` diff --git a/src/data-feeds/aptos/sources/mock_data_feeds_registry.move b/src/data-feeds/aptos/sources/mock_data_feeds_registry.move new file mode 100644 index 0000000..10a483c --- /dev/null +++ b/src/data-feeds/aptos/sources/mock_data_feeds_registry.move @@ -0,0 +1,453 @@ +module chainlink_local::mock_data_feeds_registry { + use std::error; + use std::vector; + use std::event; + use std::simple_map::{Self, SimpleMap}; + use std::string::{String}; + + use aptos_framework::object::{Self, ExtendRef, TransferRef}; + + const APP_OBJECT_SEED: vector = b"MOCK_REGISTRY"; + + struct MockRegistry has key, store, drop { + extend_ref: ExtendRef, + transfer_ref: TransferRef, + feeds: SimpleMap, Feed>, + } + + struct Feed has key, store, drop, copy { + description: String, + config_id: vector, + benchmark: u256, + report: vector, + observation_timestamp: u256 + } + + struct Benchmark has store, drop { + benchmark: u256, + observation_timestamp: u256 + } + + struct Report has store, drop { + report: vector, + observation_timestamp: u256 + } + + struct FeedMetadata has store, drop, key { + description: String, + config_id: vector + } + + struct FeedConfig has drop { + feed_id: vector, + feed: Feed + } + + // ================================================================ + // Events + // ================================================================ + + #[event] + struct FeedSet has drop, store { + feed_id: vector, + description: String, + config_id: vector + } + + #[event] + struct FeedRemoved has drop, store { + feed_id: vector + } + + #[event] + struct FeedUpdated has drop, store { + feed_id: vector, + observation_timestamp: u256, + benchmark: u256, + report: vector + } + + #[event] + struct StaleReport has drop, store { + feed_id: vector, + latest_timestamp: u256, + report_timestamp: u256 + } + + // ================================================================ + // Errors + // ================================================================ + + // Error codes from Mock mirrors the errors from the actual Registry contract + const EDUPLICATE_ELEMENTS: u64 = 2; + const EFEED_EXISTS: u64 = 3; + const EFEED_NOT_CONFIGURED: u64 = 4; + const EUNEQUAL_ARRAY_LENGTHS: u64 = 6; + + fun assert_no_duplicates(a: &vector) { + let len = vector::length(a); + for (i in 0..len) { + for (j in (i + 1)..len) { + assert!( + vector::borrow(a, i) != vector::borrow(a, j), + error::invalid_argument(EDUPLICATE_ELEMENTS) + ); + } + } + } + + // ================================================================ + // Constructor + // ================================================================ + + #[test_only] + public fun initialize(publisher: &signer) { + let constructor_ref = object::create_named_object(publisher, APP_OBJECT_SEED); + + let extend_ref = object::generate_extend_ref(&constructor_ref); + let transfer_ref = object::generate_transfer_ref(&constructor_ref); + let object_signer = object::generate_signer(&constructor_ref); + + move_to( + &object_signer, + MockRegistry { + extend_ref, + transfer_ref, + feeds: simple_map::new(), + } + ); + } + + inline fun get_state_addr(): address { + object::create_object_address(&@chainlink_local, APP_OBJECT_SEED) + } + + // ================================================================ + // Add & Remove Feeds + // ================================================================ + + public entry fun add_feeds( + feed_ids: vector>, + descriptions: vector, + config_id: vector + ) acquires MockRegistry { + let registry = borrow_global_mut(get_state_addr()); + + assert_no_duplicates(&feed_ids); + + assert!( + vector::length(&feed_ids) == vector::length(&descriptions), + error::invalid_argument(EUNEQUAL_ARRAY_LENGTHS) + ); + + vector::zip_ref( + &feed_ids, + &descriptions, + |feed_id, description| { + assert!( + !simple_map::contains_key(®istry.feeds, feed_id), + error::invalid_argument(EFEED_EXISTS) + ); + + let feed = Feed { + description: *description, + config_id, + benchmark: 0, + report: vector::empty(), + observation_timestamp: 0 + }; + simple_map::add(&mut registry.feeds, *feed_id, feed); + + event::emit( + FeedSet { feed_id: *feed_id, description: *description, config_id } + ); + } + ); + } + + public entry fun remove_feeds( + feed_ids: vector> + ) acquires MockRegistry { + let registry = borrow_global_mut(get_state_addr()); + + assert_no_duplicates(&feed_ids); + + vector::for_each( + feed_ids, + |feed_id| { + assert!( + simple_map::contains_key(®istry.feeds, &feed_id), + error::invalid_argument(EFEED_NOT_CONFIGURED) + ); + simple_map::remove(&mut registry.feeds, &feed_id); + } + ); + } + + // ================================================================ + // Set Mock Price + // ================================================================ + + public entry fun set_mock_price( + feed_id: vector, + benchmark: u256, + observation_timestamp: u256, + report: vector + ) acquires MockRegistry { + let registry = borrow_global_mut(get_state_addr()); + assert!( + simple_map::contains_key(®istry.feeds, &feed_id), + error::invalid_argument(EFEED_NOT_CONFIGURED) + ); + + let feed = simple_map::borrow_mut(&mut registry.feeds, &feed_id); + + if (feed.observation_timestamp >= observation_timestamp) { + event::emit( + StaleReport { + feed_id, + latest_timestamp: feed.observation_timestamp, + report_timestamp: observation_timestamp + } + ); + }; + + feed.benchmark = benchmark; + feed.observation_timestamp = observation_timestamp; + feed.report = report; + + event::emit( + FeedUpdated { + feed_id, + observation_timestamp, + benchmark, + report + } + ); + } + + // ================================================================ + // Getters + // ================================================================ + + #[view] + public fun get_feeds(): vector acquires MockRegistry { + let registry = borrow_global(get_state_addr()); + let feed_configs = vector[]; + let (feed_ids, feeds) = simple_map::to_vec_pair(registry.feeds); + vector::zip_ref( + &feed_ids, + &feeds, + |feed_id, feed| { + vector::push_back( + &mut feed_configs, + FeedConfig { feed_id: *feed_id, feed: *feed } + ); + } + ); + feed_configs + } + + #[view] + public fun get_feed_metadata( + feed_ids: vector> + ): vector acquires MockRegistry { + let registry = borrow_global(get_state_addr()); + + vector::map( + feed_ids, + |feed_id| { + assert!( + simple_map::contains_key(®istry.feeds, &feed_id), + error::invalid_argument(EFEED_NOT_CONFIGURED) + ); + + let feed = simple_map::borrow(®istry.feeds, &feed_id); + + FeedMetadata { description: feed.description, config_id: feed.config_id } + } + ) + } + + public fun get_benchmarks( + _authority: &signer, feed_ids: vector> + ): vector acquires MockRegistry { + let registry = borrow_global(get_state_addr()); + + vector::map( + feed_ids, + |feed_id| { + assert!( + simple_map::contains_key(®istry.feeds, &feed_id), + error::invalid_argument(EFEED_NOT_CONFIGURED) + ); + + let feed = simple_map::borrow(®istry.feeds, &feed_id); + + Benchmark { + benchmark: feed.benchmark, + observation_timestamp: feed.observation_timestamp + } + } + ) + } + + public fun get_reports( + _authority: &signer, feed_ids: vector> + ): vector acquires MockRegistry { + let registry = borrow_global(get_state_addr()); + + vector::map( + feed_ids, + |feed_id| { + assert!( + simple_map::contains_key(®istry.feeds, &feed_id), + error::invalid_argument(EFEED_NOT_CONFIGURED) + ); + + let feed = simple_map::borrow(®istry.feeds, &feed_id); + + Report { + report: feed.report, + observation_timestamp: feed.observation_timestamp + } + } + ) + } + + // ================================================================ + // Struct accessors + // ================================================================ + + public fun get_benchmark_value(result: &Benchmark): u256 { + result.benchmark + } + + public fun get_benchmark_timestamp(result: &Benchmark): u256 { + result.observation_timestamp + } + + public fun get_report_value(result: &Report): vector { + result.report + } + + public fun get_report_timestamp(result: &Report): u256 { + result.observation_timestamp + } + + public fun get_feed_metadata_description(result: &FeedMetadata): String { + result.description + } + + public fun get_feed_metadata_config_id(result: &FeedMetadata): vector { + result.config_id + } + + // ================================================================ + // Tests + // ================================================================ + + // Test error codes + const ETEST_WRONG_COUNT: u64 = 101; + const ETEST_WRONG_DESCRIPTION: u64 = 102; + const ETEST_WRONG_CONFIG: u64 = 103; + const ETEST_WRONG_BENCHMARK: u64 = 104; + const ETEST_WRONG_TIMESTAMP: u64 = 105; + const ETEST_WRONG_REPORT: u64 = 106; + + #[test_only] + fun set_up_test(publisher: &signer) { + use std::signer; + use aptos_framework::account::{Self}; + account::create_account_for_test(signer::address_of(publisher)); + + initialize(publisher); + } + + #[test_only] + fun prepare_test_scenario(): (vector>, vector) acquires MockRegistry { + let feed_ids = vector[b"BTC/USD", b"ETH/USD"]; + let descriptions = vector[std::string::utf8(b"BTC/USD"), std::string::utf8(b"ETH/USD")]; + let config_id = b"MOCK_CONFIG"; + + add_feeds(feed_ids, descriptions, config_id); + + (feed_ids, config_id) + } + + #[test(publisher = @chainlink_local)] + fun test_add_feeds(publisher: &signer) acquires MockRegistry { + set_up_test(publisher); + let (feed_ids, config_id) = prepare_test_scenario(); + + let feeds = get_feeds(); + // std::debug::print(&feeds); + assert!(vector::length(&feeds) == 2, ETEST_WRONG_COUNT); + + let feed_metadata = get_feed_metadata(feed_ids); + assert!(vector::length(&feed_metadata) == 2, ETEST_WRONG_COUNT); + + let btc_usd_feed = vector::borrow(&feed_metadata, 0); + assert!(get_feed_metadata_description(btc_usd_feed) == std::string::utf8(b"BTC/USD"), ETEST_WRONG_DESCRIPTION); + assert!(get_feed_metadata_config_id(btc_usd_feed) == config_id, ETEST_WRONG_CONFIG); + + let eth_usd_feed = vector::borrow(&feed_metadata, 1); + assert!(get_feed_metadata_description(eth_usd_feed) == std::string::utf8(b"ETH/USD"), ETEST_WRONG_DESCRIPTION); + assert!(get_feed_metadata_config_id(eth_usd_feed) == config_id, ETEST_WRONG_CONFIG); + } + + #[test(publisher = @chainlink_local)] + #[expected_failure(abort_code = 65540, location = chainlink_local::mock_data_feeds_registry)] + fun test_remove_feeds(publisher: &signer) acquires MockRegistry { + set_up_test(publisher); + let (_, _) = prepare_test_scenario(); + + let feed_id_to_remove = vector[b"BTC/USD"]; + + remove_feeds(feed_id_to_remove); + + let feeds = get_feeds(); + assert!(vector::length(&feeds) == 1, ETEST_WRONG_COUNT); + + let expected_feed = b"ETH/USD"; + let feed_metadata = get_feed_metadata(vector[expected_feed]); + assert!(vector::length(&feed_metadata) == 1, ETEST_WRONG_COUNT); + assert!(get_feed_metadata_description(vector::borrow(&feed_metadata, 0)) == std::string::utf8(expected_feed), ETEST_WRONG_DESCRIPTION); + + // This should revert with EFEED_NOT_CONFIGURED + let _ = get_feed_metadata(feed_id_to_remove); + } + + #[test(publisher = @chainlink_local, sender = @sender)] + fun test_set_mock_price(publisher: &signer, sender: &signer) acquires MockRegistry { + set_up_test(publisher); + let (feed_ids, _) = prepare_test_scenario(); + + let btc_usd_feed = *vector::borrow(&feed_ids, 0); + + let mock_benchmark = 100; + let mock_observation_timestamp = 1000; + let mock_report = x"00031111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066b3a12c0000000000000000000000000000000000000000000000000000000066b3a12c00000000000000000000000000000000000000000000000000000000000494a800000000000000000000000000000000000000000000000000000000000494a80000000000000000000000000000000000000000000000000000000066c2e36c00000000000000000000000000000000000000000000000000000000000494a800000000000000000000000000000000000000000000000000000000000494a800000000000000000000000000000000000000000000000000000000000494a8"; + + set_mock_price(btc_usd_feed, mock_benchmark, mock_observation_timestamp, mock_report); + + let benchmarks = get_benchmarks(sender, feed_ids); + assert!(vector::length(&benchmarks) == 2, ETEST_WRONG_COUNT); + + let btc_usd_benchmark = vector::borrow(&benchmarks, 0); + assert!(get_benchmark_value(btc_usd_benchmark) == mock_benchmark, ETEST_WRONG_BENCHMARK); + assert!(get_benchmark_timestamp(btc_usd_benchmark) == mock_observation_timestamp, ETEST_WRONG_TIMESTAMP); + + // We haven't set the benchmark for ETH/USD, so it should have default values + let eth_usd_benchmark = vector::borrow(&benchmarks, 1); + assert!(get_benchmark_value(eth_usd_benchmark) == 0, ETEST_WRONG_BENCHMARK); + assert!(get_benchmark_timestamp(eth_usd_benchmark) == 0, ETEST_WRONG_TIMESTAMP); + + let reports = get_reports(sender, feed_ids); + assert!(vector::length(&reports) == 2, ETEST_WRONG_COUNT); + + let btc_usd_report = vector::borrow(&reports, 0); + assert!(get_report_value(btc_usd_report) == mock_report, ETEST_WRONG_REPORT); + assert!(get_report_timestamp(btc_usd_report) == mock_observation_timestamp, ETEST_WRONG_TIMESTAMP); + } +} diff --git a/src/data-feeds/aptos/sources/mock_data_feeds_router.move b/src/data-feeds/aptos/sources/mock_data_feeds_router.move new file mode 100644 index 0000000..c7a2080 --- /dev/null +++ b/src/data-feeds/aptos/sources/mock_data_feeds_router.move @@ -0,0 +1,57 @@ +module chainlink_local::mock_data_feeds_router { + use aptos_framework::object::{Self, ExtendRef, TransferRef}; + + use chainlink_local::mock_data_feeds_registry::{Self, Benchmark, Report}; + + const APP_OBJECT_SEED: vector = b"MOCK_ROUTER"; + + struct MockRouter has key, store, drop { + extend_ref: ExtendRef, + transfer_ref: TransferRef + } + + // ================================================================ + // Constructor + // ================================================================ + + #[test_only] + public fun initialize(publisher: &signer) { + let constructor_ref = object::create_named_object(publisher, APP_OBJECT_SEED); + + let extend_ref = object::generate_extend_ref(&constructor_ref); + let transfer_ref = object::generate_transfer_ref(&constructor_ref); + let object_signer = object::generate_signer(&constructor_ref); + + move_to( + &object_signer, + MockRouter { + extend_ref, + transfer_ref + } + ); + } + + inline fun get_state_addr(): address { + object::create_object_address(&@chainlink_local, APP_OBJECT_SEED) + } + + // ================================================================ + // Getters + // ================================================================ + + public fun get_benchmarks( + _authority: &signer, feed_ids: vector>, _billing_data: vector + ): vector acquires MockRouter { + let _router = borrow_global(get_state_addr()); + + mock_data_feeds_registry::get_benchmarks(_authority, feed_ids) + } + + public fun get_reports( + _authority: &signer, feed_ids: vector>, _billing_data: vector + ): vector acquires MockRouter { + let _router = borrow_global(get_state_addr()); + + mock_data_feeds_registry::get_reports(_authority, feed_ids) + } +} \ No newline at end of file diff --git a/src/data-feeds/aptos/tests/MyOracleContractTest.move b/src/data-feeds/aptos/tests/MyOracleContractTest.move new file mode 100644 index 0000000..3a95544 --- /dev/null +++ b/src/data-feeds/aptos/tests/MyOracleContractTest.move @@ -0,0 +1,59 @@ +module sender::MyOracleContractTest { + use std::vector; + use std::signer; + + // use data_feeds::router::get_benchmarks; + // use data_feeds::registry::{Benchmark, get_benchmark_value, get_benchmark_timestamp}; + use chainlink_local::mock_data_feeds_router::get_benchmarks; + use chainlink_local::mock_data_feeds_registry::{Benchmark, get_benchmark_value, get_benchmark_timestamp}; + + use move_stdlib::option::{Option, some, none}; + + struct PriceData has copy, key, store, drop { + /// The price value with 18 decimal places of precision + price: u256, + /// Unix timestamp in seconds + timestamp: u256, + } + + // Function to fetch and store the price data for a given feed ID + public entry fun fetch_price(account: &signer, feed_id: vector) acquires PriceData { + let feed_ids = vector[feed_id]; // Use the passed feed_id + let billing_data = vector[]; + let benchmarks: vector = get_benchmarks(account, feed_ids, billing_data); + let benchmark = vector::pop_back(&mut benchmarks); + let price: u256 = get_benchmark_value(&benchmark); + let timestamp: u256 = get_benchmark_timestamp(&benchmark); + + // Check if PriceData exists and update it + if (exists(signer::address_of(account))) { + let data = borrow_global_mut(signer::address_of(account)); + data.price = price; + data.timestamp = timestamp; + } else { + // If PriceData does not exist, create a new one + move_to(account, PriceData { price, timestamp }); + } + } + + // View function to get the stored price data + #[view] + public fun get_price_data(account_address: address): Option acquires PriceData { + if (exists(account_address)) { + let data = borrow_global(account_address); + some(*data) + } else { + none() + } + } + + // Added for testing purposes + + public fun get_price(data: &PriceData): u256 { + data.price + } + + public fun get_timestamp(data: &PriceData): u256 { + data.timestamp + } +} diff --git a/src/data-feeds/aptos/tests/MyOracleContractTest_tests.move b/src/data-feeds/aptos/tests/MyOracleContractTest_tests.move new file mode 100644 index 0000000..58657e4 --- /dev/null +++ b/src/data-feeds/aptos/tests/MyOracleContractTest_tests.move @@ -0,0 +1,53 @@ +#[test_only] +module sender::MyOracleContractTest_tests { + use sender::MyOracleContractTest; + use std::string; + use move_stdlib::option; + use aptos_framework::account; + + use chainlink_local::mock_data_feeds_registry; + use chainlink_local::mock_data_feeds_router; + + const FEED_ID_ETH_USD: vector = b"ETH/USD"; + const MOCK_PRICE: u256 = 1800000000000000000000; // 1800 ETH/USD with 18 decimals + const MOCK_TIMESTAMP: u256 = 1678912345; + + #[test(publisher = @chainlink_local, sender = @sender)] + fun test_smoke( + publisher: &signer, + sender: &signer + ) { + // Initialize the publisher account + account::create_account_for_test(@chainlink_local); + + // Initialize Chainlink Local + mock_data_feeds_registry::initialize(publisher); + mock_data_feeds_router::initialize(publisher); + + // Add ETH/USD feed + let feed_ids = vector[FEED_ID_ETH_USD]; + let descriptions = vector[string::utf8(FEED_ID_ETH_USD)]; + let config_id = b"MOCK_CONFIG"; + mock_data_feeds_registry::add_feeds(feed_ids, descriptions, config_id); + + // Set mock price + mock_data_feeds_registry::set_mock_price( + FEED_ID_ETH_USD, + MOCK_PRICE, + MOCK_TIMESTAMP, + b"" // Empty report for this test + ); + + // Fetch price data from the mock data feed + MyOracleContractTest::fetch_price(sender, FEED_ID_ETH_USD); + + // Asserts + let price_data_opt = MyOracleContractTest::get_price_data(@sender); + assert!(option::is_some(&price_data_opt), 1); + + let price_data = option::borrow(&price_data_opt); + std::debug::print(price_data); // Visualize the PriceData struct + assert!(MyOracleContractTest::get_price(price_data) == MOCK_PRICE, 2); + assert!(MyOracleContractTest::get_timestamp(price_data) == MOCK_TIMESTAMP, 3); + } +} \ No newline at end of file