diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index f24cf4b8c..29271e933 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -401,6 +401,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chainlink_solana" +version = "0.1.0" +dependencies = [ + "borsh", + "borsh-derive", + "solana-program", +] + [[package]] name = "constant_time_eq" version = "0.1.5" diff --git a/contracts/crates/chainlink-solana/Cargo.toml b/contracts/crates/chainlink-solana/Cargo.toml new file mode 100644 index 000000000..621add26c --- /dev/null +++ b/contracts/crates/chainlink-solana/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "chainlink_solana" +description = "Chainlink client for Solana" +version = "0.1.0" +edition = "2018" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "lib"] +name = "chainlink_solana" + +[features] +default = [] + +[dependencies] +solana-program = "1.8.6" +borsh = "0.9.1" +borsh-derive = "0.9.1" \ No newline at end of file diff --git a/contracts/crates/chainlink-solana/LICENSE b/contracts/crates/chainlink-solana/LICENSE new file mode 100644 index 000000000..812debd8e --- /dev/null +++ b/contracts/crates/chainlink-solana/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 SmartContract ChainLink, Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/contracts/crates/chainlink-solana/README.md b/contracts/crates/chainlink-solana/README.md new file mode 100644 index 000000000..14ed90beb --- /dev/null +++ b/contracts/crates/chainlink-solana/README.md @@ -0,0 +1,3 @@ +# chainlink-solana + +Chainlink client for Solana. \ No newline at end of file diff --git a/contracts/crates/chainlink-solana/src/lib.rs b/contracts/crates/chainlink-solana/src/lib.rs new file mode 100644 index 000000000..00db7e0c4 --- /dev/null +++ b/contracts/crates/chainlink-solana/src/lib.rs @@ -0,0 +1,115 @@ +//! Chainlink feed client for Solana. +#![deny(rustdoc::all)] +#![allow(rustdoc::missing_doc_code_examples)] +#![deny(missing_docs)] + +use borsh::{BorshDeserialize, BorshSerialize}; + +use solana_program::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction}, + program::invoke, + program_error::ProgramError, + pubkey::Pubkey, +}; + +#[derive(BorshSerialize, BorshDeserialize)] +enum Query { + Version, + Decimals, + Description, + RoundData { round_id: u32 }, + LatestRoundData, + Aggregator, +} + +/// Represents a single oracle round. +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Round { + /// The round id. + pub round_id: u32, + /// Round timestamp, as reported by the oracle. + pub timestamp: u64, + /// Current answer, formatted to `decimals` decimal places. + pub answer: i128, +} + +fn query<'info, T: BorshDeserialize>( + program_id: AccountInfo<'info>, + feed: AccountInfo<'info>, + scope: Query, +) -> Result { + use std::io::{Cursor, Write}; + + const QUERY_INSTRUCTION_DISCRIMINATOR: &[u8] = + &[0x27, 0xfb, 0x82, 0x9f, 0x2e, 0x88, 0xa4, 0xa9]; + + // Avoid array resizes by using the maximum response size as the initial capacity. + const MAX_SIZE: usize = QUERY_INSTRUCTION_DISCRIMINATOR.len() + std::mem::size_of::(); + + let mut data = Cursor::new(Vec::with_capacity(MAX_SIZE)); + data.write_all(QUERY_INSTRUCTION_DISCRIMINATOR)?; + scope.serialize(&mut data)?; + + let ix = Instruction { + program_id: *program_id.key, + accounts: vec![AccountMeta::new_readonly(*feed.key, false)], + data: data.into_inner(), + }; + + invoke(&ix, &[feed.clone()])?; + + let (_key, data) = + solana_program::program::get_return_data().expect("chainlink store had no return_data!"); + let data = T::try_from_slice(&data)?; + Ok(data) +} + +/// Query the feed version. +pub fn version<'info>( + program_id: AccountInfo<'info>, + feed: AccountInfo<'info>, +) -> Result { + query(program_id, feed, Query::Version) +} + +/// Returns the amount of decimal places. +pub fn decimals<'info>( + program_id: AccountInfo<'info>, + feed: AccountInfo<'info>, +) -> Result { + query(program_id, feed, Query::Decimals) +} + +/// Returns the feed description. +pub fn description<'info>( + program_id: AccountInfo<'info>, + feed: AccountInfo<'info>, +) -> Result { + query(program_id, feed, Query::Description) +} + +/// Returns round data for a specific `round_id`. +pub fn round_data<'info>( + program_id: AccountInfo<'info>, + feed: AccountInfo<'info>, + round_id: u32, +) -> Result { + query(program_id, feed, Query::RoundData { round_id }) +} + +/// Returns round data for the latest round. +pub fn latest_round_data<'info>( + program_id: AccountInfo<'info>, + feed: AccountInfo<'info>, +) -> Result { + query(program_id, feed, Query::LatestRoundData) +} + +/// Returns the address of the underlying OCR2 aggregator. +pub fn aggregator<'info>( + program_id: AccountInfo<'info>, + feed: AccountInfo<'info>, +) -> Result { + query(program_id, feed, Query::Aggregator) +} diff --git a/contracts/examples/hello-world/Cargo.lock b/contracts/examples/hello-world/Cargo.lock index 66825fb9c..38047632f 100644 --- a/contracts/examples/hello-world/Cargo.lock +++ b/contracts/examples/hello-world/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "access-controller" -version = "0.1.0" -dependencies = [ - "anchor-lang", - "arrayvec 0.1.0", - "static_assertions", -] - [[package]] name = "ahash" version = "0.4.7" @@ -199,10 +190,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" -[[package]] -name = "arrayvec" -version = "0.1.0" - [[package]] name = "arrayvec" version = "0.7.2" @@ -260,7 +247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" dependencies = [ "arrayref", - "arrayvec 0.7.2", + "arrayvec", "cc", "cfg-if", "constant_time_eq", @@ -403,6 +390,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chainlink-solana" +version = "0.1.0" +dependencies = [ + "borsh", + "borsh-derive", + "solana-program", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -569,7 +565,7 @@ name = "hello-world" version = "0.1.0" dependencies = [ "anchor-lang", - "store", + "chainlink-solana", ] [[package]] @@ -1107,22 +1103,6 @@ dependencies = [ "syn", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "store" -version = "0.1.0" -dependencies = [ - "access-controller", - "anchor-lang", - "arrayvec 0.1.0", - "bytemuck", -] - [[package]] name = "subtle" version = "2.4.1" diff --git a/contracts/examples/hello-world/programs/hello-world/Cargo.toml b/contracts/examples/hello-world/programs/hello-world/Cargo.toml index cf7089760..da0becfcf 100644 --- a/contracts/examples/hello-world/programs/hello-world/Cargo.toml +++ b/contracts/examples/hello-world/programs/hello-world/Cargo.toml @@ -21,4 +21,4 @@ default = ["localnet"] [dependencies] anchor-lang = "0.20.1" -chainlink-solana = { version = "0.1.0", package = "store", path = "../../../../programs/store", default-features = false, features = ["cpi"] } +chainlink-solana = "0.1.0" diff --git a/contracts/examples/hello-world/programs/hello-world/src/lib.rs b/contracts/examples/hello-world/programs/hello-world/src/lib.rs index 81446c78f..7464ac9d7 100644 --- a/contracts/examples/hello-world/programs/hello-world/src/lib.rs +++ b/contracts/examples/hello-world/programs/hello-world/src/lib.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use chainlink_solana::accessors as chainlink; +use chainlink_solana as chainlink; declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); diff --git a/contracts/examples/hello-world/tests/hello-world.ts b/contracts/examples/hello-world/tests/hello-world.ts index f2de6b2f0..8bea0ddca 100644 --- a/contracts/examples/hello-world/tests/hello-world.ts +++ b/contracts/examples/hello-world/tests/hello-world.ts @@ -111,6 +111,6 @@ describe('hello-world', () => { }); console.log("Your transaction signature", tx); let t = await provider.connection.getConfirmedTransaction(tx, "confirmed"); - console.log(t.logMessages) + console.log(t.meta.logMessages) }); }); diff --git a/contracts/programs/store/src/lib.rs b/contracts/programs/store/src/lib.rs index 9539f8da3..ce91efea6 100644 --- a/contracts/programs/store/src/lib.rs +++ b/contracts/programs/store/src/lib.rs @@ -39,7 +39,7 @@ pub enum Scope { // Owner } -#[account] +#[derive(AnchorSerialize, AnchorDeserialize)] pub struct Round { pub round_id: u32, pub timestamp: u64,