From 957f735030f0f53fcf6b8bc581210d69c2fe9473 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 20:34:59 -0400 Subject: [PATCH 01/69] Add example contract for solana --- Cargo.toml | 1 + examples/sol-contract/Cargo.toml | 20 +++++++++ examples/sol-contract/build/README.md | 1 + examples/sol-contract/scripts/build.sh | 1 + examples/sol-contract/scripts/deploy.sh | 1 + examples/sol-contract/scripts/invoke.ts | 52 ++++++++++++++++++++++++ examples/sol-contract/src/entrypoint.rs | 17 ++++++++ examples/sol-contract/src/instruction.rs | 24 +++++++++++ examples/sol-contract/src/lib.rs | 8 ++++ examples/sol-contract/src/processor.rs | 48 ++++++++++++++++++++++ 10 files changed, 173 insertions(+) create mode 100644 examples/sol-contract/Cargo.toml create mode 100644 examples/sol-contract/build/README.md create mode 100755 examples/sol-contract/scripts/build.sh create mode 100755 examples/sol-contract/scripts/deploy.sh create mode 100644 examples/sol-contract/scripts/invoke.ts create mode 100644 examples/sol-contract/src/entrypoint.rs create mode 100644 examples/sol-contract/src/instruction.rs create mode 100644 examples/sol-contract/src/lib.rs create mode 100644 examples/sol-contract/src/processor.rs diff --git a/Cargo.toml b/Cargo.toml index 9ea6f83..aa2dca3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "pyth-sdk-solana/test-contract", "pyth-sdk-cw", "examples/cw-contract", + "examples/sol-contract" ] diff --git a/examples/sol-contract/Cargo.toml b/examples/sol-contract/Cargo.toml new file mode 100644 index 0000000..4e512ab --- /dev/null +++ b/examples/sol-contract/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "example-sol-contract" +version = "0.1.0" +authors = ["Pyth Data Foundation"] +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +borsh = "0.9" +bytemuck = "1.7.2" +borsh-derive = "0.9.0" +pyth-sdk-solana = "0.5.0" +solana-program = "1.8.1, < 1.11" + +[dev-dependencies] +solana-sdk = "1.8.1, < 1.11" +solana-client = "1.8.1, < 1.11" +solana-program-test = "1.8.1, < 1.11" \ No newline at end of file diff --git a/examples/sol-contract/build/README.md b/examples/sol-contract/build/README.md new file mode 100644 index 0000000..5a4d9af --- /dev/null +++ b/examples/sol-contract/build/README.md @@ -0,0 +1 @@ +This directory holds the output of build-bpf. See scripts/build.sh. diff --git a/examples/sol-contract/scripts/build.sh b/examples/sol-contract/scripts/build.sh new file mode 100755 index 0000000..f39080b --- /dev/null +++ b/examples/sol-contract/scripts/build.sh @@ -0,0 +1 @@ +cargo build-bpf --sbf-out-dir ./build diff --git a/examples/sol-contract/scripts/deploy.sh b/examples/sol-contract/scripts/deploy.sh new file mode 100755 index 0000000..adc1169 --- /dev/null +++ b/examples/sol-contract/scripts/deploy.sh @@ -0,0 +1 @@ +solana program deploy --program-id build/example_sol_contract-keypair.json build/example_sol_contract.so diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts new file mode 100644 index 0000000..7bf1ed1 --- /dev/null +++ b/examples/sol-contract/scripts/invoke.ts @@ -0,0 +1,52 @@ +const {struct, u8} = require("@solana/buffer-layout"); +const web3 = require("@solana/web3.js"); + +export const invoke = async (loan: string, collateral: string) => { + /* Airdrop */ + console.log("Airdroping..."); + let keypair = web3.Keypair.generate(); + let payer = web3.Keypair.generate(); + let conn = new web3.Connection(web3.clusterApiUrl('devnet')); + let airdropSig = await conn.requestAirdrop( + payer.publicKey, + web3.LAMPORTS_PER_SOL, + ); + await conn.confirmTransaction(airdropSig); + + /* Prepare accounts */ + const loanKey = new web3.PublicKey(loan); + const collateralKey = new web3.PublicKey(collateral); + let keys = [{pubkey: loanKey, isSigner: false, isWritable: false}, + {pubkey: collateralKey, isSigner: false, isWritable: false}, + {pubkey: keypair.publicKey, isSigner: true, isWritable: false}]; + + /* Prepare parameters */ + let allocateStruct = { + index: 0, // Loan2Value is instruction#0 in the program + layout: struct([ + u8('instruction'), + ]) + }; + let data = Buffer.alloc(allocateStruct.layout.span); + let layoutFields = Object.assign({instruction: allocateStruct.index}); + allocateStruct.layout.encode(layoutFields, data); + + /* Invoke transaction */ + console.log("Invoking transaction..."); + let tx = new web3.Transaction({ + feePayer: payer.publicKey + }); + tx.add(new web3.TransactionInstruction({ + keys, + programId: "F6SbH5bQZ8peCqQUDmpYqhUtDJVMcAe9JGLPMLGAesfy", + data + })); + + let txSig = await web3.sendAndConfirmTransaction(conn, tx, [payer, keypair]); + console.log("Confirmed TxHash: " + txSig); +} + +let eth = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; +let usdc = "5SSkXsEKQepHHAewytPVwdej4epN1nxgLVM84L4KXgy7"; +let usdt = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; +invoke(eth, usdt); diff --git a/examples/sol-contract/src/entrypoint.rs b/examples/sol-contract/src/entrypoint.rs new file mode 100644 index 0000000..e6fd7ba --- /dev/null +++ b/examples/sol-contract/src/entrypoint.rs @@ -0,0 +1,17 @@ +//! Program entrypoint + +use solana_program::entrypoint; +use solana_program::pubkey::Pubkey; +use solana_program::account_info::AccountInfo; +use solana_program::entrypoint::ProgramResult; + +entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + crate::processor::process_instruction( + program_id, accounts, instruction_data + ) +} diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs new file mode 100644 index 0000000..acb42f8 --- /dev/null +++ b/examples/sol-contract/src/instruction.rs @@ -0,0 +1,24 @@ +//! Program instructions + +use crate::id; +use borsh::{ + BorshDeserialize, + BorshSerialize, +}; +use solana_program::instruction::Instruction; +use solana_program::instruction::AccountMeta; + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] +pub enum PythClientInstruction { + Loan2Value {}, +} + +pub fn loan_to_value(loan: AccountMeta, collateral: AccountMeta) -> Instruction { + Instruction { + program_id: id(), + accounts: vec![loan, collateral], + data: PythClientInstruction::Loan2Value {} + .try_to_vec() + .unwrap(), + } +} diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs new file mode 100644 index 0000000..701f834 --- /dev/null +++ b/examples/sol-contract/src/lib.rs @@ -0,0 +1,8 @@ +pub mod processor; +pub mod entrypoint; +pub mod instruction; + +// This will only be used in local testing. +solana_program::declare_id!( + "PythBestPractice111111111111111111111111111" +); diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs new file mode 100644 index 0000000..22baa7e --- /dev/null +++ b/examples/sol-contract/src/processor.rs @@ -0,0 +1,48 @@ +//! Program instruction processor + +use solana_program::msg; +use solana_program::pubkey::Pubkey; +use solana_program::account_info::AccountInfo; +use solana_program::entrypoint::ProgramResult; +use solana_program::program_error::ProgramError; + +use borsh::BorshDeserialize; +use crate::instruction::PythClientInstruction; +use pyth_sdk_solana::load_price_feed_from_account_info; + +pub fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + input: &[u8], +) -> ProgramResult { + let instruction = PythClientInstruction::try_from_slice(input).unwrap(); + match instruction { + PythClientInstruction::Loan2Value {} => { + // Suppose we have 1 loan token and 3000 collateral token + let loan_cnt = 1; + let collateral_cnt = 3000; + + let loan = &_accounts[0]; + msg!("The loan key is {}.", loan.key); + let feed1 = load_price_feed_from_account_info(&loan).unwrap(); + let result1 = feed1.get_current_price().unwrap(); + let loan_value = result1.price * loan_cnt; + + let collateral = &_accounts[1]; + msg!("The collateral key is {}.", collateral.key); + let feed2 = load_price_feed_from_account_info(&collateral).unwrap(); + let result2 = feed2.get_current_price().unwrap(); + let collateral_value = result2.price * collateral_cnt; + + if collateral_value > loan_value { + msg!("Loan unit price is {}.", result1.price); + msg!("Collateral unit price is {}.", result2.price); + msg!("Collateral value is higher."); + Ok(()) + } else { + msg!("Loan value is higher!"); + Err(ProgramError::Custom(0)) + } + } + } +} From 8aed7a02a57dc6bc5d9bec44e18dffa44b793941 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 20:52:22 -0400 Subject: [PATCH 02/69] Add scripts for invocation in examples/sol-contract --- examples/sol-contract/.gitignore | 6 ++++++ examples/sol-contract/scripts/invoke.sh | 1 + examples/sol-contract/scripts/invoke.ts | 17 ++++++++--------- examples/sol-contract/scripts/package.json | 18 ++++++++++++++++++ examples/sol-contract/scripts/tsconfig.json | 10 ++++++++++ examples/sol-contract/src/processor.rs | 15 ++++++++------- 6 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 examples/sol-contract/.gitignore create mode 100755 examples/sol-contract/scripts/invoke.sh create mode 100644 examples/sol-contract/scripts/package.json create mode 100644 examples/sol-contract/scripts/tsconfig.json diff --git a/examples/sol-contract/.gitignore b/examples/sol-contract/.gitignore new file mode 100644 index 0000000..9a3314f --- /dev/null +++ b/examples/sol-contract/.gitignore @@ -0,0 +1,6 @@ +build/example_sol_contract.so +build/example_sol_contract-keypair.json +scripts/invoke.js +scripts/invoke.js.map +scripts/node_modules/ +scripts/package-lock.json \ No newline at end of file diff --git a/examples/sol-contract/scripts/invoke.sh b/examples/sol-contract/scripts/invoke.sh new file mode 100755 index 0000000..ef35dba --- /dev/null +++ b/examples/sol-contract/scripts/invoke.sh @@ -0,0 +1 @@ +cd scripts; npm install typescript; npm run build; node invoke.js diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 7bf1ed1..034c243 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,11 +1,11 @@ -const {struct, u8} = require("@solana/buffer-layout"); const web3 = require("@solana/web3.js"); +const {struct, u8} = require("@solana/buffer-layout"); export const invoke = async (loan: string, collateral: string) => { /* Airdrop */ console.log("Airdroping..."); - let keypair = web3.Keypair.generate(); let payer = web3.Keypair.generate(); + let keypair = web3.Keypair.generate(); let conn = new web3.Connection(web3.clusterApiUrl('devnet')); let airdropSig = await conn.requestAirdrop( payer.publicKey, @@ -13,7 +13,7 @@ export const invoke = async (loan: string, collateral: string) => { ); await conn.confirmTransaction(airdropSig); - /* Prepare accounts */ + /* Specify accounts being touched */ const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); let keys = [{pubkey: loanKey, isSigner: false, isWritable: false}, @@ -22,7 +22,7 @@ export const invoke = async (loan: string, collateral: string) => { /* Prepare parameters */ let allocateStruct = { - index: 0, // Loan2Value is instruction#0 in the program + index: 0, // Loan2Value is instruction #0 in the program layout: struct([ u8('instruction'), ]) @@ -38,7 +38,7 @@ export const invoke = async (loan: string, collateral: string) => { }); tx.add(new web3.TransactionInstruction({ keys, - programId: "F6SbH5bQZ8peCqQUDmpYqhUtDJVMcAe9JGLPMLGAesfy", + programId: "CD9PFy8satZh27JeEixunWjBqoYbpbLbUtZ6eWPbT1s6", data })); @@ -46,7 +46,6 @@ export const invoke = async (loan: string, collateral: string) => { console.log("Confirmed TxHash: " + txSig); } -let eth = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; -let usdc = "5SSkXsEKQepHHAewytPVwdej4epN1nxgLVM84L4KXgy7"; -let usdt = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; -invoke(eth, usdt); +let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; +let usdtToUSD = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; +invoke(ethToUSD, usdtToUSD); diff --git a/examples/sol-contract/scripts/package.json b/examples/sol-contract/scripts/package.json new file mode 100644 index 0000000..ad4ad3a --- /dev/null +++ b/examples/sol-contract/scripts/package.json @@ -0,0 +1,18 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "", + "main": "invoke.js", + "scripts": { + "build": "npx tsc" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "typescript": "^4.8.3" + }, + "dependencies": { + "@solana/web3.js": "^1.56.2" + } +} diff --git a/examples/sol-contract/scripts/tsconfig.json b/examples/sol-contract/scripts/tsconfig.json new file mode 100644 index 0000000..f33e9f4 --- /dev/null +++ b/examples/sol-contract/scripts/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true, + "target": "es6", + "moduleResolution": "node", + "sourceMap": true + }, + "lib": ["es2015"] +} diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 22baa7e..c5ee918 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -18,18 +18,19 @@ pub fn process_instruction( let instruction = PythClientInstruction::try_from_slice(input).unwrap(); match instruction { PythClientInstruction::Loan2Value {} => { - // Suppose we have 1 loan token and 3000 collateral token + let loan = &_accounts[0]; + let collateral = &_accounts[1]; + msg!("The loan key is {}.", loan.key); + msg!("The collateral key is {}.", collateral.key); + + msg!("Assume 1 unit of loan and 3000 unit of collateral."); let loan_cnt = 1; let collateral_cnt = 3000; - let loan = &_accounts[0]; - msg!("The loan key is {}.", loan.key); let feed1 = load_price_feed_from_account_info(&loan).unwrap(); let result1 = feed1.get_current_price().unwrap(); let loan_value = result1.price * loan_cnt; - let collateral = &_accounts[1]; - msg!("The collateral key is {}.", collateral.key); let feed2 = load_price_feed_from_account_info(&collateral).unwrap(); let result2 = feed2.get_current_price().unwrap(); let collateral_value = result2.price * collateral_cnt; @@ -37,10 +38,10 @@ pub fn process_instruction( if collateral_value > loan_value { msg!("Loan unit price is {}.", result1.price); msg!("Collateral unit price is {}.", result2.price); - msg!("Collateral value is higher."); + msg!("The value of collateral is higher."); Ok(()) } else { - msg!("Loan value is higher!"); + msg!("The value of loan is higher!"); Err(ProgramError::Custom(0)) } } From 0677a3ef9d1902cd3b74ada7cf11aac902ef72db Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 21:02:06 -0400 Subject: [PATCH 03/69] Add readme in examples/sol-contract --- examples/sol-contract/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 examples/sol-contract/README.md diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md new file mode 100644 index 0000000..c507c59 --- /dev/null +++ b/examples/sol-contract/README.md @@ -0,0 +1,18 @@ +# Pyth SDK Example Contract for Solana + +This repository contains a simple example demonstrating how to read the Pyth price from the Pyth contract on Solana. + +The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. +The loan-to-value ratio is an important metric tracked by many lending protocols. +An example invocation of this contract on the Solana devnet can be find in `scripts/invoke.ts`. + +## Usage + +``` +# To build the example contract +> scripts/build.sh +# To deploy the example contract +> scripts/deploy.sh +# To invoke the example contract +> scripts/invoke.ts +``` From 1b9a91e17a3900c6e8a69099918d02807690b649 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 21:26:34 -0400 Subject: [PATCH 04/69] Add overflow checks in examples/sol-contract --- examples/sol-contract/src/processor.rs | 31 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index c5ee918..cae2f91 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -18,6 +18,7 @@ pub fn process_instruction( let instruction = PythClientInstruction::try_from_slice(input).unwrap(); match instruction { PythClientInstruction::Loan2Value {} => { + // Parse the parameters and skip the necessary checks let loan = &_accounts[0]; let collateral = &_accounts[1]; msg!("The loan key is {}.", loan.key); @@ -26,23 +27,35 @@ pub fn process_instruction( msg!("Assume 1 unit of loan and 3000 unit of collateral."); let loan_cnt = 1; let collateral_cnt = 3000; - - let feed1 = load_price_feed_from_account_info(&loan).unwrap(); - let result1 = feed1.get_current_price().unwrap(); - let loan_value = result1.price * loan_cnt; - let feed2 = load_price_feed_from_account_info(&collateral).unwrap(); - let result2 = feed2.get_current_price().unwrap(); - let collateral_value = result2.price * collateral_cnt; + // Calculate the value of the loan + let loan_value; + let feed1 = load_price_feed_from_account_info(&loan); + let result1 = feed1.unwrap().get_current_price().unwrap(); + if let Some(v) = result1.price.checked_mul(loan_cnt) { + loan_value = v; + } else { + return Err(ProgramError::Custom(0)) + } + + // Calculate the value of the collateral + let collateral_value; + let feed2 = load_price_feed_from_account_info(&collateral); + let result2 = feed2.unwrap().get_current_price().unwrap(); + if let Some(v) = result2.price.checked_mul(collateral_cnt) { + collateral_value = v; + } else { + return Err(ProgramError::Custom(0)) + } if collateral_value > loan_value { msg!("Loan unit price is {}.", result1.price); msg!("Collateral unit price is {}.", result2.price); msg!("The value of collateral is higher."); - Ok(()) + return Ok(()) } else { msg!("The value of loan is higher!"); - Err(ProgramError::Custom(0)) + return Err(ProgramError::Custom(1)) } } } From 685bffd74a397a9f46da5499266c98ada3d9c4bb Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 21:41:43 -0400 Subject: [PATCH 05/69] Cleanup and add comments in examples/sol-contract --- examples/sol-contract/src/entrypoint.rs | 3 +++ examples/sol-contract/src/instruction.rs | 23 +++++------------------ examples/sol-contract/src/lib.rs | 7 ++----- examples/sol-contract/src/processor.rs | 13 +++++++++---- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/examples/sol-contract/src/entrypoint.rs b/examples/sol-contract/src/entrypoint.rs index e6fd7ba..6b151a3 100644 --- a/examples/sol-contract/src/entrypoint.rs +++ b/examples/sol-contract/src/entrypoint.rs @@ -5,6 +5,9 @@ use solana_program::pubkey::Pubkey; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; +// Every solana program has an entry point function with 3 parameters: +// the program ID, the accounts being touched by this program, +// and an arbitrary byte array as the input data for execution. entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index acb42f8..092bdcc 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -1,24 +1,11 @@ //! Program instructions -use crate::id; -use borsh::{ - BorshDeserialize, - BorshSerialize, -}; -use solana_program::instruction::Instruction; -use solana_program::instruction::AccountMeta; +use borsh::BorshSerialize; +use borsh::BorshDeserialize; +// A solana program contains a number of instructions. +// And this example contract contains only one instruction. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum PythClientInstruction { - Loan2Value {}, -} - -pub fn loan_to_value(loan: AccountMeta, collateral: AccountMeta) -> Instruction { - Instruction { - program_id: id(), - accounts: vec![loan, collateral], - data: PythClientInstruction::Loan2Value {} - .try_to_vec() - .unwrap(), - } + Loan2Value {}, // In this enum, Loan2Value is number 0. } diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs index 701f834..3ddeda9 100644 --- a/examples/sol-contract/src/lib.rs +++ b/examples/sol-contract/src/lib.rs @@ -1,8 +1,5 @@ +// This is the file being compiled to the bpf shared object (.so). +// It specifies the 3 modules of this example contract. pub mod processor; pub mod entrypoint; pub mod instruction; - -// This will only be used in local testing. -solana_program::declare_id!( - "PythBestPractice111111111111111111111111111" -); diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index cae2f91..b2fde31 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -15,39 +15,44 @@ pub fn process_instruction( _accounts: &[AccountInfo], input: &[u8], ) -> ProgramResult { - let instruction = PythClientInstruction::try_from_slice(input).unwrap(); - match instruction { + // Checking the validity of parameters is important. + // This checking step is skipped in this example contract. + let instruction = PythClientInstruction::try_from_slice(input); + match instruction.unwrap() { PythClientInstruction::Loan2Value {} => { - // Parse the parameters and skip the necessary checks let loan = &_accounts[0]; let collateral = &_accounts[1]; msg!("The loan key is {}.", loan.key); msg!("The collateral key is {}.", collateral.key); - msg!("Assume 1 unit of loan and 3000 unit of collateral."); let loan_cnt = 1; let collateral_cnt = 3000; // Calculate the value of the loan + // Pyth is called to get the unit price of the loan. let loan_value; let feed1 = load_price_feed_from_account_info(&loan); let result1 = feed1.unwrap().get_current_price().unwrap(); if let Some(v) = result1.price.checked_mul(loan_cnt) { loan_value = v; } else { + // An overflow occurs for result1.price * loan_cnt. return Err(ProgramError::Custom(0)) } // Calculate the value of the collateral + // Pyth is called to get the unit price of the collateral. let collateral_value; let feed2 = load_price_feed_from_account_info(&collateral); let result2 = feed2.unwrap().get_current_price().unwrap(); if let Some(v) = result2.price.checked_mul(collateral_cnt) { collateral_value = v; } else { + // An overflow occurs for result2.price * collateral_cnt. return Err(ProgramError::Custom(0)) } + // Check whether the value of the collateral is higher. if collateral_value > loan_value { msg!("Loan unit price is {}.", result1.price); msg!("Collateral unit price is {}.", result2.price); From b045b706e5de1ebbf4566ed33a6071c29e585cd2 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 21:52:34 -0400 Subject: [PATCH 06/69] Remove hard-coded contract hash in examples/sol-contract --- examples/sol-contract/scripts/invoke.sh | 2 +- examples/sol-contract/scripts/invoke.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.sh b/examples/sol-contract/scripts/invoke.sh index ef35dba..7c017ee 100755 --- a/examples/sol-contract/scripts/invoke.sh +++ b/examples/sol-contract/scripts/invoke.sh @@ -1 +1 @@ -cd scripts; npm install typescript; npm run build; node invoke.js +cd scripts; npm install typescript; npm run build; node invoke.js `solana-keygen pubkey ../build/example_sol_contract-keypair.json` diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 034c243..4113da2 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -32,18 +32,19 @@ export const invoke = async (loan: string, collateral: string) => { allocateStruct.layout.encode(layoutFields, data); /* Invoke transaction */ - console.log("Invoking transaction..."); let tx = new web3.Transaction({ feePayer: payer.publicKey }); + let contract = process.argv[2]; + console.log("Invoking contract " + contract + "..."); tx.add(new web3.TransactionInstruction({ keys, - programId: "CD9PFy8satZh27JeEixunWjBqoYbpbLbUtZ6eWPbT1s6", + programId: contract, data })); let txSig = await web3.sendAndConfirmTransaction(conn, tx, [payer, keypair]); - console.log("Confirmed TxHash: " + txSig); + console.log("Confirmed TxHash " + txSig); } let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; From 7432028afb4326e9257d5e5c4e73c40e9677c99c Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 22:03:34 -0400 Subject: [PATCH 07/69] Improve comments wording in examples/sol-contract --- examples/sol-contract/README.md | 4 +++- examples/sol-contract/src/processor.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index c507c59..b448917 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -3,9 +3,11 @@ This repository contains a simple example demonstrating how to read the Pyth price from the Pyth contract on Solana. The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. -The loan-to-value ratio is an important metric tracked by many lending protocols. +This function compares the value of some loan and some collateral, which is important in many lending protocols. An example invocation of this contract on the Solana devnet can be find in `scripts/invoke.ts`. +We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and `node`. + ## Usage ``` diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index b2fde31..859e963 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -16,7 +16,7 @@ pub fn process_instruction( input: &[u8], ) -> ProgramResult { // Checking the validity of parameters is important. - // This checking step is skipped in this example contract. + // This important step is skipped in this example contract. let instruction = PythClientInstruction::try_from_slice(input); match instruction.unwrap() { PythClientInstruction::Loan2Value {} => { @@ -28,7 +28,7 @@ pub fn process_instruction( let loan_cnt = 1; let collateral_cnt = 3000; - // Calculate the value of the loan + // Calculate the value of the loan. // Pyth is called to get the unit price of the loan. let loan_value; let feed1 = load_price_feed_from_account_info(&loan); @@ -40,7 +40,7 @@ pub fn process_instruction( return Err(ProgramError::Custom(0)) } - // Calculate the value of the collateral + // Calculate the value of the collateral. // Pyth is called to get the unit price of the collateral. let collateral_value; let feed2 = load_price_feed_from_account_info(&collateral); From d5f6d756e15464cbed6b7bb88231b62fe2fcd488 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 22:08:37 -0400 Subject: [PATCH 08/69] Update readme of examples/sol-contract --- examples/sol-contract/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index b448917..131cafc 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -10,7 +10,8 @@ We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and ## Usage -``` +```shell +> cd examples/sol-contract # To build the example contract > scripts/build.sh # To deploy the example contract From eba9da516e0cb447fbad5fc857bbd97a9a4308ef Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 22:21:44 -0400 Subject: [PATCH 09/69] Remove some unnecessary dependencies in examples/sol-contract --- examples/sol-contract/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/sol-contract/Cargo.toml b/examples/sol-contract/Cargo.toml index 4e512ab..63e2d02 100644 --- a/examples/sol-contract/Cargo.toml +++ b/examples/sol-contract/Cargo.toml @@ -9,8 +9,6 @@ crate-type = ["cdylib", "lib"] [dependencies] borsh = "0.9" -bytemuck = "1.7.2" -borsh-derive = "0.9.0" pyth-sdk-solana = "0.5.0" solana-program = "1.8.1, < 1.11" From aac5b9d04630be1c1f229aeb64677ee69352d908 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 14 Sep 2022 10:47:42 -0400 Subject: [PATCH 10/69] Remove the unwrap() in the code of examples/sol-contract --- examples/sol-contract/src/processor.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 859e963..218f56d 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -17,8 +17,8 @@ pub fn process_instruction( ) -> ProgramResult { // Checking the validity of parameters is important. // This important step is skipped in this example contract. - let instruction = PythClientInstruction::try_from_slice(input); - match instruction.unwrap() { + let instruction = PythClientInstruction::try_from_slice(input)?; + match instruction { PythClientInstruction::Loan2Value {} => { let loan = &_accounts[0]; let collateral = &_accounts[1]; @@ -31,25 +31,25 @@ pub fn process_instruction( // Calculate the value of the loan. // Pyth is called to get the unit price of the loan. let loan_value; - let feed1 = load_price_feed_from_account_info(&loan); - let result1 = feed1.unwrap().get_current_price().unwrap(); + let feed1 = load_price_feed_from_account_info(loan)?; + let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(0))?; if let Some(v) = result1.price.checked_mul(loan_cnt) { loan_value = v; } else { // An overflow occurs for result1.price * loan_cnt. - return Err(ProgramError::Custom(0)) + return Err(ProgramError::Custom(1)) } // Calculate the value of the collateral. // Pyth is called to get the unit price of the collateral. let collateral_value; - let feed2 = load_price_feed_from_account_info(&collateral); - let result2 = feed2.unwrap().get_current_price().unwrap(); + let feed2 = load_price_feed_from_account_info(collateral)?; + let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(0))?; if let Some(v) = result2.price.checked_mul(collateral_cnt) { collateral_value = v; } else { // An overflow occurs for result2.price * collateral_cnt. - return Err(ProgramError::Custom(0)) + return Err(ProgramError::Custom(1)) } // Check whether the value of the collateral is higher. @@ -60,7 +60,7 @@ pub fn process_instruction( return Ok(()) } else { msg!("The value of loan is higher!"); - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(2)) } } } From d0b28d98729511176629e4679b1b14a87730a56c Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 14 Sep 2022 11:11:15 -0400 Subject: [PATCH 11/69] Fix a grammar error --- examples/sol-contract/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index 131cafc..047652b 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -4,7 +4,7 @@ This repository contains a simple example demonstrating how to read the Pyth pri The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. This function compares the value of some loan and some collateral, which is important in many lending protocols. -An example invocation of this contract on the Solana devnet can be find in `scripts/invoke.ts`. +An example invocation of this contract on the Solana devnet can be found in `scripts/invoke.ts`. We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and `node`. From 8ff6ee19daadec169a59c80d30688dbc93e6e8ab Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 22:32:29 -0400 Subject: [PATCH 12/69] Succeed in adding state.rs and an initialization instruction --- examples/sol-contract/Cargo.toml | 1 + examples/sol-contract/scripts/invoke.ts | 94 ++++++++++++++-------- examples/sol-contract/scripts/package.json | 2 +- examples/sol-contract/src/instruction.rs | 3 +- examples/sol-contract/src/lib.rs | 1 + examples/sol-contract/src/processor.rs | 89 +++++++++++++------- examples/sol-contract/src/state.rs | 69 ++++++++++++++++ 7 files changed, 192 insertions(+), 67 deletions(-) create mode 100644 examples/sol-contract/src/state.rs diff --git a/examples/sol-contract/Cargo.toml b/examples/sol-contract/Cargo.toml index 63e2d02..41e6b4e 100644 --- a/examples/sol-contract/Cargo.toml +++ b/examples/sol-contract/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib", "lib"] [dependencies] borsh = "0.9" +arrayref = "0.3.6" pyth-sdk-solana = "0.5.0" solana-program = "1.8.1, < 1.11" diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 4113da2..032d6c4 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,50 +1,74 @@ const web3 = require("@solana/web3.js"); -const {struct, u8} = require("@solana/buffer-layout"); +const {struct, b, u8, u32} = require("@solana/buffer-layout"); + +const admin = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) export const invoke = async (loan: string, collateral: string) => { - /* Airdrop */ - console.log("Airdroping..."); - let payer = web3.Keypair.generate(); - let keypair = web3.Keypair.generate(); + let contract = admin.publicKey; let conn = new web3.Connection(web3.clusterApiUrl('devnet')); + + /* Prepare the payer account */ + console.log("Airdropping..."); + let payer = web3.Keypair.generate(); let airdropSig = await conn.requestAirdrop( - payer.publicKey, - web3.LAMPORTS_PER_SOL, + payer.publicKey, web3.LAMPORTS_PER_SOL ); await conn.confirmTransaction(airdropSig); - /* Specify accounts being touched */ + /* createInst is an instruction creating an account storing the + * LoanInfo data, which will be passed to Init for initialization*/ + let sizeofLoanInfo = 1 + 32 + 8 + 32 + 8; + let dataAccount = web3.Keypair.generate(); + let cost = await conn.getMinimumBalanceForRentExemption(sizeofLoanInfo); + const createInst = web3.SystemProgram.createAccount({ + programId: contract, + fromPubkey: payer.publicKey, + space: sizeofLoanInfo, + newAccountPubkey: dataAccount.publicKey, + lamports: cost, + }); + + /* Specify accounts being touched by Init */ const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); - let keys = [{pubkey: loanKey, isSigner: false, isWritable: false}, + let keys = [{pubkey: contract, isSigner: true, isWritable: false}, + {pubkey: dataAccount.publicKey, isSigner: false, isWritable: false}, + {pubkey: loanKey, isSigner: false, isWritable: false}, {pubkey: collateralKey, isSigner: false, isWritable: false}, - {pubkey: keypair.publicKey, isSigner: true, isWritable: false}]; - - /* Prepare parameters */ - let allocateStruct = { - index: 0, // Loan2Value is instruction #0 in the program - layout: struct([ - u8('instruction'), - ]) - }; - let data = Buffer.alloc(allocateStruct.layout.span); - let layoutFields = Object.assign({instruction: allocateStruct.index}); - allocateStruct.layout.encode(layoutFields, data); - - /* Invoke transaction */ - let tx = new web3.Transaction({ - feePayer: payer.publicKey - }); - let contract = process.argv[2]; - console.log("Invoking contract " + contract + "..."); - tx.add(new web3.TransactionInstruction({ - keys, - programId: contract, - data - })); + ]; + + /* Prepare parameters for Invoking the contract */ + let dataLayout = struct([ u8('instruction') ]) + let data = Buffer.alloc(dataLayout.span); + dataLayout.encode(Object.assign({instruction: 1}), data); - let txSig = await web3.sendAndConfirmTransaction(conn, tx, [payer, keypair]); - console.log("Confirmed TxHash " + txSig); + /* Invoke the Init instruction */ + console.log("Creating data account and invoking Init..."); + let txInit = new web3.Transaction({ feePayer: payer.publicKey }); + txInit.add( + createInst, + new web3.TransactionInstruction({ + keys, + programId: contract, + data + }) + ); + let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, admin]); + console.log("TxHash: " + txInitSig); + + /* Invoke the Loan2Value instruction */ + dataLayout.encode(Object.assign({instruction: 0}), data); + console.log("Checking loan to value ratio..."); + let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); + txCheck.add( + new web3.TransactionInstruction({ + keys, + programId: contract, + data + }) + ); + let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, admin]); + console.log("TxHash: " + txCheckSig); } let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; diff --git a/examples/sol-contract/scripts/package.json b/examples/sol-contract/scripts/package.json index ad4ad3a..17a4921 100644 --- a/examples/sol-contract/scripts/package.json +++ b/examples/sol-contract/scripts/package.json @@ -10,7 +10,7 @@ "author": "", "license": "ISC", "devDependencies": { - "typescript": "^4.8.3" + "typescript": "^4.8.4" }, "dependencies": { "@solana/web3.js": "^1.56.2" diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index 092bdcc..12e19a5 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -7,5 +7,6 @@ use borsh::BorshDeserialize; // And this example contract contains only one instruction. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum PythClientInstruction { - Loan2Value {}, // In this enum, Loan2Value is number 0. + Loan2Value {}, // in this enum, Loan2Value is number 0 + Init{}, // and Init is 1 } diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs index 3ddeda9..0939b10 100644 --- a/examples/sol-contract/src/lib.rs +++ b/examples/sol-contract/src/lib.rs @@ -1,5 +1,6 @@ // This is the file being compiled to the bpf shared object (.so). // It specifies the 3 modules of this example contract. +pub mod state; pub mod processor; pub mod entrypoint; pub mod instruction; diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 218f56d..4208b6f 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -1,57 +1,86 @@ //! Program instruction processor + use solana_program::msg; use solana_program::pubkey::Pubkey; -use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; use solana_program::program_error::ProgramError; +use solana_program::program_pack::{IsInitialized, Pack}; +use solana_program::account_info::{next_account_info, AccountInfo}; use borsh::BorshDeserialize; -use crate::instruction::PythClientInstruction; use pyth_sdk_solana::load_price_feed_from_account_info; +use crate::state::LoanInfo; +use crate::instruction::PythClientInstruction; + pub fn process_instruction( _program_id: &Pubkey, _accounts: &[AccountInfo], input: &[u8], ) -> ProgramResult { - // Checking the validity of parameters is important. - // This important step is skipped in this example contract. + let account_iter = &mut _accounts.iter(); + let signer = next_account_info(account_iter)?; + let data_account = next_account_info(account_iter)?; + let pyth_loan_account = next_account_info(account_iter)?; + let pyth_collateral_account = next_account_info(account_iter)?; + let instruction = PythClientInstruction::try_from_slice(input)?; match instruction { - PythClientInstruction::Loan2Value {} => { - let loan = &_accounts[0]; - let collateral = &_accounts[1]; - msg!("The loan key is {}.", loan.key); - msg!("The collateral key is {}.", collateral.key); - msg!("Assume 1 unit of loan and 3000 unit of collateral."); - let loan_cnt = 1; - let collateral_cnt = 3000; + PythClientInstruction::Init {} => { + // Only the program admin can initialize a loan. + if !(signer.key == _program_id && signer.is_signer) { + return Err(ProgramError::Custom(1)) + } - // Calculate the value of the loan. - // Pyth is called to get the unit price of the loan. - let loan_value; - let feed1 = load_price_feed_from_account_info(loan)?; - let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(0))?; - if let Some(v) = result1.price.checked_mul(loan_cnt) { - loan_value = v; - } else { - // An overflow occurs for result1.price * loan_cnt. + let mut loan_info = LoanInfo::unpack_from_slice( + &data_account.try_borrow_data()?)?; + + if loan_info.is_initialized() { return Err(ProgramError::Custom(1)) } - // Calculate the value of the collateral. - // Pyth is called to get the unit price of the collateral. - let collateral_value; - let feed2 = load_price_feed_from_account_info(collateral)?; - let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(0))?; - if let Some(v) = result2.price.checked_mul(collateral_cnt) { - collateral_value = v; - } else { - // An overflow occurs for result2.price * collateral_cnt. + loan_info.is_initialized = true; + loan_info.loan_key = *pyth_loan_account.key; + loan_info.collateral_key = *pyth_collateral_account.key; + // Give some dummy numbers for simplicity of this example. + loan_info.loan_qty = 1; + loan_info.collateral_qty = 3000; + + msg!("The loan key is {}.", loan_info.loan_key); + msg!("The collateral key is {}.", loan_info.collateral_key); + msg!("Assume 1 unit of loan and 3000 unit of collateral."); + LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; + Ok(()) + }, + PythClientInstruction::Loan2Value {} => { + // Anyone can check the loan to value ratio. + let loan_info = LoanInfo::unpack_from_slice( + &data_account.try_borrow_data()?)?; + + if !loan_info.is_initialized() { return Err(ProgramError::Custom(1)) } + if loan_info.loan_key != *pyth_loan_account.key || + loan_info.collateral_key != *pyth_collateral_account.key { + return Err(ProgramError::Custom(1)) + } + + // Calculate the value of the loan using Pyth price. + let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; + let result1 = feed1.get_current_price() + .ok_or(ProgramError::Custom(0))?; + let loan_value = result1.price.checked_mul(loan_info.loan_qty) + .ok_or(ProgramError::Custom(0))?; + + // Calculate the value of the loan using Pyth price. + let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; + let result2 = feed2.get_current_price() + .ok_or(ProgramError::Custom(0))?; + let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) + .ok_or(ProgramError::Custom(1))?; + // Check whether the value of the collateral is higher. if collateral_value > loan_value { msg!("Loan unit price is {}.", result1.price); diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs new file mode 100644 index 0000000..d92617d --- /dev/null +++ b/examples/sol-contract/src/state.rs @@ -0,0 +1,69 @@ +use solana_program::{ + program_error::ProgramError, + program_pack::{IsInitialized, Pack, Sealed}, + pubkey::Pubkey, +}; + +use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; + +pub struct LoanInfo { + pub is_initialized: bool, + pub loan_key: Pubkey, + pub loan_qty: i64, + pub collateral_key: Pubkey, + pub collateral_qty: i64 +} + +impl Sealed for LoanInfo {} + +impl IsInitialized for LoanInfo { + fn is_initialized(&self) -> bool { + self.is_initialized + } +} + +impl Pack for LoanInfo { + const LEN: usize = 1 + 32 + 8 + 32 + 8; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, LoanInfo::LEN]; + let ( + src_is_initialized, + src_loan_key, src_loan_qty, + src_collateral_key, src_collateral_qty, + ) = array_refs![src, 1, 32, 8, 32, 8]; + let is_initialized = match src_is_initialized { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }; + + Ok(LoanInfo { + is_initialized, + loan_key: Pubkey::new_from_array(*src_loan_key), + loan_qty: i64::from_le_bytes(*src_loan_qty), + collateral_key: Pubkey::new_from_array(*src_collateral_key), + collateral_qty: i64::from_le_bytes(*src_collateral_qty), + }) + } + + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, LoanInfo::LEN]; + let ( + dst_is_initialized, + dst_loan_key, dst_loan_qty, + dst_collateral_key, dst_collateral_qty, + ) = mut_array_refs![dst, 1, 32, 8, 32, 8]; + + let LoanInfo { + is_initialized, + loan_key, loan_qty, + collateral_key, collateral_qty, + } = self; + + dst_is_initialized[0] = *is_initialized as u8; + dst_loan_key.copy_from_slice(loan_key.as_ref()); + *dst_loan_qty = loan_qty.to_le_bytes(); + dst_collateral_key.copy_from_slice(collateral_key.as_ref()); + *dst_collateral_qty = collateral_qty.to_le_bytes(); + } +} From 004b1369fb448f75a3424f170540fb1e4fe4cfc8 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 22:59:40 -0400 Subject: [PATCH 13/69] Cleanup --- examples/sol-contract/scripts/invoke.ts | 77 ++++++++++++++++-------- examples/sol-contract/src/instruction.rs | 4 +- examples/sol-contract/src/processor.rs | 14 ++--- 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 032d6c4..756b679 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -4,71 +4,98 @@ const {struct, b, u8, u32} = require("@solana/buffer-layout"); const admin = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) export const invoke = async (loan: string, collateral: string) => { - let contract = admin.publicKey; let conn = new web3.Connection(web3.clusterApiUrl('devnet')); /* Prepare the payer account */ - console.log("Airdropping..."); + console.info("Airdropping..."); let payer = web3.Keypair.generate(); let airdropSig = await conn.requestAirdrop( payer.publicKey, web3.LAMPORTS_PER_SOL ); await conn.confirmTransaction(airdropSig); - /* createInst is an instruction creating an account storing the + /* Prepare the createInst instruction: Create an account to store the * LoanInfo data, which will be passed to Init for initialization*/ - let sizeofLoanInfo = 1 + 32 + 8 + 32 + 8; + let contract = admin.publicKey; + let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); - let cost = await conn.getMinimumBalanceForRentExemption(sizeofLoanInfo); + let cost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); const createInst = web3.SystemProgram.createAccount({ + lamports: cost, + space: loanInfoSize, programId: contract, fromPubkey: payer.publicKey, - space: sizeofLoanInfo, newAccountPubkey: dataAccount.publicKey, - lamports: cost, }); - /* Specify accounts being touched by Init */ + /* Specify the accounts and parameters for invocations */ + const dataKey = dataAccount.publicKey; const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); - let keys = [{pubkey: contract, isSigner: true, isWritable: false}, - {pubkey: dataAccount.publicKey, isSigner: false, isWritable: false}, - {pubkey: loanKey, isSigner: false, isWritable: false}, - {pubkey: collateralKey, isSigner: false, isWritable: false}, - ]; - - /* Prepare parameters for Invoking the contract */ + let accounts = + [{pubkey: contract, isSigner: true, isWritable: false}, + {pubkey: dataKey, isSigner: false, isWritable: false}, + {pubkey: loanKey, isSigner: false, isWritable: false}, + {pubkey: collateralKey, isSigner: false, isWritable: false}, + ]; let dataLayout = struct([ u8('instruction') ]) let data = Buffer.alloc(dataLayout.span); - dataLayout.encode(Object.assign({instruction: 1}), data); + dataLayout.encode(Object.assign({instruction: 0}), data); - /* Invoke the Init instruction */ + /* Invoke the Init instruction (instruction #0) */ console.log("Creating data account and invoking Init..."); let txInit = new web3.Transaction({ feePayer: payer.publicKey }); txInit.add( createInst, new web3.TransactionInstruction({ - keys, - programId: contract, - data + data: data, + keys: accounts, + programId: contract }) ); let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, admin]); console.log("TxHash: " + txInitSig); - /* Invoke the Loan2Value instruction */ - dataLayout.encode(Object.assign({instruction: 0}), data); + /* Invoke the Loan2Value instruction (instruction #1) */ console.log("Checking loan to value ratio..."); let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); + dataLayout.encode(Object.assign({instruction: 1}), data); txCheck.add( new web3.TransactionInstruction({ - keys, - programId: contract, - data + data: data, + keys: accounts, + programId: contract }) ); let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, admin]); console.log("TxHash: " + txCheckSig); + + /* Try to invoke the Init instruction without authority */ + console.log("Trying an unauthorized invocation of Init..."); + let attacker = web3.Keypair.generate(); + accounts[0].pubkey = attacker.publicKey + + let attackerDataAccount = web3.Keypair.generate(); + const attackerCreateInst = web3.SystemProgram.createAccount({ + lamports: cost, + space: loanInfoSize, + programId: contract, + fromPubkey: payer.publicKey, + newAccountPubkey: attackerDataAccount.publicKey, + }); + + let txAttacker = new web3.Transaction({ feePayer: payer.publicKey }); + dataLayout.encode(Object.assign({instruction: 0}), data); + txAttacker.add( + attackerCreateInst, + new web3.TransactionInstruction({ + data: data, + keys: accounts, + programId: contract + }) + ); + let txAttackerSig = await web3.sendAndConfirmTransaction(conn, txAttacker, [payer, attackerDataAccount, attacker]); + console.log("TxHash: " + txAttackerSig); } let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index 12e19a5..e0602f1 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -7,6 +7,6 @@ use borsh::BorshDeserialize; // And this example contract contains only one instruction. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum PythClientInstruction { - Loan2Value {}, // in this enum, Loan2Value is number 0 - Init{}, // and Init is 1 + Init{}, + Loan2Value {}, } diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 4208b6f..fba7f43 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -30,7 +30,7 @@ pub fn process_instruction( PythClientInstruction::Init {} => { // Only the program admin can initialize a loan. if !(signer.key == _program_id && signer.is_signer) { - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(0)) } let mut loan_info = LoanInfo::unpack_from_slice( @@ -64,22 +64,22 @@ pub fn process_instruction( if loan_info.loan_key != *pyth_loan_account.key || loan_info.collateral_key != *pyth_collateral_account.key { - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(2)) } // Calculate the value of the loan using Pyth price. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price() - .ok_or(ProgramError::Custom(0))?; + .ok_or(ProgramError::Custom(3))?; let loan_value = result1.price.checked_mul(loan_info.loan_qty) - .ok_or(ProgramError::Custom(0))?; + .ok_or(ProgramError::Custom(4))?; // Calculate the value of the loan using Pyth price. let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; let result2 = feed2.get_current_price() - .ok_or(ProgramError::Custom(0))?; + .ok_or(ProgramError::Custom(3))?; let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) - .ok_or(ProgramError::Custom(1))?; + .ok_or(ProgramError::Custom(4))?; // Check whether the value of the collateral is higher. if collateral_value > loan_value { @@ -89,7 +89,7 @@ pub fn process_instruction( return Ok(()) } else { msg!("The value of loan is higher!"); - return Err(ProgramError::Custom(2)) + return Err(ProgramError::Custom(5)) } } } From 39d83005da9e9d0c237db4e6e693ccc5de377644 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:13:07 -0400 Subject: [PATCH 14/69] Cleanup --- examples/sol-contract/scripts/invoke.ts | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 756b679..686ee88 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,13 +1,18 @@ const web3 = require("@solana/web3.js"); const {struct, b, u8, u32} = require("@solana/buffer-layout"); -const admin = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) +const contract = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) export const invoke = async (loan: string, collateral: string) => { + if (contract.publicKey != process.argv[2]) { + console.info("Please update the contract key pair in invoke.ts."); + return; + } + console.info("Invoking contract " + contract.publicKey); let conn = new web3.Connection(web3.clusterApiUrl('devnet')); /* Prepare the payer account */ - console.info("Airdropping..."); + console.info("Airdropping to payer account..."); let payer = web3.Keypair.generate(); let airdropSig = await conn.requestAirdrop( payer.publicKey, web3.LAMPORTS_PER_SOL @@ -16,14 +21,13 @@ export const invoke = async (loan: string, collateral: string) => { /* Prepare the createInst instruction: Create an account to store the * LoanInfo data, which will be passed to Init for initialization*/ - let contract = admin.publicKey; let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); let cost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); const createInst = web3.SystemProgram.createAccount({ lamports: cost, space: loanInfoSize, - programId: contract, + programId: contract.publicKey, fromPubkey: payer.publicKey, newAccountPubkey: dataAccount.publicKey, }); @@ -33,7 +37,7 @@ export const invoke = async (loan: string, collateral: string) => { const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); let accounts = - [{pubkey: contract, isSigner: true, isWritable: false}, + [{pubkey: contract.publicKey, isSigner: true, isWritable: false}, {pubkey: dataKey, isSigner: false, isWritable: false}, {pubkey: loanKey, isSigner: false, isWritable: false}, {pubkey: collateralKey, isSigner: false, isWritable: false}, @@ -50,10 +54,10 @@ export const invoke = async (loan: string, collateral: string) => { new web3.TransactionInstruction({ data: data, keys: accounts, - programId: contract + programId: contract.publicKey }) ); - let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, admin]); + let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, contract]); console.log("TxHash: " + txInitSig); /* Invoke the Loan2Value instruction (instruction #1) */ @@ -64,10 +68,10 @@ export const invoke = async (loan: string, collateral: string) => { new web3.TransactionInstruction({ data: data, keys: accounts, - programId: contract + programId: contract.publicKey }) ); - let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, admin]); + let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, contract]); console.log("TxHash: " + txCheckSig); /* Try to invoke the Init instruction without authority */ @@ -79,7 +83,7 @@ export const invoke = async (loan: string, collateral: string) => { const attackerCreateInst = web3.SystemProgram.createAccount({ lamports: cost, space: loanInfoSize, - programId: contract, + programId: contract.publicKey, fromPubkey: payer.publicKey, newAccountPubkey: attackerDataAccount.publicKey, }); @@ -91,7 +95,7 @@ export const invoke = async (loan: string, collateral: string) => { new web3.TransactionInstruction({ data: data, keys: accounts, - programId: contract + programId: contract.publicKey }) ); let txAttackerSig = await web3.sendAndConfirmTransaction(conn, txAttacker, [payer, attackerDataAccount, attacker]); From 32692b8a60c61bd15bfd5cfba7af75f29262be7e Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:17:49 -0400 Subject: [PATCH 15/69] Cleanup --- examples/sol-contract/scripts/invoke.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 686ee88..31b2b79 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -5,7 +5,7 @@ const contract = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100 export const invoke = async (loan: string, collateral: string) => { if (contract.publicKey != process.argv[2]) { - console.info("Please update the contract key pair in invoke.ts."); + console.info("Please update the contract keypair in invoke.ts with build/example_sol_contract-keypair.json."); return; } console.info("Invoking contract " + contract.publicKey); @@ -20,7 +20,7 @@ export const invoke = async (loan: string, collateral: string) => { await conn.confirmTransaction(airdropSig); /* Prepare the createInst instruction: Create an account to store the - * LoanInfo data, which will be passed to Init for initialization*/ + * LoanInfo data, which will be passed to Init for initialization */ let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); let cost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); From 30ef43f6fe749f5fc29370802003213d32655d28 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:53:48 -0400 Subject: [PATCH 16/69] Add confidence interval --- examples/sol-contract/src/processor.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index fba7f43..a3b11cb 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -47,9 +47,6 @@ pub fn process_instruction( loan_info.loan_qty = 1; loan_info.collateral_qty = 3000; - msg!("The loan key is {}.", loan_info.loan_key); - msg!("The collateral key is {}.", loan_info.collateral_key); - msg!("Assume 1 unit of loan and 3000 unit of collateral."); LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) }, @@ -67,28 +64,36 @@ pub fn process_instruction( return Err(ProgramError::Custom(2)) } - // Calculate the value of the loan using Pyth price. + // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price() .ok_or(ProgramError::Custom(3))?; let loan_value = result1.price.checked_mul(loan_info.loan_qty) .ok_or(ProgramError::Custom(4))?; + let loan_conf = (result1.conf as f64) + * (10 as f64).powf(result1.expo as f64) + * (loan_info.loan_qty as f64); + let loan_value_max = loan_value as f64 + loan_conf; - // Calculate the value of the loan using Pyth price. + // Calculate the minimum value of the collateral using Pyth. let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; let result2 = feed2.get_current_price() .ok_or(ProgramError::Custom(3))?; let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) .ok_or(ProgramError::Custom(4))?; + let collateral_conf = (result2.conf as f64) + * (10 as f64).powf(result2.expo as f64) + * (loan_info.collateral_qty as f64); + let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. - if collateral_value > loan_value { - msg!("Loan unit price is {}.", result1.price); - msg!("Collateral unit price is {}.", result2.price); - msg!("The value of collateral is higher."); + msg!("The maximum loan value is {}.", loan_value_max); + msg!("The minimum collateral value is {}.", collateral_value_min); + if collateral_value_min > loan_value_max { + msg!("The value of the collateral is higher."); return Ok(()) } else { - msg!("The value of loan is higher!"); + msg!("The value of the loan is higher!"); return Err(ProgramError::Custom(5)) } } From 7235d205b118c649b7fc07c920ffbe3ffda57dac Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:56:12 -0400 Subject: [PATCH 17/69] Minor --- examples/sol-contract/scripts/invoke.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 31b2b79..b6fa6d7 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -98,6 +98,7 @@ export const invoke = async (loan: string, collateral: string) => { programId: contract.publicKey }) ); + /* The code should crash here with custom program error 0 */ let txAttackerSig = await web3.sendAndConfirmTransaction(conn, txAttacker, [payer, attackerDataAccount, attacker]); console.log("TxHash: " + txAttackerSig); } From d026e35d6e2fc5c0e63f2461f90f2cf5aefed49b Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:57:54 -0400 Subject: [PATCH 18/69] Minor --- examples/sol-contract/scripts/invoke.ts | 6 +++--- examples/sol-contract/src/processor.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index b6fa6d7..df48371 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -23,9 +23,9 @@ export const invoke = async (loan: string, collateral: string) => { * LoanInfo data, which will be passed to Init for initialization */ let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); - let cost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); + let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); const createInst = web3.SystemProgram.createAccount({ - lamports: cost, + lamports: dataCost, space: loanInfoSize, programId: contract.publicKey, fromPubkey: payer.publicKey, @@ -81,7 +81,7 @@ export const invoke = async (loan: string, collateral: string) => { let attackerDataAccount = web3.Keypair.generate(); const attackerCreateInst = web3.SystemProgram.createAccount({ - lamports: cost, + lamports: dataCost, space: loanInfoSize, programId: contract.publicKey, fromPubkey: payer.publicKey, diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index a3b11cb..8dea473 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -1,6 +1,5 @@ //! Program instruction processor - use solana_program::msg; use solana_program::pubkey::Pubkey; use solana_program::entrypoint::ProgramResult; From 715de4de25fd1aae7e0662375f7fc68989acf82a Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 09:03:46 -0400 Subject: [PATCH 19/69] Cleanup the contract --- examples/sol-contract/src/entrypoint.rs | 6 +++--- examples/sol-contract/src/instruction.rs | 8 +++++--- examples/sol-contract/src/lib.rs | 4 ++-- examples/sol-contract/src/processor.rs | 26 ++++++++++++------------ examples/sol-contract/src/state.rs | 11 +++++++--- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/examples/sol-contract/src/entrypoint.rs b/examples/sol-contract/src/entrypoint.rs index 6b151a3..8d29e4a 100644 --- a/examples/sol-contract/src/entrypoint.rs +++ b/examples/sol-contract/src/entrypoint.rs @@ -1,13 +1,13 @@ //! Program entrypoint +//! Every solana program has an entry point function with 3 parameters: +//! the program ID, the accounts being touched by this program, +//! and an arbitrary byte array as the input data for execution. use solana_program::entrypoint; use solana_program::pubkey::Pubkey; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; -// Every solana program has an entry point function with 3 parameters: -// the program ID, the accounts being touched by this program, -// and an arbitrary byte array as the input data for execution. entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index e0602f1..554bab1 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -1,12 +1,14 @@ //! Program instructions +//! A solana program contains a number of instructions. +//! There are 2 instructions in this example: +//! Init{} initializing some loan information, +//! Loan2Value{} checking the loan-to-value ratio of the loan. use borsh::BorshSerialize; use borsh::BorshDeserialize; -// A solana program contains a number of instructions. -// And this example contract contains only one instruction. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] -pub enum PythClientInstruction { +pub enum ExampleInstructions { Init{}, Loan2Value {}, } diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs index 0939b10..0e45b8a 100644 --- a/examples/sol-contract/src/lib.rs +++ b/examples/sol-contract/src/lib.rs @@ -1,5 +1,5 @@ -// This is the file being compiled to the bpf shared object (.so). -// It specifies the 3 modules of this example contract. +//! This file specifies the 4 modules of this example contract. + pub mod state; pub mod processor; pub mod entrypoint; diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 8dea473..b51b051 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -1,17 +1,19 @@ //! Program instruction processor +//! Only the program admin can issue the Init instruction. +//! And anyone can check the loan with the Loan2Value instruction. use solana_program::msg; use solana_program::pubkey::Pubkey; use solana_program::entrypoint::ProgramResult; use solana_program::program_error::ProgramError; use solana_program::program_pack::{IsInitialized, Pack}; -use solana_program::account_info::{next_account_info, AccountInfo}; +use solana_program::account_info::{AccountInfo, next_account_info}; use borsh::BorshDeserialize; use pyth_sdk_solana::load_price_feed_from_account_info; use crate::state::LoanInfo; -use crate::instruction::PythClientInstruction; +use crate::instruction::ExampleInstructions; pub fn process_instruction( _program_id: &Pubkey, @@ -24,10 +26,9 @@ pub fn process_instruction( let pyth_loan_account = next_account_info(account_iter)?; let pyth_collateral_account = next_account_info(account_iter)?; - let instruction = PythClientInstruction::try_from_slice(input)?; + let instruction = ExampleInstructions::try_from_slice(input)?; match instruction { - PythClientInstruction::Init {} => { - // Only the program admin can initialize a loan. + ExampleInstructions::Init {} => { if !(signer.key == _program_id && signer.is_signer) { return Err(ProgramError::Custom(0)) } @@ -49,8 +50,7 @@ pub fn process_instruction( LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) }, - PythClientInstruction::Loan2Value {} => { - // Anyone can check the loan to value ratio. + ExampleInstructions::Loan2Value {} => { let loan_info = LoanInfo::unpack_from_slice( &data_account.try_borrow_data()?)?; @@ -69,9 +69,9 @@ pub fn process_instruction( .ok_or(ProgramError::Custom(3))?; let loan_value = result1.price.checked_mul(loan_info.loan_qty) .ok_or(ProgramError::Custom(4))?; - let loan_conf = (result1.conf as f64) - * (10 as f64).powf(result1.expo as f64) - * (loan_info.loan_qty as f64); + let loan_conf = (result1.conf as f64) // confidence + * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent + * (loan_info.loan_qty as f64); // * quantity let loan_value_max = loan_value as f64 + loan_conf; // Calculate the minimum value of the collateral using Pyth. @@ -80,9 +80,9 @@ pub fn process_instruction( .ok_or(ProgramError::Custom(3))?; let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) .ok_or(ProgramError::Custom(4))?; - let collateral_conf = (result2.conf as f64) - * (10 as f64).powf(result2.expo as f64) - * (loan_info.collateral_qty as f64); + let collateral_conf = (result2.conf as f64) // confidence + * (10 as f64).powf(result2.expo as f64) // * 10 ^ exponent + * (loan_info.collateral_qty as f64); // * quantity let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index d92617d..cc86e72 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -1,15 +1,19 @@ +//! Program states +//! A data account would store a LoanInfo structure for the instructions. +//! This file contains the serialization and deserialization of LoanInfo. + use solana_program::{ + pubkey::Pubkey, program_error::ProgramError, program_pack::{IsInitialized, Pack, Sealed}, - pubkey::Pubkey, }; use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; pub struct LoanInfo { pub is_initialized: bool, - pub loan_key: Pubkey, - pub loan_qty: i64, + pub loan_key: Pubkey, + pub loan_qty: i64, pub collateral_key: Pubkey, pub collateral_qty: i64 } @@ -31,6 +35,7 @@ impl Pack for LoanInfo { src_loan_key, src_loan_qty, src_collateral_key, src_collateral_qty, ) = array_refs![src, 1, 32, 8, 32, 8]; + let is_initialized = match src_is_initialized { [0] => false, [1] => true, From 017030bdf361bcce08010cb7c1b12e93d7f52ef3 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 09:44:27 -0400 Subject: [PATCH 20/69] Cleanup the client-side code --- examples/sol-contract/scripts/invoke.sh | 2 +- examples/sol-contract/scripts/invoke.ts | 55 +++++++++++++++++-------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.sh b/examples/sol-contract/scripts/invoke.sh index 7c017ee..ef35dba 100755 --- a/examples/sol-contract/scripts/invoke.sh +++ b/examples/sol-contract/scripts/invoke.sh @@ -1 +1 @@ -cd scripts; npm install typescript; npm run build; node invoke.js `solana-keygen pubkey ../build/example_sol_contract-keypair.json` +cd scripts; npm install typescript; npm run build; node invoke.js diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index df48371..82dc1a7 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,26 +1,34 @@ +const fs = require('fs'); const web3 = require("@solana/web3.js"); const {struct, b, u8, u32} = require("@solana/buffer-layout"); -const contract = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) - export const invoke = async (loan: string, collateral: string) => { - if (contract.publicKey != process.argv[2]) { - console.info("Please update the contract keypair in invoke.ts with build/example_sol_contract-keypair.json."); + /* Obtain the contract keypair */ + var contract; + try { + let data = fs.readFileSync( + '../build/example_sol_contract-keypair.json' + ); + contract = web3.Keypair.fromSecretKey( + new Uint8Array(JSON.parse(data)) + ); + console.info("Invoking contract " + contract.publicKey); + } catch (error) { + console.error("Please run scripts/build.sh first."); return; } - console.info("Invoking contract " + contract.publicKey); - let conn = new web3.Connection(web3.clusterApiUrl('devnet')); /* Prepare the payer account */ - console.info("Airdropping to payer account..."); + let conn = new web3.Connection(web3.clusterApiUrl('devnet')); + console.info("Airdropping to the payer account..."); let payer = web3.Keypair.generate(); let airdropSig = await conn.requestAirdrop( payer.publicKey, web3.LAMPORTS_PER_SOL ); await conn.confirmTransaction(airdropSig); - /* Prepare the createInst instruction: Create an account to store the - * LoanInfo data, which will be passed to Init for initialization */ + /* Prepare the createInst instruction which creates an + * account storing the LoanInfo data for the instructions */ let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); @@ -32,7 +40,7 @@ export const invoke = async (loan: string, collateral: string) => { newAccountPubkey: dataAccount.publicKey, }); - /* Specify the accounts and parameters for invocations */ + /* Prepare the accounts and instruction data for transactions */ const dataKey = dataAccount.publicKey; const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); @@ -44,20 +52,22 @@ export const invoke = async (loan: string, collateral: string) => { ]; let dataLayout = struct([ u8('instruction') ]) let data = Buffer.alloc(dataLayout.span); - dataLayout.encode(Object.assign({instruction: 0}), data); /* Invoke the Init instruction (instruction #0) */ console.log("Creating data account and invoking Init..."); let txInit = new web3.Transaction({ feePayer: payer.publicKey }); + dataLayout.encode(Object.assign({instruction: 0}), data); txInit.add( - createInst, - new web3.TransactionInstruction({ + createInst, /* Create data account */ + new web3.TransactionInstruction({ /* Initialize data account */ data: data, keys: accounts, programId: contract.publicKey }) ); - let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, contract]); + let txInitSig = await web3.sendAndConfirmTransaction( + conn, txInit, [payer, dataAccount, contract] + ); console.log("TxHash: " + txInitSig); /* Invoke the Loan2Value instruction (instruction #1) */ @@ -71,7 +81,9 @@ export const invoke = async (loan: string, collateral: string) => { programId: contract.publicKey }) ); - let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, contract]); + let txCheckSig = await web3.sendAndConfirmTransaction( + conn, txCheck, [payer, contract] + ); console.log("TxHash: " + txCheckSig); /* Try to invoke the Init instruction without authority */ @@ -98,11 +110,18 @@ export const invoke = async (loan: string, collateral: string) => { programId: contract.publicKey }) ); - /* The code should crash here with custom program error 0 */ - let txAttackerSig = await web3.sendAndConfirmTransaction(conn, txAttacker, [payer, attackerDataAccount, attacker]); - console.log("TxHash: " + txAttackerSig); + + try { + let txAttackerSig = await web3.sendAndConfirmTransaction( + conn, txAttacker, [payer, attackerDataAccount, attacker] + ); + console.error("Attacker succeeded with TxHash: " + txAttackerSig); + } catch (error) { + console.log("Attacker failed to invoke unauthorized Init."); + } } +/* Pyth price accounts on the solana devnet */ let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; let usdtToUSD = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; invoke(ethToUSD, usdtToUSD); From 9458fc01b6814ac960b0912e3c4f0d0d9fa08397 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 17:17:30 -0400 Subject: [PATCH 21/69] Cleanup the contract code --- examples/sol-contract/README.md | 4 +- examples/sol-contract/src/entrypoint.rs | 10 ++-- examples/sol-contract/src/instruction.rs | 10 ++-- examples/sol-contract/src/lib.rs | 6 +-- examples/sol-contract/src/processor.rs | 61 +++++++++++++----------- examples/sol-contract/src/state.rs | 40 +++++++++++----- 6 files changed, 77 insertions(+), 54 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index 047652b..ab601e3 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -2,12 +2,14 @@ This repository contains a simple example demonstrating how to read the Pyth price from the Pyth contract on Solana. -The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. +The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. This function compares the value of some loan and some collateral, which is important in many lending protocols. An example invocation of this contract on the Solana devnet can be found in `scripts/invoke.ts`. We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and `node`. + + ## Usage ```shell diff --git a/examples/sol-contract/src/entrypoint.rs b/examples/sol-contract/src/entrypoint.rs index 8d29e4a..77591b9 100644 --- a/examples/sol-contract/src/entrypoint.rs +++ b/examples/sol-contract/src/entrypoint.rs @@ -1,12 +1,12 @@ //! Program entrypoint //! Every solana program has an entry point function with 3 parameters: //! the program ID, the accounts being touched by this program, -//! and an arbitrary byte array as the input data for execution. +//! and a byte array as the instruction data. -use solana_program::entrypoint; -use solana_program::pubkey::Pubkey; use solana_program::account_info::AccountInfo; +use solana_program::entrypoint; use solana_program::entrypoint::ProgramResult; +use solana_program::pubkey::Pubkey; entrypoint!(process_instruction); fn process_instruction( @@ -14,7 +14,5 @@ fn process_instruction( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - crate::processor::process_instruction( - program_id, accounts, instruction_data - ) + crate::processor::process_instruction(program_id, accounts, instruction_data) } diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index 554bab1..db553df 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -1,14 +1,16 @@ //! Program instructions //! A solana program contains a number of instructions. //! There are 2 instructions in this example: -//! Init{} initializing some loan information, +//! Init{} initializing some loan information and //! Loan2Value{} checking the loan-to-value ratio of the loan. -use borsh::BorshSerialize; -use borsh::BorshDeserialize; +use borsh::{ + BorshDeserialize, + BorshSerialize, +}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum ExampleInstructions { - Init{}, + Init {}, Loan2Value {}, } diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs index 0e45b8a..6924718 100644 --- a/examples/sol-contract/src/lib.rs +++ b/examples/sol-contract/src/lib.rs @@ -1,6 +1,6 @@ -//! This file specifies the 4 modules of this example contract. +//! This file specifies the 4 modules of this example program. -pub mod state; -pub mod processor; pub mod entrypoint; pub mod instruction; +pub mod processor; +pub mod state; diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index b51b051..9e51184 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -2,18 +2,24 @@ //! Only the program admin can issue the Init instruction. //! And anyone can check the loan with the Loan2Value instruction. -use solana_program::msg; -use solana_program::pubkey::Pubkey; +use solana_program::account_info::{ + next_account_info, + AccountInfo, +}; use solana_program::entrypoint::ProgramResult; +use solana_program::msg; use solana_program::program_error::ProgramError; -use solana_program::program_pack::{IsInitialized, Pack}; -use solana_program::account_info::{AccountInfo, next_account_info}; +use solana_program::program_pack::{ + IsInitialized, + Pack, +}; +use solana_program::pubkey::Pubkey; use borsh::BorshDeserialize; use pyth_sdk_solana::load_price_feed_from_account_info; -use crate::state::LoanInfo; use crate::instruction::ExampleInstructions; +use crate::state::LoanInfo; pub fn process_instruction( _program_id: &Pubkey, @@ -30,14 +36,13 @@ pub fn process_instruction( match instruction { ExampleInstructions::Init {} => { if !(signer.key == _program_id && signer.is_signer) { - return Err(ProgramError::Custom(0)) + return Err(ProgramError::Custom(0)); } - let mut loan_info = LoanInfo::unpack_from_slice( - &data_account.try_borrow_data()?)?; + let mut loan_info = LoanInfo::unpack_from_slice(&data_account.try_borrow_data()?)?; if loan_info.is_initialized() { - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(1)); } loan_info.is_initialized = true; @@ -49,40 +54,42 @@ pub fn process_instruction( LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) - }, + } ExampleInstructions::Loan2Value {} => { - let loan_info = LoanInfo::unpack_from_slice( - &data_account.try_borrow_data()?)?; + let loan_info = LoanInfo::unpack_from_slice(&data_account.try_borrow_data()?)?; if !loan_info.is_initialized() { - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(1)); } - if loan_info.loan_key != *pyth_loan_account.key || - loan_info.collateral_key != *pyth_collateral_account.key { - return Err(ProgramError::Custom(2)) - } + if loan_info.loan_key != *pyth_loan_account.key + || loan_info.collateral_key != *pyth_collateral_account.key + { + return Err(ProgramError::Custom(2)); + } // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; - let result1 = feed1.get_current_price() - .ok_or(ProgramError::Custom(3))?; - let loan_value = result1.price.checked_mul(loan_info.loan_qty) + let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; + let loan_value = result1 + .price + .checked_mul(loan_info.loan_qty) .ok_or(ProgramError::Custom(4))?; let loan_conf = (result1.conf as f64) // confidence * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent - * (loan_info.loan_qty as f64); // * quantity + * (loan_info.loan_qty as f64); // * quantity let loan_value_max = loan_value as f64 + loan_conf; // Calculate the minimum value of the collateral using Pyth. let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; - let result2 = feed2.get_current_price() - .ok_or(ProgramError::Custom(3))?; - let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) + let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(3))?; + let collateral_value = result2 + .price + .checked_mul(loan_info.collateral_qty) .ok_or(ProgramError::Custom(4))?; let collateral_conf = (result2.conf as f64) // confidence * (10 as f64).powf(result2.expo as f64) // * 10 ^ exponent - * (loan_info.collateral_qty as f64); // * quantity + * (loan_info.collateral_qty as f64); // * quantity let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. @@ -90,10 +97,10 @@ pub fn process_instruction( msg!("The minimum collateral value is {}.", collateral_value_min); if collateral_value_min > loan_value_max { msg!("The value of the collateral is higher."); - return Ok(()) + return Ok(()); } else { msg!("The value of the loan is higher!"); - return Err(ProgramError::Custom(5)) + return Err(ProgramError::Custom(5)); } } } diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index cc86e72..8fd892f 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -2,23 +2,31 @@ //! A data account would store a LoanInfo structure for the instructions. //! This file contains the serialization and deserialization of LoanInfo. -use solana_program::{ - pubkey::Pubkey, - program_error::ProgramError, - program_pack::{IsInitialized, Pack, Sealed}, +use solana_program::program_error::ProgramError; +use solana_program::program_pack::{ + IsInitialized, + Pack, + Sealed, }; +use solana_program::pubkey::Pubkey; -use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; +use arrayref::{ + array_mut_ref, + array_ref, + array_refs, + mut_array_refs, +}; pub struct LoanInfo { pub is_initialized: bool, pub loan_key: Pubkey, pub loan_qty: i64, pub collateral_key: Pubkey, - pub collateral_qty: i64 + pub collateral_qty: i64, } -impl Sealed for LoanInfo {} +impl Sealed for LoanInfo { +} impl IsInitialized for LoanInfo { fn is_initialized(&self) -> bool { @@ -32,8 +40,10 @@ impl Pack for LoanInfo { let src = array_ref![src, 0, LoanInfo::LEN]; let ( src_is_initialized, - src_loan_key, src_loan_qty, - src_collateral_key, src_collateral_qty, + src_loan_key, + src_loan_qty, + src_collateral_key, + src_collateral_qty, ) = array_refs![src, 1, 32, 8, 32, 8]; let is_initialized = match src_is_initialized { @@ -55,14 +65,18 @@ impl Pack for LoanInfo { let dst = array_mut_ref![dst, 0, LoanInfo::LEN]; let ( dst_is_initialized, - dst_loan_key, dst_loan_qty, - dst_collateral_key, dst_collateral_qty, + dst_loan_key, + dst_loan_qty, + dst_collateral_key, + dst_collateral_qty, ) = mut_array_refs![dst, 1, 32, 8, 32, 8]; let LoanInfo { is_initialized, - loan_key, loan_qty, - collateral_key, collateral_qty, + loan_key, + loan_qty, + collateral_key, + collateral_qty, } = self; dst_is_initialized[0] = *is_initialized as u8; From ed7791e43b947c866d7f5030aaabbfe88ce15cd7 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 17:54:06 -0400 Subject: [PATCH 22/69] Update readme --- examples/sol-contract/README.md | 47 ++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index ab601e3..6d3aa30 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -1,23 +1,50 @@ -# Pyth SDK Example Contract for Solana +# Pyth SDK Example Program for Solana -This repository contains a simple example demonstrating how to read the Pyth price from the Pyth contract on Solana. +This is an example demonstrating how to read prices from Pyth on Solana. -The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. -This function compares the value of some loan and some collateral, which is important in many lending protocols. -An example invocation of this contract on the Solana devnet can be found in `scripts/invoke.ts`. +The program has two instructions: `Init` and `Loan2Value`. +`Init` can *only* be invoked by the program admin and it will initialize some loan information. +`Loan2Value` can be invoked by anyone and it uses the current Pyth price to compare the value of the loan and the value of the collateral. +This is an important functionality in many lending protocols. -We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and `node`. +The key program logic is in 3 files. +The loan information structure is defined in `src/state.rs`, which also contains the serialization and deserialization code. +The two instructions are implemented in `src/processor.rs`. +An example invocation of these instructions on the Solana devnet can be found in `scripts/invoke.ts`. +## Where and how the Pyth SDK is used? +Pyth SDK is used in the `Loan2Value` instruction in `src/processor.rs`. +For the loan, the code first reads the unit price from the Pyth oracle. +```rust +let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; +let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; +``` + +And then calculate the loan value given the quantity of the loan. +```rust +let loan_value = result1 + .price + .checked_mul(loan_info.loan_qty) + .ok_or(ProgramError::Custom(4))?; +let loan_conf = (result1.conf as f64) // confidence + * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent + * (loan_info.loan_qty as f64); // * quantity +let loan_value_max = loan_value as f64 + loan_conf; +``` +This code says that, with high confidence, the maximum value of the loan does not exceed `loan_value_max`. +In a similar way, the code then calculates the minimum value of the collateral and compare the two. -## Usage +## Run this example program +We assume that you have installed `cargo`, `solana`, `npm` and `node`. ```shell +# Enter the root directory of this example > cd examples/sol-contract -# To build the example contract +# Build the example contract > scripts/build.sh -# To deploy the example contract +# Deploy the example contract > scripts/deploy.sh -# To invoke the example contract +# Invoke the example contract > scripts/invoke.ts ``` From 59dff00a51530bf160df6a52492ccbec9da42012 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 18:06:51 -0400 Subject: [PATCH 23/69] Minor --- examples/sol-contract/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index 6d3aa30..e067a5e 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -35,7 +35,7 @@ let loan_value_max = loan_value as f64 + loan_conf; This code says that, with high confidence, the maximum value of the loan does not exceed `loan_value_max`. In a similar way, the code then calculates the minimum value of the collateral and compare the two. -## Run this example program +## Run this program We assume that you have installed `cargo`, `solana`, `npm` and `node`. ```shell From 829d38bcaf18197a63b37c25ee14b6301aeded87 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 18:13:15 -0400 Subject: [PATCH 24/69] Minor --- examples/sol-contract/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index e067a5e..fa123a3 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -12,7 +12,7 @@ The loan information structure is defined in `src/state.rs`, which also contains The two instructions are implemented in `src/processor.rs`. An example invocation of these instructions on the Solana devnet can be found in `scripts/invoke.ts`. -## Where and how the Pyth SDK is used? +## Where and how is the Pyth SDK used? Pyth SDK is used in the `Loan2Value` instruction in `src/processor.rs`. For the loan, the code first reads the unit price from the Pyth oracle. ```rust From 6d580bc284670f211e2179cc668691f0937ca706 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 4 Oct 2022 19:41:21 -0400 Subject: [PATCH 25/69] Add package-lock.json --- examples/sol-contract/.gitignore | 1 - .../sol-contract/scripts/package-lock.json | 928 ++++++++++++++++++ 2 files changed, 928 insertions(+), 1 deletion(-) create mode 100644 examples/sol-contract/scripts/package-lock.json diff --git a/examples/sol-contract/.gitignore b/examples/sol-contract/.gitignore index 9a3314f..85faf92 100644 --- a/examples/sol-contract/.gitignore +++ b/examples/sol-contract/.gitignore @@ -3,4 +3,3 @@ build/example_sol_contract-keypair.json scripts/invoke.js scripts/invoke.js.map scripts/node_modules/ -scripts/package-lock.json \ No newline at end of file diff --git a/examples/sol-contract/scripts/package-lock.json b/examples/sol-contract/scripts/package-lock.json new file mode 100644 index 0000000..487b1e8 --- /dev/null +++ b/examples/sol-contract/scripts/package-lock.json @@ -0,0 +1,928 @@ +{ + "name": "scripts", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "scripts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@solana/web3.js": "^1.56.2" + }, + "devDependencies": { + "typescript": "^4.8.4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", + "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@noble/ed25519": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz", + "integrity": "sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/hashes": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.3.tgz", + "integrity": "sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz", + "integrity": "sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz", + "integrity": "sha512-lR0EMP2HC3+Mxwd4YcnZb0smnaDw7Bl2IQWZiTevRH5ZZBZn6VRWn3/92E3qdU4SSImJkA6IDHawOHAnx/qUvQ==", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/buffer-layout/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.63.1", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.63.1.tgz", + "integrity": "sha512-wgEdGVK5FTS2zENxbcGSvKpGZ0jDS6BUdGu8Gn6ns0CzgJkK83u4ip3THSnBPEQ5i/jrqukg998BwV1H67+qiQ==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@noble/ed25519": "^1.7.0", + "@noble/hashes": "^1.1.2", + "@noble/secp256k1": "^1.6.3", + "@solana/buffer-layout": "^4.0.0", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.0.0", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.1", + "fast-stable-stringify": "^1.0.0", + "jayson": "^3.4.4", + "node-fetch": "2", + "rpc-websockets": "^7.5.0", + "superstruct": "^0.14.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz", + "integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bufferutil": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", + "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", + "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "JSONStream": "^1.3.5", + "lodash": "^4.17.20", + "uuid": "^8.3.2", + "ws": "^7.4.5" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "node_modules/rpc-websockets": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz", + "integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "eventemitter3": "^4.0.7", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + } + }, + "node_modules/rpc-websockets/node_modules/ws": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", + "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + }, + "dependencies": { + "@babel/runtime": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", + "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@noble/ed25519": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz", + "integrity": "sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==" + }, + "@noble/hashes": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.3.tgz", + "integrity": "sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A==" + }, + "@noble/secp256k1": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz", + "integrity": "sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==" + }, + "@solana/buffer-layout": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz", + "integrity": "sha512-lR0EMP2HC3+Mxwd4YcnZb0smnaDw7Bl2IQWZiTevRH5ZZBZn6VRWn3/92E3qdU4SSImJkA6IDHawOHAnx/qUvQ==", + "requires": { + "buffer": "~6.0.3" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, + "@solana/web3.js": { + "version": "1.63.1", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.63.1.tgz", + "integrity": "sha512-wgEdGVK5FTS2zENxbcGSvKpGZ0jDS6BUdGu8Gn6ns0CzgJkK83u4ip3THSnBPEQ5i/jrqukg998BwV1H67+qiQ==", + "requires": { + "@babel/runtime": "^7.12.5", + "@noble/ed25519": "^1.7.0", + "@noble/hashes": "^1.1.2", + "@noble/secp256k1": "^1.6.3", + "@solana/buffer-layout": "^4.0.0", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.0.0", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.1", + "fast-stable-stringify": "^1.0.0", + "jayson": "^3.4.4", + "node-fetch": "2", + "rpc-websockets": "^7.5.0", + "superstruct": "^0.14.2" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + }, + "@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "requires": { + "@types/node": "*" + } + }, + "base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "requires": { + "bindings": "^1.3.0" + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "requires": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "requires": { + "base-x": "^3.0.2" + } + }, + "buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz", + "integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "bufferutil": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", + "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==" + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==" + }, + "fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "requires": {} + }, + "jayson": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", + "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", + "requires": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "JSONStream": "^1.3.5", + "lodash": "^4.17.20", + "uuid": "^8.3.2", + "ws": "^7.4.5" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==" + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "optional": true + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "rpc-websockets": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz", + "integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==", + "requires": { + "@babel/runtime": "^7.17.2", + "bufferutil": "^4.0.1", + "eventemitter3": "^4.0.7", + "utf-8-validate": "^5.0.2", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "dependencies": { + "ws": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", + "requires": {} + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + }, + "text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true + }, + "utf-8-validate": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", + "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} + } + } +} From eb7462b834b45c24b0935ecdfceb8f1b325f01f5 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 4 Oct 2022 19:57:46 -0400 Subject: [PATCH 26/69] Some renaming --- examples/sol-contract/README.md | 2 + examples/sol-contract/scripts/invoke.ts | 6 +-- examples/sol-contract/src/processor.rs | 16 ++++---- examples/sol-contract/src/state.rs | 52 +++++++++++++------------ 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index fa123a3..fed35f8 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -43,6 +43,8 @@ We assume that you have installed `cargo`, `solana`, `npm` and `node`. > cd examples/sol-contract # Build the example contract > scripts/build.sh +# Config solana CLI and set the url as devnet +> solana config set --url https://api.devnet.solana.com # Deploy the example contract > scripts/deploy.sh # Invoke the example contract diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 82dc1a7..729431f 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -28,7 +28,7 @@ export const invoke = async (loan: string, collateral: string) => { await conn.confirmTransaction(airdropSig); /* Prepare the createInst instruction which creates an - * account storing the LoanInfo data for the instructions */ + * account storing the AdminConfig data for the instructions */ let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); @@ -84,7 +84,7 @@ export const invoke = async (loan: string, collateral: string) => { let txCheckSig = await web3.sendAndConfirmTransaction( conn, txCheck, [payer, contract] ); - console.log("TxHash: " + txCheckSig); + console.log("TxHash: " + txCheckSig); /* Try to invoke the Init instruction without authority */ console.log("Trying an unauthorized invocation of Init..."); @@ -103,7 +103,7 @@ export const invoke = async (loan: string, collateral: string) => { let txAttacker = new web3.Transaction({ feePayer: payer.publicKey }); dataLayout.encode(Object.assign({instruction: 0}), data); txAttacker.add( - attackerCreateInst, + attackerCreateInst, new web3.TransactionInstruction({ data: data, keys: accounts, diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 9e51184..8642457 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -19,7 +19,7 @@ use borsh::BorshDeserialize; use pyth_sdk_solana::load_price_feed_from_account_info; use crate::instruction::ExampleInstructions; -use crate::state::LoanInfo; +use crate::state::AdminConfig; pub fn process_instruction( _program_id: &Pubkey, @@ -39,31 +39,31 @@ pub fn process_instruction( return Err(ProgramError::Custom(0)); } - let mut loan_info = LoanInfo::unpack_from_slice(&data_account.try_borrow_data()?)?; + let mut loan_info = AdminConfig::unpack_from_slice(&data_account.try_borrow_data()?)?; if loan_info.is_initialized() { return Err(ProgramError::Custom(1)); } loan_info.is_initialized = true; - loan_info.loan_key = *pyth_loan_account.key; - loan_info.collateral_key = *pyth_collateral_account.key; + loan_info.loan_price_feed_id = *pyth_loan_account.key; + loan_info.collateral_price_feed_id = *pyth_collateral_account.key; // Give some dummy numbers for simplicity of this example. loan_info.loan_qty = 1; loan_info.collateral_qty = 3000; - LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; + AdminConfig::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) } ExampleInstructions::Loan2Value {} => { - let loan_info = LoanInfo::unpack_from_slice(&data_account.try_borrow_data()?)?; + let loan_info = AdminConfig::unpack_from_slice(&data_account.try_borrow_data()?)?; if !loan_info.is_initialized() { return Err(ProgramError::Custom(1)); } - if loan_info.loan_key != *pyth_loan_account.key - || loan_info.collateral_key != *pyth_collateral_account.key + if loan_info.loan_price_feed_id != *pyth_loan_account.key + || loan_info.collateral_price_feed_id != *pyth_collateral_account.key { return Err(ProgramError::Custom(2)); } diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index 8fd892f..5337352 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -1,6 +1,6 @@ //! Program states -//! A data account would store a LoanInfo structure for the instructions. -//! This file contains the serialization and deserialization of LoanInfo. +//! A data account would store an AdminConfig structure for instructions. +//! This file contains the serialization / deserialization of AdminConfig. use solana_program::program_error::ProgramError; use solana_program::program_pack::{ @@ -17,32 +17,34 @@ use arrayref::{ mut_array_refs, }; -pub struct LoanInfo { - pub is_initialized: bool, - pub loan_key: Pubkey, - pub loan_qty: i64, - pub collateral_key: Pubkey, - pub collateral_qty: i64, +// loan_price_feed_id and collateral_price_feed_id are the +// Pyth price accounts for the loan and collateral tokens +pub struct AdminConfig { + pub is_initialized: bool, + pub loan_price_feed_id: Pubkey, + pub loan_qty: i64, + pub collateral_price_feed_id: Pubkey, + pub collateral_qty: i64, } -impl Sealed for LoanInfo { +impl Sealed for AdminConfig { } -impl IsInitialized for LoanInfo { +impl IsInitialized for AdminConfig { fn is_initialized(&self) -> bool { self.is_initialized } } -impl Pack for LoanInfo { +impl Pack for AdminConfig { const LEN: usize = 1 + 32 + 8 + 32 + 8; fn unpack_from_slice(src: &[u8]) -> Result { - let src = array_ref![src, 0, LoanInfo::LEN]; + let src = array_ref![src, 0, AdminConfig::LEN]; let ( src_is_initialized, - src_loan_key, + src_loan_price_feed_id, src_loan_qty, - src_collateral_key, + src_collateral_price_feed_id, src_collateral_qty, ) = array_refs![src, 1, 32, 8, 32, 8]; @@ -52,37 +54,37 @@ impl Pack for LoanInfo { _ => return Err(ProgramError::InvalidAccountData), }; - Ok(LoanInfo { + Ok(AdminConfig { is_initialized, - loan_key: Pubkey::new_from_array(*src_loan_key), + loan_price_feed_id: Pubkey::new_from_array(*src_loan_price_feed_id), loan_qty: i64::from_le_bytes(*src_loan_qty), - collateral_key: Pubkey::new_from_array(*src_collateral_key), + collateral_price_feed_id: Pubkey::new_from_array(*src_collateral_price_feed_id), collateral_qty: i64::from_le_bytes(*src_collateral_qty), }) } fn pack_into_slice(&self, dst: &mut [u8]) { - let dst = array_mut_ref![dst, 0, LoanInfo::LEN]; + let dst = array_mut_ref![dst, 0, AdminConfig::LEN]; let ( dst_is_initialized, - dst_loan_key, + dst_loan_price_feed_id, dst_loan_qty, - dst_collateral_key, + dst_collateral_price_feed_id, dst_collateral_qty, ) = mut_array_refs![dst, 1, 32, 8, 32, 8]; - let LoanInfo { + let AdminConfig { is_initialized, - loan_key, + loan_price_feed_id, loan_qty, - collateral_key, + collateral_price_feed_id, collateral_qty, } = self; dst_is_initialized[0] = *is_initialized as u8; - dst_loan_key.copy_from_slice(loan_key.as_ref()); + dst_loan_price_feed_id.copy_from_slice(loan_price_feed_id.as_ref()); *dst_loan_qty = loan_qty.to_le_bytes(); - dst_collateral_key.copy_from_slice(collateral_key.as_ref()); + dst_collateral_price_feed_id.copy_from_slice(collateral_price_feed_id.as_ref()); *dst_collateral_qty = collateral_qty.to_le_bytes(); } } From 9d86e9fa27ec6476bad4a77096a2fe38eafc40fb Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 4 Oct 2022 20:04:50 -0400 Subject: [PATCH 27/69] Hardcode loan/collateral quantity in Loan2Value instruction --- examples/sol-contract/scripts/invoke.ts | 2 +- examples/sol-contract/src/processor.rs | 15 ++++++------- examples/sol-contract/src/state.rs | 28 +++++-------------------- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 729431f..829c569 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -29,7 +29,7 @@ export const invoke = async (loan: string, collateral: string) => { /* Prepare the createInst instruction which creates an * account storing the AdminConfig data for the instructions */ - let loanInfoSize = 1 + 32 + 8 + 32 + 8; + let loanInfoSize = 1 + 32 + 32; let dataAccount = web3.Keypair.generate(); let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); const createInst = web3.SystemProgram.createAccount({ diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 8642457..532491d 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -48,9 +48,6 @@ pub fn process_instruction( loan_info.is_initialized = true; loan_info.loan_price_feed_id = *pyth_loan_account.key; loan_info.collateral_price_feed_id = *pyth_collateral_account.key; - // Give some dummy numbers for simplicity of this example. - loan_info.loan_qty = 1; - loan_info.collateral_qty = 3000; AdminConfig::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) @@ -68,16 +65,20 @@ pub fn process_instruction( return Err(ProgramError::Custom(2)); } + // Give some dummy numbers for simplicity of this example. + let loan_qty = 1; + let collateral_qty = 3000; + // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; let loan_value = result1 .price - .checked_mul(loan_info.loan_qty) + .checked_mul(loan_qty) .ok_or(ProgramError::Custom(4))?; let loan_conf = (result1.conf as f64) // confidence * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent - * (loan_info.loan_qty as f64); // * quantity + * (loan_qty as f64); // * quantity let loan_value_max = loan_value as f64 + loan_conf; // Calculate the minimum value of the collateral using Pyth. @@ -85,11 +86,11 @@ pub fn process_instruction( let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(3))?; let collateral_value = result2 .price - .checked_mul(loan_info.collateral_qty) + .checked_mul(collateral_qty) .ok_or(ProgramError::Custom(4))?; let collateral_conf = (result2.conf as f64) // confidence * (10 as f64).powf(result2.expo as f64) // * 10 ^ exponent - * (loan_info.collateral_qty as f64); // * quantity + * (collateral_qty as f64); // * quantity let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index 5337352..db6a979 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -22,9 +22,7 @@ use arrayref::{ pub struct AdminConfig { pub is_initialized: bool, pub loan_price_feed_id: Pubkey, - pub loan_qty: i64, pub collateral_price_feed_id: Pubkey, - pub collateral_qty: i64, } impl Sealed for AdminConfig { @@ -37,16 +35,11 @@ impl IsInitialized for AdminConfig { } impl Pack for AdminConfig { - const LEN: usize = 1 + 32 + 8 + 32 + 8; + const LEN: usize = 1 + 32 + 32; fn unpack_from_slice(src: &[u8]) -> Result { let src = array_ref![src, 0, AdminConfig::LEN]; - let ( - src_is_initialized, - src_loan_price_feed_id, - src_loan_qty, - src_collateral_price_feed_id, - src_collateral_qty, - ) = array_refs![src, 1, 32, 8, 32, 8]; + let (src_is_initialized, src_loan_price_feed_id, src_collateral_price_feed_id) = + array_refs![src, 1, 32, 32]; let is_initialized = match src_is_initialized { [0] => false, @@ -57,34 +50,23 @@ impl Pack for AdminConfig { Ok(AdminConfig { is_initialized, loan_price_feed_id: Pubkey::new_from_array(*src_loan_price_feed_id), - loan_qty: i64::from_le_bytes(*src_loan_qty), collateral_price_feed_id: Pubkey::new_from_array(*src_collateral_price_feed_id), - collateral_qty: i64::from_le_bytes(*src_collateral_qty), }) } fn pack_into_slice(&self, dst: &mut [u8]) { let dst = array_mut_ref![dst, 0, AdminConfig::LEN]; - let ( - dst_is_initialized, - dst_loan_price_feed_id, - dst_loan_qty, - dst_collateral_price_feed_id, - dst_collateral_qty, - ) = mut_array_refs![dst, 1, 32, 8, 32, 8]; + let (dst_is_initialized, dst_loan_price_feed_id, dst_collateral_price_feed_id) = + mut_array_refs![dst, 1, 32, 32]; let AdminConfig { is_initialized, loan_price_feed_id, - loan_qty, collateral_price_feed_id, - collateral_qty, } = self; dst_is_initialized[0] = *is_initialized as u8; dst_loan_price_feed_id.copy_from_slice(loan_price_feed_id.as_ref()); - *dst_loan_qty = loan_qty.to_le_bytes(); dst_collateral_price_feed_id.copy_from_slice(collateral_price_feed_id.as_ref()); - *dst_collateral_qty = collateral_qty.to_le_bytes(); } } From 0bdb6c1c2e053a64ab6dfaba5669146509bc56ec Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 4 Oct 2022 21:10:05 -0400 Subject: [PATCH 28/69] Make loan_qty and collateral_qty parameters of instruction --- examples/sol-contract/scripts/invoke.ts | 30 +++++++++++++++++------- examples/sol-contract/src/instruction.rs | 5 +++- examples/sol-contract/src/processor.rs | 12 ++++++---- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 829c569..0d2c91d 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,6 +1,6 @@ const fs = require('fs'); const web3 = require("@solana/web3.js"); -const {struct, b, u8, u32} = require("@solana/buffer-layout"); +const {struct, b, u8, blob} = require("@solana/buffer-layout"); export const invoke = async (loan: string, collateral: string) => { /* Obtain the contract keypair */ @@ -50,17 +50,22 @@ export const invoke = async (loan: string, collateral: string) => { {pubkey: loanKey, isSigner: false, isWritable: false}, {pubkey: collateralKey, isSigner: false, isWritable: false}, ]; - let dataLayout = struct([ u8('instruction') ]) - let data = Buffer.alloc(dataLayout.span); + + let initLayout = struct([ u8('instruction') ]) + let initData = Buffer.alloc(initLayout.span); + let loan2ValueLayout = struct([ + u8('instruction'), blob(8, 'loan_qty'), blob(8, 'collateral_qty') + ]) + let loan2ValueData = Buffer.alloc(loan2ValueLayout.span); /* Invoke the Init instruction (instruction #0) */ console.log("Creating data account and invoking Init..."); let txInit = new web3.Transaction({ feePayer: payer.publicKey }); - dataLayout.encode(Object.assign({instruction: 0}), data); + initLayout.encode({instruction: 0}, initData); txInit.add( createInst, /* Create data account */ new web3.TransactionInstruction({ /* Initialize data account */ - data: data, + data: initData, keys: accounts, programId: contract.publicKey }) @@ -72,11 +77,19 @@ export const invoke = async (loan: string, collateral: string) => { /* Invoke the Loan2Value instruction (instruction #1) */ console.log("Checking loan to value ratio..."); + /* Encode 0x1 in big ending */ + let loan_qty = Buffer.from('0100000000000000', 'hex'); + /* Encode 0xbb8 (3000) in big ending */ + let collateral_qty = Buffer.from('b80b000000000000', 'hex'); let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); - dataLayout.encode(Object.assign({instruction: 1}), data); + loan2ValueLayout.encode( + {instruction: 1, + loan_qty: blob(8).decode(loan_qty), + collateral_qty: blob(8).decode(collateral_qty)} + , loan2ValueData); txCheck.add( new web3.TransactionInstruction({ - data: data, + data: loan2ValueData, keys: accounts, programId: contract.publicKey }) @@ -101,11 +114,10 @@ export const invoke = async (loan: string, collateral: string) => { }); let txAttacker = new web3.Transaction({ feePayer: payer.publicKey }); - dataLayout.encode(Object.assign({instruction: 0}), data); txAttacker.add( attackerCreateInst, new web3.TransactionInstruction({ - data: data, + data: initData, keys: accounts, programId: contract.publicKey }) diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index db553df..b4409da 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -12,5 +12,8 @@ use borsh::{ #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum ExampleInstructions { Init {}, - Loan2Value {}, + Loan2Value { + loan_qty: i64, + collateral_qty: i64, + }, } diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 532491d..691ff28 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -52,7 +52,13 @@ pub fn process_instruction( AdminConfig::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) } - ExampleInstructions::Loan2Value {} => { + ExampleInstructions::Loan2Value { + loan_qty, + collateral_qty, + } => { + msg!("Loan quantity is {}.", loan_qty); + msg!("Collateral quantity is {}.", collateral_qty); + let loan_info = AdminConfig::unpack_from_slice(&data_account.try_borrow_data()?)?; if !loan_info.is_initialized() { @@ -65,10 +71,6 @@ pub fn process_instruction( return Err(ProgramError::Custom(2)); } - // Give some dummy numbers for simplicity of this example. - let loan_qty = 1; - let collateral_qty = 3000; - // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; From bca85aeb705c9129e790ddb4a6cbd4652f9de295 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 5 Oct 2022 10:46:12 -0400 Subject: [PATCH 29/69] Minor --- examples/sol-contract/scripts/invoke.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 0d2c91d..0019ee1 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -60,8 +60,8 @@ export const invoke = async (loan: string, collateral: string) => { /* Invoke the Init instruction (instruction #0) */ console.log("Creating data account and invoking Init..."); - let txInit = new web3.Transaction({ feePayer: payer.publicKey }); initLayout.encode({instruction: 0}, initData); + let txInit = new web3.Transaction({ feePayer: payer.publicKey }); txInit.add( createInst, /* Create data account */ new web3.TransactionInstruction({ /* Initialize data account */ @@ -81,12 +81,13 @@ export const invoke = async (loan: string, collateral: string) => { let loan_qty = Buffer.from('0100000000000000', 'hex'); /* Encode 0xbb8 (3000) in big ending */ let collateral_qty = Buffer.from('b80b000000000000', 'hex'); - let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); loan2ValueLayout.encode( {instruction: 1, loan_qty: blob(8).decode(loan_qty), collateral_qty: blob(8).decode(collateral_qty)} , loan2ValueData); + + let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); txCheck.add( new web3.TransactionInstruction({ data: loan2ValueData, From 6f4a4c012c90f090b1b650bdeafb26e383128c92 Mon Sep 17 00:00:00 2001 From: dadepo Date: Thu, 6 Oct 2022 13:12:59 +0400 Subject: [PATCH 30/69] Update dependency requirements for Solana related dependencies (#75) --- .github/workflows/pyth-sdk-solana.yml | 2 +- pyth-sdk-solana/Cargo.toml | 6 +++--- pyth-sdk-solana/test-contract/Cargo.toml | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pyth-sdk-solana.yml b/.github/workflows/pyth-sdk-solana.yml index 4470624..6a891ce 100644 --- a/.github/workflows/pyth-sdk-solana.yml +++ b/.github/workflows/pyth-sdk-solana.yml @@ -34,7 +34,7 @@ jobs: run: sudo apt-get update && sudo apt-get install libudev-dev - name: Install Solana Binaries run: | - sh -c "$(curl -sSfL https://release.solana.com/v1.8.14/install)" + sh -c "$(curl -sSfL https://release.solana.com/v1.10.40/install)" echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH - name: Build run: cargo build --verbose diff --git a/pyth-sdk-solana/Cargo.toml b/pyth-sdk-solana/Cargo.toml index 38e0012..4e4c7a7 100644 --- a/pyth-sdk-solana/Cargo.toml +++ b/pyth-sdk-solana/Cargo.toml @@ -11,7 +11,7 @@ keywords = [ "pyth", "solana", "oracle" ] readme = "README.md" [dependencies] -solana-program = "1.8.1, < 1.11" +solana-program = "1.10.40" borsh = "0.9" borsh-derive = "0.9.0" bytemuck = "1.7.2" @@ -22,8 +22,8 @@ serde = { version = "1.0.136", features = ["derive"] } pyth-sdk = { path = "../pyth-sdk", version = "0.6.1" } [dev-dependencies] -solana-client = "1.8.1, < 1.11" -solana-sdk = "1.8.1, < 1.11" +solana-client = "1.10.40" +solana-sdk = "1.10.40" [lib] crate-type = ["cdylib", "lib"] diff --git a/pyth-sdk-solana/test-contract/Cargo.toml b/pyth-sdk-solana/test-contract/Cargo.toml index 27e2710..754f44c 100644 --- a/pyth-sdk-solana/test-contract/Cargo.toml +++ b/pyth-sdk-solana/test-contract/Cargo.toml @@ -9,15 +9,15 @@ no-entrypoint = [] [dependencies] pyth-sdk-solana = { path = "../", version = "0.6.1" } -solana-program = "1.8.1, < 1.11" # Currently latest Solana 1.11 crate can't build bpf: https://github.com/solana-labs/solana/issues/26188 +solana-program = "1.10.40" bytemuck = "1.7.2" borsh = "0.9" borsh-derive = "0.9.0" [dev-dependencies] -solana-program-test = "1.8.1, < 1.11" -solana-client = "1.8.1, < 1.11" -solana-sdk = "1.8.1, < 1.11" +solana-program-test = "1.10.40" +solana-client = "1.10.40" +solana-sdk = "1.10.40" [lib] crate-type = ["cdylib", "lib"] From 37604d22444ccfee98bcfbe6dec54636e63d980d Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Thu, 6 Oct 2022 09:26:11 +0000 Subject: [PATCH 31/69] Update the price schema (#76) --- pyth-sdk-cw/schema/price_feed_response.json | 35 +++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/pyth-sdk-cw/schema/price_feed_response.json b/pyth-sdk-cw/schema/price_feed_response.json index 730ecdb..f7df932 100644 --- a/pyth-sdk-cw/schema/price_feed_response.json +++ b/pyth-sdk-cw/schema/price_feed_response.json @@ -118,12 +118,35 @@ }, "PriceStatus": { "description": "Represents availability status of a price feed.", - "type": "string", - "enum": [ - "Unknown", - "Trading", - "Halted", - "Auction" + "oneOf": [ + { + "description": "The price feed is not currently updating for an unknown reason.", + "type": "string", + "enum": [ + "Unknown" + ] + }, + { + "description": "The price feed is updating as expected.", + "type": "string", + "enum": [ + "Trading" + ] + }, + { + "description": "The price feed is not currently updating because trading in the product has been halted.", + "type": "string", + "enum": [ + "Halted" + ] + }, + { + "description": "The price feed is not currently updating because an auction is setting the price.", + "type": "string", + "enum": [ + "Auction" + ] + } ] } } From 4b7008c9a496ab8610c5f37cc74ee365e5d80a15 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 20:34:59 -0400 Subject: [PATCH 32/69] Add example contract for solana --- Cargo.toml | 1 + examples/sol-contract/Cargo.toml | 20 +++++++++ examples/sol-contract/build/README.md | 1 + examples/sol-contract/scripts/build.sh | 1 + examples/sol-contract/scripts/deploy.sh | 1 + examples/sol-contract/scripts/invoke.ts | 52 ++++++++++++++++++++++++ examples/sol-contract/src/entrypoint.rs | 17 ++++++++ examples/sol-contract/src/instruction.rs | 24 +++++++++++ examples/sol-contract/src/lib.rs | 8 ++++ examples/sol-contract/src/processor.rs | 48 ++++++++++++++++++++++ 10 files changed, 173 insertions(+) create mode 100644 examples/sol-contract/Cargo.toml create mode 100644 examples/sol-contract/build/README.md create mode 100755 examples/sol-contract/scripts/build.sh create mode 100755 examples/sol-contract/scripts/deploy.sh create mode 100644 examples/sol-contract/scripts/invoke.ts create mode 100644 examples/sol-contract/src/entrypoint.rs create mode 100644 examples/sol-contract/src/instruction.rs create mode 100644 examples/sol-contract/src/lib.rs create mode 100644 examples/sol-contract/src/processor.rs diff --git a/Cargo.toml b/Cargo.toml index 9ea6f83..aa2dca3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "pyth-sdk-solana/test-contract", "pyth-sdk-cw", "examples/cw-contract", + "examples/sol-contract" ] diff --git a/examples/sol-contract/Cargo.toml b/examples/sol-contract/Cargo.toml new file mode 100644 index 0000000..4e512ab --- /dev/null +++ b/examples/sol-contract/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "example-sol-contract" +version = "0.1.0" +authors = ["Pyth Data Foundation"] +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +borsh = "0.9" +bytemuck = "1.7.2" +borsh-derive = "0.9.0" +pyth-sdk-solana = "0.5.0" +solana-program = "1.8.1, < 1.11" + +[dev-dependencies] +solana-sdk = "1.8.1, < 1.11" +solana-client = "1.8.1, < 1.11" +solana-program-test = "1.8.1, < 1.11" \ No newline at end of file diff --git a/examples/sol-contract/build/README.md b/examples/sol-contract/build/README.md new file mode 100644 index 0000000..5a4d9af --- /dev/null +++ b/examples/sol-contract/build/README.md @@ -0,0 +1 @@ +This directory holds the output of build-bpf. See scripts/build.sh. diff --git a/examples/sol-contract/scripts/build.sh b/examples/sol-contract/scripts/build.sh new file mode 100755 index 0000000..f39080b --- /dev/null +++ b/examples/sol-contract/scripts/build.sh @@ -0,0 +1 @@ +cargo build-bpf --sbf-out-dir ./build diff --git a/examples/sol-contract/scripts/deploy.sh b/examples/sol-contract/scripts/deploy.sh new file mode 100755 index 0000000..adc1169 --- /dev/null +++ b/examples/sol-contract/scripts/deploy.sh @@ -0,0 +1 @@ +solana program deploy --program-id build/example_sol_contract-keypair.json build/example_sol_contract.so diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts new file mode 100644 index 0000000..7bf1ed1 --- /dev/null +++ b/examples/sol-contract/scripts/invoke.ts @@ -0,0 +1,52 @@ +const {struct, u8} = require("@solana/buffer-layout"); +const web3 = require("@solana/web3.js"); + +export const invoke = async (loan: string, collateral: string) => { + /* Airdrop */ + console.log("Airdroping..."); + let keypair = web3.Keypair.generate(); + let payer = web3.Keypair.generate(); + let conn = new web3.Connection(web3.clusterApiUrl('devnet')); + let airdropSig = await conn.requestAirdrop( + payer.publicKey, + web3.LAMPORTS_PER_SOL, + ); + await conn.confirmTransaction(airdropSig); + + /* Prepare accounts */ + const loanKey = new web3.PublicKey(loan); + const collateralKey = new web3.PublicKey(collateral); + let keys = [{pubkey: loanKey, isSigner: false, isWritable: false}, + {pubkey: collateralKey, isSigner: false, isWritable: false}, + {pubkey: keypair.publicKey, isSigner: true, isWritable: false}]; + + /* Prepare parameters */ + let allocateStruct = { + index: 0, // Loan2Value is instruction#0 in the program + layout: struct([ + u8('instruction'), + ]) + }; + let data = Buffer.alloc(allocateStruct.layout.span); + let layoutFields = Object.assign({instruction: allocateStruct.index}); + allocateStruct.layout.encode(layoutFields, data); + + /* Invoke transaction */ + console.log("Invoking transaction..."); + let tx = new web3.Transaction({ + feePayer: payer.publicKey + }); + tx.add(new web3.TransactionInstruction({ + keys, + programId: "F6SbH5bQZ8peCqQUDmpYqhUtDJVMcAe9JGLPMLGAesfy", + data + })); + + let txSig = await web3.sendAndConfirmTransaction(conn, tx, [payer, keypair]); + console.log("Confirmed TxHash: " + txSig); +} + +let eth = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; +let usdc = "5SSkXsEKQepHHAewytPVwdej4epN1nxgLVM84L4KXgy7"; +let usdt = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; +invoke(eth, usdt); diff --git a/examples/sol-contract/src/entrypoint.rs b/examples/sol-contract/src/entrypoint.rs new file mode 100644 index 0000000..e6fd7ba --- /dev/null +++ b/examples/sol-contract/src/entrypoint.rs @@ -0,0 +1,17 @@ +//! Program entrypoint + +use solana_program::entrypoint; +use solana_program::pubkey::Pubkey; +use solana_program::account_info::AccountInfo; +use solana_program::entrypoint::ProgramResult; + +entrypoint!(process_instruction); +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + crate::processor::process_instruction( + program_id, accounts, instruction_data + ) +} diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs new file mode 100644 index 0000000..acb42f8 --- /dev/null +++ b/examples/sol-contract/src/instruction.rs @@ -0,0 +1,24 @@ +//! Program instructions + +use crate::id; +use borsh::{ + BorshDeserialize, + BorshSerialize, +}; +use solana_program::instruction::Instruction; +use solana_program::instruction::AccountMeta; + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] +pub enum PythClientInstruction { + Loan2Value {}, +} + +pub fn loan_to_value(loan: AccountMeta, collateral: AccountMeta) -> Instruction { + Instruction { + program_id: id(), + accounts: vec![loan, collateral], + data: PythClientInstruction::Loan2Value {} + .try_to_vec() + .unwrap(), + } +} diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs new file mode 100644 index 0000000..701f834 --- /dev/null +++ b/examples/sol-contract/src/lib.rs @@ -0,0 +1,8 @@ +pub mod processor; +pub mod entrypoint; +pub mod instruction; + +// This will only be used in local testing. +solana_program::declare_id!( + "PythBestPractice111111111111111111111111111" +); diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs new file mode 100644 index 0000000..22baa7e --- /dev/null +++ b/examples/sol-contract/src/processor.rs @@ -0,0 +1,48 @@ +//! Program instruction processor + +use solana_program::msg; +use solana_program::pubkey::Pubkey; +use solana_program::account_info::AccountInfo; +use solana_program::entrypoint::ProgramResult; +use solana_program::program_error::ProgramError; + +use borsh::BorshDeserialize; +use crate::instruction::PythClientInstruction; +use pyth_sdk_solana::load_price_feed_from_account_info; + +pub fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + input: &[u8], +) -> ProgramResult { + let instruction = PythClientInstruction::try_from_slice(input).unwrap(); + match instruction { + PythClientInstruction::Loan2Value {} => { + // Suppose we have 1 loan token and 3000 collateral token + let loan_cnt = 1; + let collateral_cnt = 3000; + + let loan = &_accounts[0]; + msg!("The loan key is {}.", loan.key); + let feed1 = load_price_feed_from_account_info(&loan).unwrap(); + let result1 = feed1.get_current_price().unwrap(); + let loan_value = result1.price * loan_cnt; + + let collateral = &_accounts[1]; + msg!("The collateral key is {}.", collateral.key); + let feed2 = load_price_feed_from_account_info(&collateral).unwrap(); + let result2 = feed2.get_current_price().unwrap(); + let collateral_value = result2.price * collateral_cnt; + + if collateral_value > loan_value { + msg!("Loan unit price is {}.", result1.price); + msg!("Collateral unit price is {}.", result2.price); + msg!("Collateral value is higher."); + Ok(()) + } else { + msg!("Loan value is higher!"); + Err(ProgramError::Custom(0)) + } + } + } +} From 3a03ac30926d912405d543f0152f7e28bd0bff47 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 20:52:22 -0400 Subject: [PATCH 33/69] Add scripts for invocation in examples/sol-contract --- examples/sol-contract/.gitignore | 6 ++++++ examples/sol-contract/scripts/invoke.sh | 1 + examples/sol-contract/scripts/invoke.ts | 17 ++++++++--------- examples/sol-contract/scripts/package.json | 18 ++++++++++++++++++ examples/sol-contract/scripts/tsconfig.json | 10 ++++++++++ examples/sol-contract/src/processor.rs | 15 ++++++++------- 6 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 examples/sol-contract/.gitignore create mode 100755 examples/sol-contract/scripts/invoke.sh create mode 100644 examples/sol-contract/scripts/package.json create mode 100644 examples/sol-contract/scripts/tsconfig.json diff --git a/examples/sol-contract/.gitignore b/examples/sol-contract/.gitignore new file mode 100644 index 0000000..9a3314f --- /dev/null +++ b/examples/sol-contract/.gitignore @@ -0,0 +1,6 @@ +build/example_sol_contract.so +build/example_sol_contract-keypair.json +scripts/invoke.js +scripts/invoke.js.map +scripts/node_modules/ +scripts/package-lock.json \ No newline at end of file diff --git a/examples/sol-contract/scripts/invoke.sh b/examples/sol-contract/scripts/invoke.sh new file mode 100755 index 0000000..ef35dba --- /dev/null +++ b/examples/sol-contract/scripts/invoke.sh @@ -0,0 +1 @@ +cd scripts; npm install typescript; npm run build; node invoke.js diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 7bf1ed1..034c243 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,11 +1,11 @@ -const {struct, u8} = require("@solana/buffer-layout"); const web3 = require("@solana/web3.js"); +const {struct, u8} = require("@solana/buffer-layout"); export const invoke = async (loan: string, collateral: string) => { /* Airdrop */ console.log("Airdroping..."); - let keypair = web3.Keypair.generate(); let payer = web3.Keypair.generate(); + let keypair = web3.Keypair.generate(); let conn = new web3.Connection(web3.clusterApiUrl('devnet')); let airdropSig = await conn.requestAirdrop( payer.publicKey, @@ -13,7 +13,7 @@ export const invoke = async (loan: string, collateral: string) => { ); await conn.confirmTransaction(airdropSig); - /* Prepare accounts */ + /* Specify accounts being touched */ const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); let keys = [{pubkey: loanKey, isSigner: false, isWritable: false}, @@ -22,7 +22,7 @@ export const invoke = async (loan: string, collateral: string) => { /* Prepare parameters */ let allocateStruct = { - index: 0, // Loan2Value is instruction#0 in the program + index: 0, // Loan2Value is instruction #0 in the program layout: struct([ u8('instruction'), ]) @@ -38,7 +38,7 @@ export const invoke = async (loan: string, collateral: string) => { }); tx.add(new web3.TransactionInstruction({ keys, - programId: "F6SbH5bQZ8peCqQUDmpYqhUtDJVMcAe9JGLPMLGAesfy", + programId: "CD9PFy8satZh27JeEixunWjBqoYbpbLbUtZ6eWPbT1s6", data })); @@ -46,7 +46,6 @@ export const invoke = async (loan: string, collateral: string) => { console.log("Confirmed TxHash: " + txSig); } -let eth = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; -let usdc = "5SSkXsEKQepHHAewytPVwdej4epN1nxgLVM84L4KXgy7"; -let usdt = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; -invoke(eth, usdt); +let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; +let usdtToUSD = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; +invoke(ethToUSD, usdtToUSD); diff --git a/examples/sol-contract/scripts/package.json b/examples/sol-contract/scripts/package.json new file mode 100644 index 0000000..ad4ad3a --- /dev/null +++ b/examples/sol-contract/scripts/package.json @@ -0,0 +1,18 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "", + "main": "invoke.js", + "scripts": { + "build": "npx tsc" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "typescript": "^4.8.3" + }, + "dependencies": { + "@solana/web3.js": "^1.56.2" + } +} diff --git a/examples/sol-contract/scripts/tsconfig.json b/examples/sol-contract/scripts/tsconfig.json new file mode 100644 index 0000000..f33e9f4 --- /dev/null +++ b/examples/sol-contract/scripts/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true, + "target": "es6", + "moduleResolution": "node", + "sourceMap": true + }, + "lib": ["es2015"] +} diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 22baa7e..c5ee918 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -18,18 +18,19 @@ pub fn process_instruction( let instruction = PythClientInstruction::try_from_slice(input).unwrap(); match instruction { PythClientInstruction::Loan2Value {} => { - // Suppose we have 1 loan token and 3000 collateral token + let loan = &_accounts[0]; + let collateral = &_accounts[1]; + msg!("The loan key is {}.", loan.key); + msg!("The collateral key is {}.", collateral.key); + + msg!("Assume 1 unit of loan and 3000 unit of collateral."); let loan_cnt = 1; let collateral_cnt = 3000; - let loan = &_accounts[0]; - msg!("The loan key is {}.", loan.key); let feed1 = load_price_feed_from_account_info(&loan).unwrap(); let result1 = feed1.get_current_price().unwrap(); let loan_value = result1.price * loan_cnt; - let collateral = &_accounts[1]; - msg!("The collateral key is {}.", collateral.key); let feed2 = load_price_feed_from_account_info(&collateral).unwrap(); let result2 = feed2.get_current_price().unwrap(); let collateral_value = result2.price * collateral_cnt; @@ -37,10 +38,10 @@ pub fn process_instruction( if collateral_value > loan_value { msg!("Loan unit price is {}.", result1.price); msg!("Collateral unit price is {}.", result2.price); - msg!("Collateral value is higher."); + msg!("The value of collateral is higher."); Ok(()) } else { - msg!("Loan value is higher!"); + msg!("The value of loan is higher!"); Err(ProgramError::Custom(0)) } } From 33c8afa015210a6b90acd62c5ae418c81f80f79a Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 21:02:06 -0400 Subject: [PATCH 34/69] Add readme in examples/sol-contract --- examples/sol-contract/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 examples/sol-contract/README.md diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md new file mode 100644 index 0000000..c507c59 --- /dev/null +++ b/examples/sol-contract/README.md @@ -0,0 +1,18 @@ +# Pyth SDK Example Contract for Solana + +This repository contains a simple example demonstrating how to read the Pyth price from the Pyth contract on Solana. + +The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. +The loan-to-value ratio is an important metric tracked by many lending protocols. +An example invocation of this contract on the Solana devnet can be find in `scripts/invoke.ts`. + +## Usage + +``` +# To build the example contract +> scripts/build.sh +# To deploy the example contract +> scripts/deploy.sh +# To invoke the example contract +> scripts/invoke.ts +``` From 969b8ff4eb256092a5c702b98f613d0f806f7a4b Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 21:26:34 -0400 Subject: [PATCH 35/69] Add overflow checks in examples/sol-contract --- examples/sol-contract/src/processor.rs | 31 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index c5ee918..cae2f91 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -18,6 +18,7 @@ pub fn process_instruction( let instruction = PythClientInstruction::try_from_slice(input).unwrap(); match instruction { PythClientInstruction::Loan2Value {} => { + // Parse the parameters and skip the necessary checks let loan = &_accounts[0]; let collateral = &_accounts[1]; msg!("The loan key is {}.", loan.key); @@ -26,23 +27,35 @@ pub fn process_instruction( msg!("Assume 1 unit of loan and 3000 unit of collateral."); let loan_cnt = 1; let collateral_cnt = 3000; - - let feed1 = load_price_feed_from_account_info(&loan).unwrap(); - let result1 = feed1.get_current_price().unwrap(); - let loan_value = result1.price * loan_cnt; - let feed2 = load_price_feed_from_account_info(&collateral).unwrap(); - let result2 = feed2.get_current_price().unwrap(); - let collateral_value = result2.price * collateral_cnt; + // Calculate the value of the loan + let loan_value; + let feed1 = load_price_feed_from_account_info(&loan); + let result1 = feed1.unwrap().get_current_price().unwrap(); + if let Some(v) = result1.price.checked_mul(loan_cnt) { + loan_value = v; + } else { + return Err(ProgramError::Custom(0)) + } + + // Calculate the value of the collateral + let collateral_value; + let feed2 = load_price_feed_from_account_info(&collateral); + let result2 = feed2.unwrap().get_current_price().unwrap(); + if let Some(v) = result2.price.checked_mul(collateral_cnt) { + collateral_value = v; + } else { + return Err(ProgramError::Custom(0)) + } if collateral_value > loan_value { msg!("Loan unit price is {}.", result1.price); msg!("Collateral unit price is {}.", result2.price); msg!("The value of collateral is higher."); - Ok(()) + return Ok(()) } else { msg!("The value of loan is higher!"); - Err(ProgramError::Custom(0)) + return Err(ProgramError::Custom(1)) } } } From 81a444223adc339bb45599c6cf0ffdf6b2dbaf08 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 21:41:43 -0400 Subject: [PATCH 36/69] Cleanup and add comments in examples/sol-contract --- examples/sol-contract/src/entrypoint.rs | 3 +++ examples/sol-contract/src/instruction.rs | 23 +++++------------------ examples/sol-contract/src/lib.rs | 7 ++----- examples/sol-contract/src/processor.rs | 13 +++++++++---- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/examples/sol-contract/src/entrypoint.rs b/examples/sol-contract/src/entrypoint.rs index e6fd7ba..6b151a3 100644 --- a/examples/sol-contract/src/entrypoint.rs +++ b/examples/sol-contract/src/entrypoint.rs @@ -5,6 +5,9 @@ use solana_program::pubkey::Pubkey; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; +// Every solana program has an entry point function with 3 parameters: +// the program ID, the accounts being touched by this program, +// and an arbitrary byte array as the input data for execution. entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index acb42f8..092bdcc 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -1,24 +1,11 @@ //! Program instructions -use crate::id; -use borsh::{ - BorshDeserialize, - BorshSerialize, -}; -use solana_program::instruction::Instruction; -use solana_program::instruction::AccountMeta; +use borsh::BorshSerialize; +use borsh::BorshDeserialize; +// A solana program contains a number of instructions. +// And this example contract contains only one instruction. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum PythClientInstruction { - Loan2Value {}, -} - -pub fn loan_to_value(loan: AccountMeta, collateral: AccountMeta) -> Instruction { - Instruction { - program_id: id(), - accounts: vec![loan, collateral], - data: PythClientInstruction::Loan2Value {} - .try_to_vec() - .unwrap(), - } + Loan2Value {}, // In this enum, Loan2Value is number 0. } diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs index 701f834..3ddeda9 100644 --- a/examples/sol-contract/src/lib.rs +++ b/examples/sol-contract/src/lib.rs @@ -1,8 +1,5 @@ +// This is the file being compiled to the bpf shared object (.so). +// It specifies the 3 modules of this example contract. pub mod processor; pub mod entrypoint; pub mod instruction; - -// This will only be used in local testing. -solana_program::declare_id!( - "PythBestPractice111111111111111111111111111" -); diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index cae2f91..b2fde31 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -15,39 +15,44 @@ pub fn process_instruction( _accounts: &[AccountInfo], input: &[u8], ) -> ProgramResult { - let instruction = PythClientInstruction::try_from_slice(input).unwrap(); - match instruction { + // Checking the validity of parameters is important. + // This checking step is skipped in this example contract. + let instruction = PythClientInstruction::try_from_slice(input); + match instruction.unwrap() { PythClientInstruction::Loan2Value {} => { - // Parse the parameters and skip the necessary checks let loan = &_accounts[0]; let collateral = &_accounts[1]; msg!("The loan key is {}.", loan.key); msg!("The collateral key is {}.", collateral.key); - msg!("Assume 1 unit of loan and 3000 unit of collateral."); let loan_cnt = 1; let collateral_cnt = 3000; // Calculate the value of the loan + // Pyth is called to get the unit price of the loan. let loan_value; let feed1 = load_price_feed_from_account_info(&loan); let result1 = feed1.unwrap().get_current_price().unwrap(); if let Some(v) = result1.price.checked_mul(loan_cnt) { loan_value = v; } else { + // An overflow occurs for result1.price * loan_cnt. return Err(ProgramError::Custom(0)) } // Calculate the value of the collateral + // Pyth is called to get the unit price of the collateral. let collateral_value; let feed2 = load_price_feed_from_account_info(&collateral); let result2 = feed2.unwrap().get_current_price().unwrap(); if let Some(v) = result2.price.checked_mul(collateral_cnt) { collateral_value = v; } else { + // An overflow occurs for result2.price * collateral_cnt. return Err(ProgramError::Custom(0)) } + // Check whether the value of the collateral is higher. if collateral_value > loan_value { msg!("Loan unit price is {}.", result1.price); msg!("Collateral unit price is {}.", result2.price); From ae4734917b0148ceae39cb6dbb0c0ec66d5c4dd4 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 21:52:34 -0400 Subject: [PATCH 37/69] Remove hard-coded contract hash in examples/sol-contract --- examples/sol-contract/scripts/invoke.sh | 2 +- examples/sol-contract/scripts/invoke.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.sh b/examples/sol-contract/scripts/invoke.sh index ef35dba..7c017ee 100755 --- a/examples/sol-contract/scripts/invoke.sh +++ b/examples/sol-contract/scripts/invoke.sh @@ -1 +1 @@ -cd scripts; npm install typescript; npm run build; node invoke.js +cd scripts; npm install typescript; npm run build; node invoke.js `solana-keygen pubkey ../build/example_sol_contract-keypair.json` diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 034c243..4113da2 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -32,18 +32,19 @@ export const invoke = async (loan: string, collateral: string) => { allocateStruct.layout.encode(layoutFields, data); /* Invoke transaction */ - console.log("Invoking transaction..."); let tx = new web3.Transaction({ feePayer: payer.publicKey }); + let contract = process.argv[2]; + console.log("Invoking contract " + contract + "..."); tx.add(new web3.TransactionInstruction({ keys, - programId: "CD9PFy8satZh27JeEixunWjBqoYbpbLbUtZ6eWPbT1s6", + programId: contract, data })); let txSig = await web3.sendAndConfirmTransaction(conn, tx, [payer, keypair]); - console.log("Confirmed TxHash: " + txSig); + console.log("Confirmed TxHash " + txSig); } let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; From dc4d163a090fa3aa50eb80b0432fde4142235f28 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 22:03:34 -0400 Subject: [PATCH 38/69] Improve comments wording in examples/sol-contract --- examples/sol-contract/README.md | 4 +++- examples/sol-contract/src/processor.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index c507c59..b448917 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -3,9 +3,11 @@ This repository contains a simple example demonstrating how to read the Pyth price from the Pyth contract on Solana. The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. -The loan-to-value ratio is an important metric tracked by many lending protocols. +This function compares the value of some loan and some collateral, which is important in many lending protocols. An example invocation of this contract on the Solana devnet can be find in `scripts/invoke.ts`. +We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and `node`. + ## Usage ``` diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index b2fde31..859e963 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -16,7 +16,7 @@ pub fn process_instruction( input: &[u8], ) -> ProgramResult { // Checking the validity of parameters is important. - // This checking step is skipped in this example contract. + // This important step is skipped in this example contract. let instruction = PythClientInstruction::try_from_slice(input); match instruction.unwrap() { PythClientInstruction::Loan2Value {} => { @@ -28,7 +28,7 @@ pub fn process_instruction( let loan_cnt = 1; let collateral_cnt = 3000; - // Calculate the value of the loan + // Calculate the value of the loan. // Pyth is called to get the unit price of the loan. let loan_value; let feed1 = load_price_feed_from_account_info(&loan); @@ -40,7 +40,7 @@ pub fn process_instruction( return Err(ProgramError::Custom(0)) } - // Calculate the value of the collateral + // Calculate the value of the collateral. // Pyth is called to get the unit price of the collateral. let collateral_value; let feed2 = load_price_feed_from_account_info(&collateral); From 908a3bc8c976bedc8fd3e3300e2b113192111d72 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 22:08:37 -0400 Subject: [PATCH 39/69] Update readme of examples/sol-contract --- examples/sol-contract/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index b448917..131cafc 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -10,7 +10,8 @@ We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and ## Usage -``` +```shell +> cd examples/sol-contract # To build the example contract > scripts/build.sh # To deploy the example contract From d2ccf1a90fe6e53649e0f0626ba573f68f6f8a31 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 13 Sep 2022 22:21:44 -0400 Subject: [PATCH 40/69] Remove some unnecessary dependencies in examples/sol-contract --- examples/sol-contract/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/sol-contract/Cargo.toml b/examples/sol-contract/Cargo.toml index 4e512ab..63e2d02 100644 --- a/examples/sol-contract/Cargo.toml +++ b/examples/sol-contract/Cargo.toml @@ -9,8 +9,6 @@ crate-type = ["cdylib", "lib"] [dependencies] borsh = "0.9" -bytemuck = "1.7.2" -borsh-derive = "0.9.0" pyth-sdk-solana = "0.5.0" solana-program = "1.8.1, < 1.11" From b718bb5cf127fd1d4cdfb251ad197b1a08bccb3f Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 14 Sep 2022 10:47:42 -0400 Subject: [PATCH 41/69] Remove the unwrap() in the code of examples/sol-contract --- examples/sol-contract/src/processor.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 859e963..218f56d 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -17,8 +17,8 @@ pub fn process_instruction( ) -> ProgramResult { // Checking the validity of parameters is important. // This important step is skipped in this example contract. - let instruction = PythClientInstruction::try_from_slice(input); - match instruction.unwrap() { + let instruction = PythClientInstruction::try_from_slice(input)?; + match instruction { PythClientInstruction::Loan2Value {} => { let loan = &_accounts[0]; let collateral = &_accounts[1]; @@ -31,25 +31,25 @@ pub fn process_instruction( // Calculate the value of the loan. // Pyth is called to get the unit price of the loan. let loan_value; - let feed1 = load_price_feed_from_account_info(&loan); - let result1 = feed1.unwrap().get_current_price().unwrap(); + let feed1 = load_price_feed_from_account_info(loan)?; + let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(0))?; if let Some(v) = result1.price.checked_mul(loan_cnt) { loan_value = v; } else { // An overflow occurs for result1.price * loan_cnt. - return Err(ProgramError::Custom(0)) + return Err(ProgramError::Custom(1)) } // Calculate the value of the collateral. // Pyth is called to get the unit price of the collateral. let collateral_value; - let feed2 = load_price_feed_from_account_info(&collateral); - let result2 = feed2.unwrap().get_current_price().unwrap(); + let feed2 = load_price_feed_from_account_info(collateral)?; + let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(0))?; if let Some(v) = result2.price.checked_mul(collateral_cnt) { collateral_value = v; } else { // An overflow occurs for result2.price * collateral_cnt. - return Err(ProgramError::Custom(0)) + return Err(ProgramError::Custom(1)) } // Check whether the value of the collateral is higher. @@ -60,7 +60,7 @@ pub fn process_instruction( return Ok(()) } else { msg!("The value of loan is higher!"); - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(2)) } } } From 063f20a3898f661a274af34541eb26eacdd3f3ca Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 14 Sep 2022 11:11:15 -0400 Subject: [PATCH 42/69] Fix a grammar error --- examples/sol-contract/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index 131cafc..047652b 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -4,7 +4,7 @@ This repository contains a simple example demonstrating how to read the Pyth pri The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. This function compares the value of some loan and some collateral, which is important in many lending protocols. -An example invocation of this contract on the Solana devnet can be find in `scripts/invoke.ts`. +An example invocation of this contract on the Solana devnet can be found in `scripts/invoke.ts`. We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and `node`. From a6f947301825f433586d2412b966bdcd79eaa521 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 22:32:29 -0400 Subject: [PATCH 43/69] Succeed in adding state.rs and an initialization instruction --- examples/sol-contract/Cargo.toml | 1 + examples/sol-contract/scripts/invoke.ts | 94 ++++++++++++++-------- examples/sol-contract/scripts/package.json | 2 +- examples/sol-contract/src/instruction.rs | 3 +- examples/sol-contract/src/lib.rs | 1 + examples/sol-contract/src/processor.rs | 89 +++++++++++++------- examples/sol-contract/src/state.rs | 69 ++++++++++++++++ 7 files changed, 192 insertions(+), 67 deletions(-) create mode 100644 examples/sol-contract/src/state.rs diff --git a/examples/sol-contract/Cargo.toml b/examples/sol-contract/Cargo.toml index 63e2d02..41e6b4e 100644 --- a/examples/sol-contract/Cargo.toml +++ b/examples/sol-contract/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib", "lib"] [dependencies] borsh = "0.9" +arrayref = "0.3.6" pyth-sdk-solana = "0.5.0" solana-program = "1.8.1, < 1.11" diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 4113da2..032d6c4 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,50 +1,74 @@ const web3 = require("@solana/web3.js"); -const {struct, u8} = require("@solana/buffer-layout"); +const {struct, b, u8, u32} = require("@solana/buffer-layout"); + +const admin = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) export const invoke = async (loan: string, collateral: string) => { - /* Airdrop */ - console.log("Airdroping..."); - let payer = web3.Keypair.generate(); - let keypair = web3.Keypair.generate(); + let contract = admin.publicKey; let conn = new web3.Connection(web3.clusterApiUrl('devnet')); + + /* Prepare the payer account */ + console.log("Airdropping..."); + let payer = web3.Keypair.generate(); let airdropSig = await conn.requestAirdrop( - payer.publicKey, - web3.LAMPORTS_PER_SOL, + payer.publicKey, web3.LAMPORTS_PER_SOL ); await conn.confirmTransaction(airdropSig); - /* Specify accounts being touched */ + /* createInst is an instruction creating an account storing the + * LoanInfo data, which will be passed to Init for initialization*/ + let sizeofLoanInfo = 1 + 32 + 8 + 32 + 8; + let dataAccount = web3.Keypair.generate(); + let cost = await conn.getMinimumBalanceForRentExemption(sizeofLoanInfo); + const createInst = web3.SystemProgram.createAccount({ + programId: contract, + fromPubkey: payer.publicKey, + space: sizeofLoanInfo, + newAccountPubkey: dataAccount.publicKey, + lamports: cost, + }); + + /* Specify accounts being touched by Init */ const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); - let keys = [{pubkey: loanKey, isSigner: false, isWritable: false}, + let keys = [{pubkey: contract, isSigner: true, isWritable: false}, + {pubkey: dataAccount.publicKey, isSigner: false, isWritable: false}, + {pubkey: loanKey, isSigner: false, isWritable: false}, {pubkey: collateralKey, isSigner: false, isWritable: false}, - {pubkey: keypair.publicKey, isSigner: true, isWritable: false}]; - - /* Prepare parameters */ - let allocateStruct = { - index: 0, // Loan2Value is instruction #0 in the program - layout: struct([ - u8('instruction'), - ]) - }; - let data = Buffer.alloc(allocateStruct.layout.span); - let layoutFields = Object.assign({instruction: allocateStruct.index}); - allocateStruct.layout.encode(layoutFields, data); - - /* Invoke transaction */ - let tx = new web3.Transaction({ - feePayer: payer.publicKey - }); - let contract = process.argv[2]; - console.log("Invoking contract " + contract + "..."); - tx.add(new web3.TransactionInstruction({ - keys, - programId: contract, - data - })); + ]; + + /* Prepare parameters for Invoking the contract */ + let dataLayout = struct([ u8('instruction') ]) + let data = Buffer.alloc(dataLayout.span); + dataLayout.encode(Object.assign({instruction: 1}), data); - let txSig = await web3.sendAndConfirmTransaction(conn, tx, [payer, keypair]); - console.log("Confirmed TxHash " + txSig); + /* Invoke the Init instruction */ + console.log("Creating data account and invoking Init..."); + let txInit = new web3.Transaction({ feePayer: payer.publicKey }); + txInit.add( + createInst, + new web3.TransactionInstruction({ + keys, + programId: contract, + data + }) + ); + let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, admin]); + console.log("TxHash: " + txInitSig); + + /* Invoke the Loan2Value instruction */ + dataLayout.encode(Object.assign({instruction: 0}), data); + console.log("Checking loan to value ratio..."); + let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); + txCheck.add( + new web3.TransactionInstruction({ + keys, + programId: contract, + data + }) + ); + let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, admin]); + console.log("TxHash: " + txCheckSig); } let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; diff --git a/examples/sol-contract/scripts/package.json b/examples/sol-contract/scripts/package.json index ad4ad3a..17a4921 100644 --- a/examples/sol-contract/scripts/package.json +++ b/examples/sol-contract/scripts/package.json @@ -10,7 +10,7 @@ "author": "", "license": "ISC", "devDependencies": { - "typescript": "^4.8.3" + "typescript": "^4.8.4" }, "dependencies": { "@solana/web3.js": "^1.56.2" diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index 092bdcc..12e19a5 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -7,5 +7,6 @@ use borsh::BorshDeserialize; // And this example contract contains only one instruction. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum PythClientInstruction { - Loan2Value {}, // In this enum, Loan2Value is number 0. + Loan2Value {}, // in this enum, Loan2Value is number 0 + Init{}, // and Init is 1 } diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs index 3ddeda9..0939b10 100644 --- a/examples/sol-contract/src/lib.rs +++ b/examples/sol-contract/src/lib.rs @@ -1,5 +1,6 @@ // This is the file being compiled to the bpf shared object (.so). // It specifies the 3 modules of this example contract. +pub mod state; pub mod processor; pub mod entrypoint; pub mod instruction; diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 218f56d..4208b6f 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -1,57 +1,86 @@ //! Program instruction processor + use solana_program::msg; use solana_program::pubkey::Pubkey; -use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; use solana_program::program_error::ProgramError; +use solana_program::program_pack::{IsInitialized, Pack}; +use solana_program::account_info::{next_account_info, AccountInfo}; use borsh::BorshDeserialize; -use crate::instruction::PythClientInstruction; use pyth_sdk_solana::load_price_feed_from_account_info; +use crate::state::LoanInfo; +use crate::instruction::PythClientInstruction; + pub fn process_instruction( _program_id: &Pubkey, _accounts: &[AccountInfo], input: &[u8], ) -> ProgramResult { - // Checking the validity of parameters is important. - // This important step is skipped in this example contract. + let account_iter = &mut _accounts.iter(); + let signer = next_account_info(account_iter)?; + let data_account = next_account_info(account_iter)?; + let pyth_loan_account = next_account_info(account_iter)?; + let pyth_collateral_account = next_account_info(account_iter)?; + let instruction = PythClientInstruction::try_from_slice(input)?; match instruction { - PythClientInstruction::Loan2Value {} => { - let loan = &_accounts[0]; - let collateral = &_accounts[1]; - msg!("The loan key is {}.", loan.key); - msg!("The collateral key is {}.", collateral.key); - msg!("Assume 1 unit of loan and 3000 unit of collateral."); - let loan_cnt = 1; - let collateral_cnt = 3000; + PythClientInstruction::Init {} => { + // Only the program admin can initialize a loan. + if !(signer.key == _program_id && signer.is_signer) { + return Err(ProgramError::Custom(1)) + } - // Calculate the value of the loan. - // Pyth is called to get the unit price of the loan. - let loan_value; - let feed1 = load_price_feed_from_account_info(loan)?; - let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(0))?; - if let Some(v) = result1.price.checked_mul(loan_cnt) { - loan_value = v; - } else { - // An overflow occurs for result1.price * loan_cnt. + let mut loan_info = LoanInfo::unpack_from_slice( + &data_account.try_borrow_data()?)?; + + if loan_info.is_initialized() { return Err(ProgramError::Custom(1)) } - // Calculate the value of the collateral. - // Pyth is called to get the unit price of the collateral. - let collateral_value; - let feed2 = load_price_feed_from_account_info(collateral)?; - let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(0))?; - if let Some(v) = result2.price.checked_mul(collateral_cnt) { - collateral_value = v; - } else { - // An overflow occurs for result2.price * collateral_cnt. + loan_info.is_initialized = true; + loan_info.loan_key = *pyth_loan_account.key; + loan_info.collateral_key = *pyth_collateral_account.key; + // Give some dummy numbers for simplicity of this example. + loan_info.loan_qty = 1; + loan_info.collateral_qty = 3000; + + msg!("The loan key is {}.", loan_info.loan_key); + msg!("The collateral key is {}.", loan_info.collateral_key); + msg!("Assume 1 unit of loan and 3000 unit of collateral."); + LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; + Ok(()) + }, + PythClientInstruction::Loan2Value {} => { + // Anyone can check the loan to value ratio. + let loan_info = LoanInfo::unpack_from_slice( + &data_account.try_borrow_data()?)?; + + if !loan_info.is_initialized() { return Err(ProgramError::Custom(1)) } + if loan_info.loan_key != *pyth_loan_account.key || + loan_info.collateral_key != *pyth_collateral_account.key { + return Err(ProgramError::Custom(1)) + } + + // Calculate the value of the loan using Pyth price. + let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; + let result1 = feed1.get_current_price() + .ok_or(ProgramError::Custom(0))?; + let loan_value = result1.price.checked_mul(loan_info.loan_qty) + .ok_or(ProgramError::Custom(0))?; + + // Calculate the value of the loan using Pyth price. + let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; + let result2 = feed2.get_current_price() + .ok_or(ProgramError::Custom(0))?; + let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) + .ok_or(ProgramError::Custom(1))?; + // Check whether the value of the collateral is higher. if collateral_value > loan_value { msg!("Loan unit price is {}.", result1.price); diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs new file mode 100644 index 0000000..d92617d --- /dev/null +++ b/examples/sol-contract/src/state.rs @@ -0,0 +1,69 @@ +use solana_program::{ + program_error::ProgramError, + program_pack::{IsInitialized, Pack, Sealed}, + pubkey::Pubkey, +}; + +use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; + +pub struct LoanInfo { + pub is_initialized: bool, + pub loan_key: Pubkey, + pub loan_qty: i64, + pub collateral_key: Pubkey, + pub collateral_qty: i64 +} + +impl Sealed for LoanInfo {} + +impl IsInitialized for LoanInfo { + fn is_initialized(&self) -> bool { + self.is_initialized + } +} + +impl Pack for LoanInfo { + const LEN: usize = 1 + 32 + 8 + 32 + 8; + fn unpack_from_slice(src: &[u8]) -> Result { + let src = array_ref![src, 0, LoanInfo::LEN]; + let ( + src_is_initialized, + src_loan_key, src_loan_qty, + src_collateral_key, src_collateral_qty, + ) = array_refs![src, 1, 32, 8, 32, 8]; + let is_initialized = match src_is_initialized { + [0] => false, + [1] => true, + _ => return Err(ProgramError::InvalidAccountData), + }; + + Ok(LoanInfo { + is_initialized, + loan_key: Pubkey::new_from_array(*src_loan_key), + loan_qty: i64::from_le_bytes(*src_loan_qty), + collateral_key: Pubkey::new_from_array(*src_collateral_key), + collateral_qty: i64::from_le_bytes(*src_collateral_qty), + }) + } + + fn pack_into_slice(&self, dst: &mut [u8]) { + let dst = array_mut_ref![dst, 0, LoanInfo::LEN]; + let ( + dst_is_initialized, + dst_loan_key, dst_loan_qty, + dst_collateral_key, dst_collateral_qty, + ) = mut_array_refs![dst, 1, 32, 8, 32, 8]; + + let LoanInfo { + is_initialized, + loan_key, loan_qty, + collateral_key, collateral_qty, + } = self; + + dst_is_initialized[0] = *is_initialized as u8; + dst_loan_key.copy_from_slice(loan_key.as_ref()); + *dst_loan_qty = loan_qty.to_le_bytes(); + dst_collateral_key.copy_from_slice(collateral_key.as_ref()); + *dst_collateral_qty = collateral_qty.to_le_bytes(); + } +} From 8f3f76d49390d5e93360fb41f4fd53e6e29b0726 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 22:59:40 -0400 Subject: [PATCH 44/69] Cleanup --- examples/sol-contract/scripts/invoke.ts | 77 ++++++++++++++++-------- examples/sol-contract/src/instruction.rs | 4 +- examples/sol-contract/src/processor.rs | 14 ++--- 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 032d6c4..756b679 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -4,71 +4,98 @@ const {struct, b, u8, u32} = require("@solana/buffer-layout"); const admin = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) export const invoke = async (loan: string, collateral: string) => { - let contract = admin.publicKey; let conn = new web3.Connection(web3.clusterApiUrl('devnet')); /* Prepare the payer account */ - console.log("Airdropping..."); + console.info("Airdropping..."); let payer = web3.Keypair.generate(); let airdropSig = await conn.requestAirdrop( payer.publicKey, web3.LAMPORTS_PER_SOL ); await conn.confirmTransaction(airdropSig); - /* createInst is an instruction creating an account storing the + /* Prepare the createInst instruction: Create an account to store the * LoanInfo data, which will be passed to Init for initialization*/ - let sizeofLoanInfo = 1 + 32 + 8 + 32 + 8; + let contract = admin.publicKey; + let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); - let cost = await conn.getMinimumBalanceForRentExemption(sizeofLoanInfo); + let cost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); const createInst = web3.SystemProgram.createAccount({ + lamports: cost, + space: loanInfoSize, programId: contract, fromPubkey: payer.publicKey, - space: sizeofLoanInfo, newAccountPubkey: dataAccount.publicKey, - lamports: cost, }); - /* Specify accounts being touched by Init */ + /* Specify the accounts and parameters for invocations */ + const dataKey = dataAccount.publicKey; const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); - let keys = [{pubkey: contract, isSigner: true, isWritable: false}, - {pubkey: dataAccount.publicKey, isSigner: false, isWritable: false}, - {pubkey: loanKey, isSigner: false, isWritable: false}, - {pubkey: collateralKey, isSigner: false, isWritable: false}, - ]; - - /* Prepare parameters for Invoking the contract */ + let accounts = + [{pubkey: contract, isSigner: true, isWritable: false}, + {pubkey: dataKey, isSigner: false, isWritable: false}, + {pubkey: loanKey, isSigner: false, isWritable: false}, + {pubkey: collateralKey, isSigner: false, isWritable: false}, + ]; let dataLayout = struct([ u8('instruction') ]) let data = Buffer.alloc(dataLayout.span); - dataLayout.encode(Object.assign({instruction: 1}), data); + dataLayout.encode(Object.assign({instruction: 0}), data); - /* Invoke the Init instruction */ + /* Invoke the Init instruction (instruction #0) */ console.log("Creating data account and invoking Init..."); let txInit = new web3.Transaction({ feePayer: payer.publicKey }); txInit.add( createInst, new web3.TransactionInstruction({ - keys, - programId: contract, - data + data: data, + keys: accounts, + programId: contract }) ); let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, admin]); console.log("TxHash: " + txInitSig); - /* Invoke the Loan2Value instruction */ - dataLayout.encode(Object.assign({instruction: 0}), data); + /* Invoke the Loan2Value instruction (instruction #1) */ console.log("Checking loan to value ratio..."); let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); + dataLayout.encode(Object.assign({instruction: 1}), data); txCheck.add( new web3.TransactionInstruction({ - keys, - programId: contract, - data + data: data, + keys: accounts, + programId: contract }) ); let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, admin]); console.log("TxHash: " + txCheckSig); + + /* Try to invoke the Init instruction without authority */ + console.log("Trying an unauthorized invocation of Init..."); + let attacker = web3.Keypair.generate(); + accounts[0].pubkey = attacker.publicKey + + let attackerDataAccount = web3.Keypair.generate(); + const attackerCreateInst = web3.SystemProgram.createAccount({ + lamports: cost, + space: loanInfoSize, + programId: contract, + fromPubkey: payer.publicKey, + newAccountPubkey: attackerDataAccount.publicKey, + }); + + let txAttacker = new web3.Transaction({ feePayer: payer.publicKey }); + dataLayout.encode(Object.assign({instruction: 0}), data); + txAttacker.add( + attackerCreateInst, + new web3.TransactionInstruction({ + data: data, + keys: accounts, + programId: contract + }) + ); + let txAttackerSig = await web3.sendAndConfirmTransaction(conn, txAttacker, [payer, attackerDataAccount, attacker]); + console.log("TxHash: " + txAttackerSig); } let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index 12e19a5..e0602f1 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -7,6 +7,6 @@ use borsh::BorshDeserialize; // And this example contract contains only one instruction. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum PythClientInstruction { - Loan2Value {}, // in this enum, Loan2Value is number 0 - Init{}, // and Init is 1 + Init{}, + Loan2Value {}, } diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 4208b6f..fba7f43 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -30,7 +30,7 @@ pub fn process_instruction( PythClientInstruction::Init {} => { // Only the program admin can initialize a loan. if !(signer.key == _program_id && signer.is_signer) { - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(0)) } let mut loan_info = LoanInfo::unpack_from_slice( @@ -64,22 +64,22 @@ pub fn process_instruction( if loan_info.loan_key != *pyth_loan_account.key || loan_info.collateral_key != *pyth_collateral_account.key { - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(2)) } // Calculate the value of the loan using Pyth price. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price() - .ok_or(ProgramError::Custom(0))?; + .ok_or(ProgramError::Custom(3))?; let loan_value = result1.price.checked_mul(loan_info.loan_qty) - .ok_or(ProgramError::Custom(0))?; + .ok_or(ProgramError::Custom(4))?; // Calculate the value of the loan using Pyth price. let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; let result2 = feed2.get_current_price() - .ok_or(ProgramError::Custom(0))?; + .ok_or(ProgramError::Custom(3))?; let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) - .ok_or(ProgramError::Custom(1))?; + .ok_or(ProgramError::Custom(4))?; // Check whether the value of the collateral is higher. if collateral_value > loan_value { @@ -89,7 +89,7 @@ pub fn process_instruction( return Ok(()) } else { msg!("The value of loan is higher!"); - return Err(ProgramError::Custom(2)) + return Err(ProgramError::Custom(5)) } } } From e9911f72e527444308fb21a76b5ea04a5059411a Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:13:07 -0400 Subject: [PATCH 45/69] Cleanup --- examples/sol-contract/scripts/invoke.ts | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 756b679..686ee88 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,13 +1,18 @@ const web3 = require("@solana/web3.js"); const {struct, b, u8, u32} = require("@solana/buffer-layout"); -const admin = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) +const contract = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) export const invoke = async (loan: string, collateral: string) => { + if (contract.publicKey != process.argv[2]) { + console.info("Please update the contract key pair in invoke.ts."); + return; + } + console.info("Invoking contract " + contract.publicKey); let conn = new web3.Connection(web3.clusterApiUrl('devnet')); /* Prepare the payer account */ - console.info("Airdropping..."); + console.info("Airdropping to payer account..."); let payer = web3.Keypair.generate(); let airdropSig = await conn.requestAirdrop( payer.publicKey, web3.LAMPORTS_PER_SOL @@ -16,14 +21,13 @@ export const invoke = async (loan: string, collateral: string) => { /* Prepare the createInst instruction: Create an account to store the * LoanInfo data, which will be passed to Init for initialization*/ - let contract = admin.publicKey; let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); let cost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); const createInst = web3.SystemProgram.createAccount({ lamports: cost, space: loanInfoSize, - programId: contract, + programId: contract.publicKey, fromPubkey: payer.publicKey, newAccountPubkey: dataAccount.publicKey, }); @@ -33,7 +37,7 @@ export const invoke = async (loan: string, collateral: string) => { const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); let accounts = - [{pubkey: contract, isSigner: true, isWritable: false}, + [{pubkey: contract.publicKey, isSigner: true, isWritable: false}, {pubkey: dataKey, isSigner: false, isWritable: false}, {pubkey: loanKey, isSigner: false, isWritable: false}, {pubkey: collateralKey, isSigner: false, isWritable: false}, @@ -50,10 +54,10 @@ export const invoke = async (loan: string, collateral: string) => { new web3.TransactionInstruction({ data: data, keys: accounts, - programId: contract + programId: contract.publicKey }) ); - let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, admin]); + let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, contract]); console.log("TxHash: " + txInitSig); /* Invoke the Loan2Value instruction (instruction #1) */ @@ -64,10 +68,10 @@ export const invoke = async (loan: string, collateral: string) => { new web3.TransactionInstruction({ data: data, keys: accounts, - programId: contract + programId: contract.publicKey }) ); - let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, admin]); + let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, contract]); console.log("TxHash: " + txCheckSig); /* Try to invoke the Init instruction without authority */ @@ -79,7 +83,7 @@ export const invoke = async (loan: string, collateral: string) => { const attackerCreateInst = web3.SystemProgram.createAccount({ lamports: cost, space: loanInfoSize, - programId: contract, + programId: contract.publicKey, fromPubkey: payer.publicKey, newAccountPubkey: attackerDataAccount.publicKey, }); @@ -91,7 +95,7 @@ export const invoke = async (loan: string, collateral: string) => { new web3.TransactionInstruction({ data: data, keys: accounts, - programId: contract + programId: contract.publicKey }) ); let txAttackerSig = await web3.sendAndConfirmTransaction(conn, txAttacker, [payer, attackerDataAccount, attacker]); From 0c62d7f79b370282291c8d59e243c9b1a84642e9 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:17:49 -0400 Subject: [PATCH 46/69] Cleanup --- examples/sol-contract/scripts/invoke.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 686ee88..31b2b79 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -5,7 +5,7 @@ const contract = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100 export const invoke = async (loan: string, collateral: string) => { if (contract.publicKey != process.argv[2]) { - console.info("Please update the contract key pair in invoke.ts."); + console.info("Please update the contract keypair in invoke.ts with build/example_sol_contract-keypair.json."); return; } console.info("Invoking contract " + contract.publicKey); @@ -20,7 +20,7 @@ export const invoke = async (loan: string, collateral: string) => { await conn.confirmTransaction(airdropSig); /* Prepare the createInst instruction: Create an account to store the - * LoanInfo data, which will be passed to Init for initialization*/ + * LoanInfo data, which will be passed to Init for initialization */ let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); let cost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); From 8ceb10dc012d767e5db3735f4463e8ef0e34a2cd Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:53:48 -0400 Subject: [PATCH 47/69] Add confidence interval --- examples/sol-contract/src/processor.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index fba7f43..a3b11cb 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -47,9 +47,6 @@ pub fn process_instruction( loan_info.loan_qty = 1; loan_info.collateral_qty = 3000; - msg!("The loan key is {}.", loan_info.loan_key); - msg!("The collateral key is {}.", loan_info.collateral_key); - msg!("Assume 1 unit of loan and 3000 unit of collateral."); LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) }, @@ -67,28 +64,36 @@ pub fn process_instruction( return Err(ProgramError::Custom(2)) } - // Calculate the value of the loan using Pyth price. + // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price() .ok_or(ProgramError::Custom(3))?; let loan_value = result1.price.checked_mul(loan_info.loan_qty) .ok_or(ProgramError::Custom(4))?; + let loan_conf = (result1.conf as f64) + * (10 as f64).powf(result1.expo as f64) + * (loan_info.loan_qty as f64); + let loan_value_max = loan_value as f64 + loan_conf; - // Calculate the value of the loan using Pyth price. + // Calculate the minimum value of the collateral using Pyth. let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; let result2 = feed2.get_current_price() .ok_or(ProgramError::Custom(3))?; let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) .ok_or(ProgramError::Custom(4))?; + let collateral_conf = (result2.conf as f64) + * (10 as f64).powf(result2.expo as f64) + * (loan_info.collateral_qty as f64); + let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. - if collateral_value > loan_value { - msg!("Loan unit price is {}.", result1.price); - msg!("Collateral unit price is {}.", result2.price); - msg!("The value of collateral is higher."); + msg!("The maximum loan value is {}.", loan_value_max); + msg!("The minimum collateral value is {}.", collateral_value_min); + if collateral_value_min > loan_value_max { + msg!("The value of the collateral is higher."); return Ok(()) } else { - msg!("The value of loan is higher!"); + msg!("The value of the loan is higher!"); return Err(ProgramError::Custom(5)) } } From 0ffe5d5edab575ad33a837c3fd305ea6450c3cd9 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:56:12 -0400 Subject: [PATCH 48/69] Minor --- examples/sol-contract/scripts/invoke.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 31b2b79..b6fa6d7 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -98,6 +98,7 @@ export const invoke = async (loan: string, collateral: string) => { programId: contract.publicKey }) ); + /* The code should crash here with custom program error 0 */ let txAttackerSig = await web3.sendAndConfirmTransaction(conn, txAttacker, [payer, attackerDataAccount, attacker]); console.log("TxHash: " + txAttackerSig); } From 633f266eae1999e722116913ebef4b4b94526d6f Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 2 Oct 2022 23:57:54 -0400 Subject: [PATCH 49/69] Minor --- examples/sol-contract/scripts/invoke.ts | 6 +++--- examples/sol-contract/src/processor.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index b6fa6d7..df48371 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -23,9 +23,9 @@ export const invoke = async (loan: string, collateral: string) => { * LoanInfo data, which will be passed to Init for initialization */ let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); - let cost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); + let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); const createInst = web3.SystemProgram.createAccount({ - lamports: cost, + lamports: dataCost, space: loanInfoSize, programId: contract.publicKey, fromPubkey: payer.publicKey, @@ -81,7 +81,7 @@ export const invoke = async (loan: string, collateral: string) => { let attackerDataAccount = web3.Keypair.generate(); const attackerCreateInst = web3.SystemProgram.createAccount({ - lamports: cost, + lamports: dataCost, space: loanInfoSize, programId: contract.publicKey, fromPubkey: payer.publicKey, diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index a3b11cb..8dea473 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -1,6 +1,5 @@ //! Program instruction processor - use solana_program::msg; use solana_program::pubkey::Pubkey; use solana_program::entrypoint::ProgramResult; From a695622221fd31fc490bad97e31918bca2909ce5 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 09:03:46 -0400 Subject: [PATCH 50/69] Cleanup the contract --- examples/sol-contract/src/entrypoint.rs | 6 +++--- examples/sol-contract/src/instruction.rs | 8 +++++--- examples/sol-contract/src/lib.rs | 4 ++-- examples/sol-contract/src/processor.rs | 26 ++++++++++++------------ examples/sol-contract/src/state.rs | 11 +++++++--- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/examples/sol-contract/src/entrypoint.rs b/examples/sol-contract/src/entrypoint.rs index 6b151a3..8d29e4a 100644 --- a/examples/sol-contract/src/entrypoint.rs +++ b/examples/sol-contract/src/entrypoint.rs @@ -1,13 +1,13 @@ //! Program entrypoint +//! Every solana program has an entry point function with 3 parameters: +//! the program ID, the accounts being touched by this program, +//! and an arbitrary byte array as the input data for execution. use solana_program::entrypoint; use solana_program::pubkey::Pubkey; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; -// Every solana program has an entry point function with 3 parameters: -// the program ID, the accounts being touched by this program, -// and an arbitrary byte array as the input data for execution. entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index e0602f1..554bab1 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -1,12 +1,14 @@ //! Program instructions +//! A solana program contains a number of instructions. +//! There are 2 instructions in this example: +//! Init{} initializing some loan information, +//! Loan2Value{} checking the loan-to-value ratio of the loan. use borsh::BorshSerialize; use borsh::BorshDeserialize; -// A solana program contains a number of instructions. -// And this example contract contains only one instruction. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] -pub enum PythClientInstruction { +pub enum ExampleInstructions { Init{}, Loan2Value {}, } diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs index 0939b10..0e45b8a 100644 --- a/examples/sol-contract/src/lib.rs +++ b/examples/sol-contract/src/lib.rs @@ -1,5 +1,5 @@ -// This is the file being compiled to the bpf shared object (.so). -// It specifies the 3 modules of this example contract. +//! This file specifies the 4 modules of this example contract. + pub mod state; pub mod processor; pub mod entrypoint; diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 8dea473..b51b051 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -1,17 +1,19 @@ //! Program instruction processor +//! Only the program admin can issue the Init instruction. +//! And anyone can check the loan with the Loan2Value instruction. use solana_program::msg; use solana_program::pubkey::Pubkey; use solana_program::entrypoint::ProgramResult; use solana_program::program_error::ProgramError; use solana_program::program_pack::{IsInitialized, Pack}; -use solana_program::account_info::{next_account_info, AccountInfo}; +use solana_program::account_info::{AccountInfo, next_account_info}; use borsh::BorshDeserialize; use pyth_sdk_solana::load_price_feed_from_account_info; use crate::state::LoanInfo; -use crate::instruction::PythClientInstruction; +use crate::instruction::ExampleInstructions; pub fn process_instruction( _program_id: &Pubkey, @@ -24,10 +26,9 @@ pub fn process_instruction( let pyth_loan_account = next_account_info(account_iter)?; let pyth_collateral_account = next_account_info(account_iter)?; - let instruction = PythClientInstruction::try_from_slice(input)?; + let instruction = ExampleInstructions::try_from_slice(input)?; match instruction { - PythClientInstruction::Init {} => { - // Only the program admin can initialize a loan. + ExampleInstructions::Init {} => { if !(signer.key == _program_id && signer.is_signer) { return Err(ProgramError::Custom(0)) } @@ -49,8 +50,7 @@ pub fn process_instruction( LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) }, - PythClientInstruction::Loan2Value {} => { - // Anyone can check the loan to value ratio. + ExampleInstructions::Loan2Value {} => { let loan_info = LoanInfo::unpack_from_slice( &data_account.try_borrow_data()?)?; @@ -69,9 +69,9 @@ pub fn process_instruction( .ok_or(ProgramError::Custom(3))?; let loan_value = result1.price.checked_mul(loan_info.loan_qty) .ok_or(ProgramError::Custom(4))?; - let loan_conf = (result1.conf as f64) - * (10 as f64).powf(result1.expo as f64) - * (loan_info.loan_qty as f64); + let loan_conf = (result1.conf as f64) // confidence + * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent + * (loan_info.loan_qty as f64); // * quantity let loan_value_max = loan_value as f64 + loan_conf; // Calculate the minimum value of the collateral using Pyth. @@ -80,9 +80,9 @@ pub fn process_instruction( .ok_or(ProgramError::Custom(3))?; let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) .ok_or(ProgramError::Custom(4))?; - let collateral_conf = (result2.conf as f64) - * (10 as f64).powf(result2.expo as f64) - * (loan_info.collateral_qty as f64); + let collateral_conf = (result2.conf as f64) // confidence + * (10 as f64).powf(result2.expo as f64) // * 10 ^ exponent + * (loan_info.collateral_qty as f64); // * quantity let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index d92617d..cc86e72 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -1,15 +1,19 @@ +//! Program states +//! A data account would store a LoanInfo structure for the instructions. +//! This file contains the serialization and deserialization of LoanInfo. + use solana_program::{ + pubkey::Pubkey, program_error::ProgramError, program_pack::{IsInitialized, Pack, Sealed}, - pubkey::Pubkey, }; use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; pub struct LoanInfo { pub is_initialized: bool, - pub loan_key: Pubkey, - pub loan_qty: i64, + pub loan_key: Pubkey, + pub loan_qty: i64, pub collateral_key: Pubkey, pub collateral_qty: i64 } @@ -31,6 +35,7 @@ impl Pack for LoanInfo { src_loan_key, src_loan_qty, src_collateral_key, src_collateral_qty, ) = array_refs![src, 1, 32, 8, 32, 8]; + let is_initialized = match src_is_initialized { [0] => false, [1] => true, From 5e6d07540c5412214d62661cee4ac9f3302b7da9 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 09:44:27 -0400 Subject: [PATCH 51/69] Cleanup the client-side code --- examples/sol-contract/scripts/invoke.sh | 2 +- examples/sol-contract/scripts/invoke.ts | 55 +++++++++++++++++-------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.sh b/examples/sol-contract/scripts/invoke.sh index 7c017ee..ef35dba 100755 --- a/examples/sol-contract/scripts/invoke.sh +++ b/examples/sol-contract/scripts/invoke.sh @@ -1 +1 @@ -cd scripts; npm install typescript; npm run build; node invoke.js `solana-keygen pubkey ../build/example_sol_contract-keypair.json` +cd scripts; npm install typescript; npm run build; node invoke.js diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index df48371..82dc1a7 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,26 +1,34 @@ +const fs = require('fs'); const web3 = require("@solana/web3.js"); const {struct, b, u8, u32} = require("@solana/buffer-layout"); -const contract = web3.Keypair.fromSecretKey(new Uint8Array([223,143,8,70,205,100,4,197,222,158,132,43,89,182,188,243,24,213,136,120,189,209,235,13,167,45,132,41,17,243,58,158,114,230,85,178,27,22,80,213,200,96,166,64,152,163,191,112,35,197,55,219,24,254,117,129,227,39,37,232,106,30,178,193])) - export const invoke = async (loan: string, collateral: string) => { - if (contract.publicKey != process.argv[2]) { - console.info("Please update the contract keypair in invoke.ts with build/example_sol_contract-keypair.json."); + /* Obtain the contract keypair */ + var contract; + try { + let data = fs.readFileSync( + '../build/example_sol_contract-keypair.json' + ); + contract = web3.Keypair.fromSecretKey( + new Uint8Array(JSON.parse(data)) + ); + console.info("Invoking contract " + contract.publicKey); + } catch (error) { + console.error("Please run scripts/build.sh first."); return; } - console.info("Invoking contract " + contract.publicKey); - let conn = new web3.Connection(web3.clusterApiUrl('devnet')); /* Prepare the payer account */ - console.info("Airdropping to payer account..."); + let conn = new web3.Connection(web3.clusterApiUrl('devnet')); + console.info("Airdropping to the payer account..."); let payer = web3.Keypair.generate(); let airdropSig = await conn.requestAirdrop( payer.publicKey, web3.LAMPORTS_PER_SOL ); await conn.confirmTransaction(airdropSig); - /* Prepare the createInst instruction: Create an account to store the - * LoanInfo data, which will be passed to Init for initialization */ + /* Prepare the createInst instruction which creates an + * account storing the LoanInfo data for the instructions */ let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); @@ -32,7 +40,7 @@ export const invoke = async (loan: string, collateral: string) => { newAccountPubkey: dataAccount.publicKey, }); - /* Specify the accounts and parameters for invocations */ + /* Prepare the accounts and instruction data for transactions */ const dataKey = dataAccount.publicKey; const loanKey = new web3.PublicKey(loan); const collateralKey = new web3.PublicKey(collateral); @@ -44,20 +52,22 @@ export const invoke = async (loan: string, collateral: string) => { ]; let dataLayout = struct([ u8('instruction') ]) let data = Buffer.alloc(dataLayout.span); - dataLayout.encode(Object.assign({instruction: 0}), data); /* Invoke the Init instruction (instruction #0) */ console.log("Creating data account and invoking Init..."); let txInit = new web3.Transaction({ feePayer: payer.publicKey }); + dataLayout.encode(Object.assign({instruction: 0}), data); txInit.add( - createInst, - new web3.TransactionInstruction({ + createInst, /* Create data account */ + new web3.TransactionInstruction({ /* Initialize data account */ data: data, keys: accounts, programId: contract.publicKey }) ); - let txInitSig = await web3.sendAndConfirmTransaction(conn, txInit, [payer, dataAccount, contract]); + let txInitSig = await web3.sendAndConfirmTransaction( + conn, txInit, [payer, dataAccount, contract] + ); console.log("TxHash: " + txInitSig); /* Invoke the Loan2Value instruction (instruction #1) */ @@ -71,7 +81,9 @@ export const invoke = async (loan: string, collateral: string) => { programId: contract.publicKey }) ); - let txCheckSig = await web3.sendAndConfirmTransaction(conn, txCheck, [payer, contract]); + let txCheckSig = await web3.sendAndConfirmTransaction( + conn, txCheck, [payer, contract] + ); console.log("TxHash: " + txCheckSig); /* Try to invoke the Init instruction without authority */ @@ -98,11 +110,18 @@ export const invoke = async (loan: string, collateral: string) => { programId: contract.publicKey }) ); - /* The code should crash here with custom program error 0 */ - let txAttackerSig = await web3.sendAndConfirmTransaction(conn, txAttacker, [payer, attackerDataAccount, attacker]); - console.log("TxHash: " + txAttackerSig); + + try { + let txAttackerSig = await web3.sendAndConfirmTransaction( + conn, txAttacker, [payer, attackerDataAccount, attacker] + ); + console.error("Attacker succeeded with TxHash: " + txAttackerSig); + } catch (error) { + console.log("Attacker failed to invoke unauthorized Init."); + } } +/* Pyth price accounts on the solana devnet */ let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; let usdtToUSD = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; invoke(ethToUSD, usdtToUSD); From 5093ca1096896526630e4217ec637a5baa6742f0 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 17:17:30 -0400 Subject: [PATCH 52/69] Cleanup the contract code --- examples/sol-contract/README.md | 4 +- examples/sol-contract/src/entrypoint.rs | 10 ++-- examples/sol-contract/src/instruction.rs | 10 ++-- examples/sol-contract/src/lib.rs | 6 +-- examples/sol-contract/src/processor.rs | 61 +++++++++++++----------- examples/sol-contract/src/state.rs | 40 +++++++++++----- 6 files changed, 77 insertions(+), 54 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index 047652b..ab601e3 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -2,12 +2,14 @@ This repository contains a simple example demonstrating how to read the Pyth price from the Pyth contract on Solana. -The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. +The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. This function compares the value of some loan and some collateral, which is important in many lending protocols. An example invocation of this contract on the Solana devnet can be found in `scripts/invoke.ts`. We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and `node`. + + ## Usage ```shell diff --git a/examples/sol-contract/src/entrypoint.rs b/examples/sol-contract/src/entrypoint.rs index 8d29e4a..77591b9 100644 --- a/examples/sol-contract/src/entrypoint.rs +++ b/examples/sol-contract/src/entrypoint.rs @@ -1,12 +1,12 @@ //! Program entrypoint //! Every solana program has an entry point function with 3 parameters: //! the program ID, the accounts being touched by this program, -//! and an arbitrary byte array as the input data for execution. +//! and a byte array as the instruction data. -use solana_program::entrypoint; -use solana_program::pubkey::Pubkey; use solana_program::account_info::AccountInfo; +use solana_program::entrypoint; use solana_program::entrypoint::ProgramResult; +use solana_program::pubkey::Pubkey; entrypoint!(process_instruction); fn process_instruction( @@ -14,7 +14,5 @@ fn process_instruction( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - crate::processor::process_instruction( - program_id, accounts, instruction_data - ) + crate::processor::process_instruction(program_id, accounts, instruction_data) } diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index 554bab1..db553df 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -1,14 +1,16 @@ //! Program instructions //! A solana program contains a number of instructions. //! There are 2 instructions in this example: -//! Init{} initializing some loan information, +//! Init{} initializing some loan information and //! Loan2Value{} checking the loan-to-value ratio of the loan. -use borsh::BorshSerialize; -use borsh::BorshDeserialize; +use borsh::{ + BorshDeserialize, + BorshSerialize, +}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum ExampleInstructions { - Init{}, + Init {}, Loan2Value {}, } diff --git a/examples/sol-contract/src/lib.rs b/examples/sol-contract/src/lib.rs index 0e45b8a..6924718 100644 --- a/examples/sol-contract/src/lib.rs +++ b/examples/sol-contract/src/lib.rs @@ -1,6 +1,6 @@ -//! This file specifies the 4 modules of this example contract. +//! This file specifies the 4 modules of this example program. -pub mod state; -pub mod processor; pub mod entrypoint; pub mod instruction; +pub mod processor; +pub mod state; diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index b51b051..9e51184 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -2,18 +2,24 @@ //! Only the program admin can issue the Init instruction. //! And anyone can check the loan with the Loan2Value instruction. -use solana_program::msg; -use solana_program::pubkey::Pubkey; +use solana_program::account_info::{ + next_account_info, + AccountInfo, +}; use solana_program::entrypoint::ProgramResult; +use solana_program::msg; use solana_program::program_error::ProgramError; -use solana_program::program_pack::{IsInitialized, Pack}; -use solana_program::account_info::{AccountInfo, next_account_info}; +use solana_program::program_pack::{ + IsInitialized, + Pack, +}; +use solana_program::pubkey::Pubkey; use borsh::BorshDeserialize; use pyth_sdk_solana::load_price_feed_from_account_info; -use crate::state::LoanInfo; use crate::instruction::ExampleInstructions; +use crate::state::LoanInfo; pub fn process_instruction( _program_id: &Pubkey, @@ -30,14 +36,13 @@ pub fn process_instruction( match instruction { ExampleInstructions::Init {} => { if !(signer.key == _program_id && signer.is_signer) { - return Err(ProgramError::Custom(0)) + return Err(ProgramError::Custom(0)); } - let mut loan_info = LoanInfo::unpack_from_slice( - &data_account.try_borrow_data()?)?; + let mut loan_info = LoanInfo::unpack_from_slice(&data_account.try_borrow_data()?)?; if loan_info.is_initialized() { - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(1)); } loan_info.is_initialized = true; @@ -49,40 +54,42 @@ pub fn process_instruction( LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) - }, + } ExampleInstructions::Loan2Value {} => { - let loan_info = LoanInfo::unpack_from_slice( - &data_account.try_borrow_data()?)?; + let loan_info = LoanInfo::unpack_from_slice(&data_account.try_borrow_data()?)?; if !loan_info.is_initialized() { - return Err(ProgramError::Custom(1)) + return Err(ProgramError::Custom(1)); } - if loan_info.loan_key != *pyth_loan_account.key || - loan_info.collateral_key != *pyth_collateral_account.key { - return Err(ProgramError::Custom(2)) - } + if loan_info.loan_key != *pyth_loan_account.key + || loan_info.collateral_key != *pyth_collateral_account.key + { + return Err(ProgramError::Custom(2)); + } // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; - let result1 = feed1.get_current_price() - .ok_or(ProgramError::Custom(3))?; - let loan_value = result1.price.checked_mul(loan_info.loan_qty) + let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; + let loan_value = result1 + .price + .checked_mul(loan_info.loan_qty) .ok_or(ProgramError::Custom(4))?; let loan_conf = (result1.conf as f64) // confidence * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent - * (loan_info.loan_qty as f64); // * quantity + * (loan_info.loan_qty as f64); // * quantity let loan_value_max = loan_value as f64 + loan_conf; // Calculate the minimum value of the collateral using Pyth. let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; - let result2 = feed2.get_current_price() - .ok_or(ProgramError::Custom(3))?; - let collateral_value = result2.price.checked_mul(loan_info.collateral_qty) + let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(3))?; + let collateral_value = result2 + .price + .checked_mul(loan_info.collateral_qty) .ok_or(ProgramError::Custom(4))?; let collateral_conf = (result2.conf as f64) // confidence * (10 as f64).powf(result2.expo as f64) // * 10 ^ exponent - * (loan_info.collateral_qty as f64); // * quantity + * (loan_info.collateral_qty as f64); // * quantity let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. @@ -90,10 +97,10 @@ pub fn process_instruction( msg!("The minimum collateral value is {}.", collateral_value_min); if collateral_value_min > loan_value_max { msg!("The value of the collateral is higher."); - return Ok(()) + return Ok(()); } else { msg!("The value of the loan is higher!"); - return Err(ProgramError::Custom(5)) + return Err(ProgramError::Custom(5)); } } } diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index cc86e72..8fd892f 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -2,23 +2,31 @@ //! A data account would store a LoanInfo structure for the instructions. //! This file contains the serialization and deserialization of LoanInfo. -use solana_program::{ - pubkey::Pubkey, - program_error::ProgramError, - program_pack::{IsInitialized, Pack, Sealed}, +use solana_program::program_error::ProgramError; +use solana_program::program_pack::{ + IsInitialized, + Pack, + Sealed, }; +use solana_program::pubkey::Pubkey; -use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}; +use arrayref::{ + array_mut_ref, + array_ref, + array_refs, + mut_array_refs, +}; pub struct LoanInfo { pub is_initialized: bool, pub loan_key: Pubkey, pub loan_qty: i64, pub collateral_key: Pubkey, - pub collateral_qty: i64 + pub collateral_qty: i64, } -impl Sealed for LoanInfo {} +impl Sealed for LoanInfo { +} impl IsInitialized for LoanInfo { fn is_initialized(&self) -> bool { @@ -32,8 +40,10 @@ impl Pack for LoanInfo { let src = array_ref![src, 0, LoanInfo::LEN]; let ( src_is_initialized, - src_loan_key, src_loan_qty, - src_collateral_key, src_collateral_qty, + src_loan_key, + src_loan_qty, + src_collateral_key, + src_collateral_qty, ) = array_refs![src, 1, 32, 8, 32, 8]; let is_initialized = match src_is_initialized { @@ -55,14 +65,18 @@ impl Pack for LoanInfo { let dst = array_mut_ref![dst, 0, LoanInfo::LEN]; let ( dst_is_initialized, - dst_loan_key, dst_loan_qty, - dst_collateral_key, dst_collateral_qty, + dst_loan_key, + dst_loan_qty, + dst_collateral_key, + dst_collateral_qty, ) = mut_array_refs![dst, 1, 32, 8, 32, 8]; let LoanInfo { is_initialized, - loan_key, loan_qty, - collateral_key, collateral_qty, + loan_key, + loan_qty, + collateral_key, + collateral_qty, } = self; dst_is_initialized[0] = *is_initialized as u8; From 38867b612e1694f42fe7d66fc755c62a3a4aa5ef Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 17:54:06 -0400 Subject: [PATCH 53/69] Update readme --- examples/sol-contract/README.md | 47 ++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index ab601e3..6d3aa30 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -1,23 +1,50 @@ -# Pyth SDK Example Contract for Solana +# Pyth SDK Example Program for Solana -This repository contains a simple example demonstrating how to read the Pyth price from the Pyth contract on Solana. +This is an example demonstrating how to read prices from Pyth on Solana. -The key functionality of this contract is in the `Loan2Value` function in `src/processor.rs`. -This function compares the value of some loan and some collateral, which is important in many lending protocols. -An example invocation of this contract on the Solana devnet can be found in `scripts/invoke.ts`. +The program has two instructions: `Init` and `Loan2Value`. +`Init` can *only* be invoked by the program admin and it will initialize some loan information. +`Loan2Value` can be invoked by anyone and it uses the current Pyth price to compare the value of the loan and the value of the collateral. +This is an important functionality in many lending protocols. -We assume that you have installed `cargo`, `solana`, `solana-keygen`, `npm` and `node`. +The key program logic is in 3 files. +The loan information structure is defined in `src/state.rs`, which also contains the serialization and deserialization code. +The two instructions are implemented in `src/processor.rs`. +An example invocation of these instructions on the Solana devnet can be found in `scripts/invoke.ts`. +## Where and how the Pyth SDK is used? +Pyth SDK is used in the `Loan2Value` instruction in `src/processor.rs`. +For the loan, the code first reads the unit price from the Pyth oracle. +```rust +let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; +let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; +``` + +And then calculate the loan value given the quantity of the loan. +```rust +let loan_value = result1 + .price + .checked_mul(loan_info.loan_qty) + .ok_or(ProgramError::Custom(4))?; +let loan_conf = (result1.conf as f64) // confidence + * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent + * (loan_info.loan_qty as f64); // * quantity +let loan_value_max = loan_value as f64 + loan_conf; +``` +This code says that, with high confidence, the maximum value of the loan does not exceed `loan_value_max`. +In a similar way, the code then calculates the minimum value of the collateral and compare the two. -## Usage +## Run this example program +We assume that you have installed `cargo`, `solana`, `npm` and `node`. ```shell +# Enter the root directory of this example > cd examples/sol-contract -# To build the example contract +# Build the example contract > scripts/build.sh -# To deploy the example contract +# Deploy the example contract > scripts/deploy.sh -# To invoke the example contract +# Invoke the example contract > scripts/invoke.ts ``` From 26ed7351e6540ed7fd60d2d37f664754f5f21435 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 18:06:51 -0400 Subject: [PATCH 54/69] Minor --- examples/sol-contract/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index 6d3aa30..e067a5e 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -35,7 +35,7 @@ let loan_value_max = loan_value as f64 + loan_conf; This code says that, with high confidence, the maximum value of the loan does not exceed `loan_value_max`. In a similar way, the code then calculates the minimum value of the collateral and compare the two. -## Run this example program +## Run this program We assume that you have installed `cargo`, `solana`, `npm` and `node`. ```shell From 3406cff9aaa8f306cf35632d3b5a2ff1b20d86b0 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 3 Oct 2022 18:13:15 -0400 Subject: [PATCH 55/69] Minor --- examples/sol-contract/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index e067a5e..fa123a3 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -12,7 +12,7 @@ The loan information structure is defined in `src/state.rs`, which also contains The two instructions are implemented in `src/processor.rs`. An example invocation of these instructions on the Solana devnet can be found in `scripts/invoke.ts`. -## Where and how the Pyth SDK is used? +## Where and how is the Pyth SDK used? Pyth SDK is used in the `Loan2Value` instruction in `src/processor.rs`. For the loan, the code first reads the unit price from the Pyth oracle. ```rust From b185b2c2bccdc69aa366326899b97a5374a622b8 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 4 Oct 2022 19:41:21 -0400 Subject: [PATCH 56/69] Add package-lock.json --- examples/sol-contract/.gitignore | 1 - .../sol-contract/scripts/package-lock.json | 928 ++++++++++++++++++ 2 files changed, 928 insertions(+), 1 deletion(-) create mode 100644 examples/sol-contract/scripts/package-lock.json diff --git a/examples/sol-contract/.gitignore b/examples/sol-contract/.gitignore index 9a3314f..85faf92 100644 --- a/examples/sol-contract/.gitignore +++ b/examples/sol-contract/.gitignore @@ -3,4 +3,3 @@ build/example_sol_contract-keypair.json scripts/invoke.js scripts/invoke.js.map scripts/node_modules/ -scripts/package-lock.json \ No newline at end of file diff --git a/examples/sol-contract/scripts/package-lock.json b/examples/sol-contract/scripts/package-lock.json new file mode 100644 index 0000000..487b1e8 --- /dev/null +++ b/examples/sol-contract/scripts/package-lock.json @@ -0,0 +1,928 @@ +{ + "name": "scripts", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "scripts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@solana/web3.js": "^1.56.2" + }, + "devDependencies": { + "typescript": "^4.8.4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", + "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@noble/ed25519": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz", + "integrity": "sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/hashes": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.3.tgz", + "integrity": "sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz", + "integrity": "sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz", + "integrity": "sha512-lR0EMP2HC3+Mxwd4YcnZb0smnaDw7Bl2IQWZiTevRH5ZZBZn6VRWn3/92E3qdU4SSImJkA6IDHawOHAnx/qUvQ==", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/buffer-layout/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.63.1", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.63.1.tgz", + "integrity": "sha512-wgEdGVK5FTS2zENxbcGSvKpGZ0jDS6BUdGu8Gn6ns0CzgJkK83u4ip3THSnBPEQ5i/jrqukg998BwV1H67+qiQ==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@noble/ed25519": "^1.7.0", + "@noble/hashes": "^1.1.2", + "@noble/secp256k1": "^1.6.3", + "@solana/buffer-layout": "^4.0.0", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.0.0", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.1", + "fast-stable-stringify": "^1.0.0", + "jayson": "^3.4.4", + "node-fetch": "2", + "rpc-websockets": "^7.5.0", + "superstruct": "^0.14.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz", + "integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bufferutil": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", + "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", + "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "JSONStream": "^1.3.5", + "lodash": "^4.17.20", + "uuid": "^8.3.2", + "ws": "^7.4.5" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "node_modules/rpc-websockets": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz", + "integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "eventemitter3": "^4.0.7", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + } + }, + "node_modules/rpc-websockets/node_modules/ws": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", + "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + }, + "dependencies": { + "@babel/runtime": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", + "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@noble/ed25519": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.1.tgz", + "integrity": "sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==" + }, + "@noble/hashes": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.3.tgz", + "integrity": "sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A==" + }, + "@noble/secp256k1": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.0.tgz", + "integrity": "sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==" + }, + "@solana/buffer-layout": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz", + "integrity": "sha512-lR0EMP2HC3+Mxwd4YcnZb0smnaDw7Bl2IQWZiTevRH5ZZBZn6VRWn3/92E3qdU4SSImJkA6IDHawOHAnx/qUvQ==", + "requires": { + "buffer": "~6.0.3" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, + "@solana/web3.js": { + "version": "1.63.1", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.63.1.tgz", + "integrity": "sha512-wgEdGVK5FTS2zENxbcGSvKpGZ0jDS6BUdGu8Gn6ns0CzgJkK83u4ip3THSnBPEQ5i/jrqukg998BwV1H67+qiQ==", + "requires": { + "@babel/runtime": "^7.12.5", + "@noble/ed25519": "^1.7.0", + "@noble/hashes": "^1.1.2", + "@noble/secp256k1": "^1.6.3", + "@solana/buffer-layout": "^4.0.0", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.0.0", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.1", + "fast-stable-stringify": "^1.0.0", + "jayson": "^3.4.4", + "node-fetch": "2", + "rpc-websockets": "^7.5.0", + "superstruct": "^0.14.2" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + }, + "@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "requires": { + "@types/node": "*" + } + }, + "base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "requires": { + "bindings": "^1.3.0" + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "requires": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "requires": { + "base-x": "^3.0.2" + } + }, + "buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz", + "integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "bufferutil": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", + "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==" + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==" + }, + "fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "requires": {} + }, + "jayson": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", + "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", + "requires": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "JSONStream": "^1.3.5", + "lodash": "^4.17.20", + "uuid": "^8.3.2", + "ws": "^7.4.5" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==" + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "optional": true + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "rpc-websockets": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.5.0.tgz", + "integrity": "sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ==", + "requires": { + "@babel/runtime": "^7.17.2", + "bufferutil": "^4.0.1", + "eventemitter3": "^4.0.7", + "utf-8-validate": "^5.0.2", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "dependencies": { + "ws": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", + "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", + "requires": {} + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + }, + "text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true + }, + "utf-8-validate": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", + "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} + } + } +} From cb1451b9064880d86eb008c71a520fc5af1b8afb Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 4 Oct 2022 19:57:46 -0400 Subject: [PATCH 57/69] Some renaming --- examples/sol-contract/README.md | 2 + examples/sol-contract/scripts/invoke.ts | 6 +-- examples/sol-contract/src/processor.rs | 16 ++++---- examples/sol-contract/src/state.rs | 52 +++++++++++++------------ 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index fa123a3..fed35f8 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -43,6 +43,8 @@ We assume that you have installed `cargo`, `solana`, `npm` and `node`. > cd examples/sol-contract # Build the example contract > scripts/build.sh +# Config solana CLI and set the url as devnet +> solana config set --url https://api.devnet.solana.com # Deploy the example contract > scripts/deploy.sh # Invoke the example contract diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 82dc1a7..729431f 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -28,7 +28,7 @@ export const invoke = async (loan: string, collateral: string) => { await conn.confirmTransaction(airdropSig); /* Prepare the createInst instruction which creates an - * account storing the LoanInfo data for the instructions */ + * account storing the AdminConfig data for the instructions */ let loanInfoSize = 1 + 32 + 8 + 32 + 8; let dataAccount = web3.Keypair.generate(); let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); @@ -84,7 +84,7 @@ export const invoke = async (loan: string, collateral: string) => { let txCheckSig = await web3.sendAndConfirmTransaction( conn, txCheck, [payer, contract] ); - console.log("TxHash: " + txCheckSig); + console.log("TxHash: " + txCheckSig); /* Try to invoke the Init instruction without authority */ console.log("Trying an unauthorized invocation of Init..."); @@ -103,7 +103,7 @@ export const invoke = async (loan: string, collateral: string) => { let txAttacker = new web3.Transaction({ feePayer: payer.publicKey }); dataLayout.encode(Object.assign({instruction: 0}), data); txAttacker.add( - attackerCreateInst, + attackerCreateInst, new web3.TransactionInstruction({ data: data, keys: accounts, diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 9e51184..8642457 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -19,7 +19,7 @@ use borsh::BorshDeserialize; use pyth_sdk_solana::load_price_feed_from_account_info; use crate::instruction::ExampleInstructions; -use crate::state::LoanInfo; +use crate::state::AdminConfig; pub fn process_instruction( _program_id: &Pubkey, @@ -39,31 +39,31 @@ pub fn process_instruction( return Err(ProgramError::Custom(0)); } - let mut loan_info = LoanInfo::unpack_from_slice(&data_account.try_borrow_data()?)?; + let mut loan_info = AdminConfig::unpack_from_slice(&data_account.try_borrow_data()?)?; if loan_info.is_initialized() { return Err(ProgramError::Custom(1)); } loan_info.is_initialized = true; - loan_info.loan_key = *pyth_loan_account.key; - loan_info.collateral_key = *pyth_collateral_account.key; + loan_info.loan_price_feed_id = *pyth_loan_account.key; + loan_info.collateral_price_feed_id = *pyth_collateral_account.key; // Give some dummy numbers for simplicity of this example. loan_info.loan_qty = 1; loan_info.collateral_qty = 3000; - LoanInfo::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; + AdminConfig::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) } ExampleInstructions::Loan2Value {} => { - let loan_info = LoanInfo::unpack_from_slice(&data_account.try_borrow_data()?)?; + let loan_info = AdminConfig::unpack_from_slice(&data_account.try_borrow_data()?)?; if !loan_info.is_initialized() { return Err(ProgramError::Custom(1)); } - if loan_info.loan_key != *pyth_loan_account.key - || loan_info.collateral_key != *pyth_collateral_account.key + if loan_info.loan_price_feed_id != *pyth_loan_account.key + || loan_info.collateral_price_feed_id != *pyth_collateral_account.key { return Err(ProgramError::Custom(2)); } diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index 8fd892f..5337352 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -1,6 +1,6 @@ //! Program states -//! A data account would store a LoanInfo structure for the instructions. -//! This file contains the serialization and deserialization of LoanInfo. +//! A data account would store an AdminConfig structure for instructions. +//! This file contains the serialization / deserialization of AdminConfig. use solana_program::program_error::ProgramError; use solana_program::program_pack::{ @@ -17,32 +17,34 @@ use arrayref::{ mut_array_refs, }; -pub struct LoanInfo { - pub is_initialized: bool, - pub loan_key: Pubkey, - pub loan_qty: i64, - pub collateral_key: Pubkey, - pub collateral_qty: i64, +// loan_price_feed_id and collateral_price_feed_id are the +// Pyth price accounts for the loan and collateral tokens +pub struct AdminConfig { + pub is_initialized: bool, + pub loan_price_feed_id: Pubkey, + pub loan_qty: i64, + pub collateral_price_feed_id: Pubkey, + pub collateral_qty: i64, } -impl Sealed for LoanInfo { +impl Sealed for AdminConfig { } -impl IsInitialized for LoanInfo { +impl IsInitialized for AdminConfig { fn is_initialized(&self) -> bool { self.is_initialized } } -impl Pack for LoanInfo { +impl Pack for AdminConfig { const LEN: usize = 1 + 32 + 8 + 32 + 8; fn unpack_from_slice(src: &[u8]) -> Result { - let src = array_ref![src, 0, LoanInfo::LEN]; + let src = array_ref![src, 0, AdminConfig::LEN]; let ( src_is_initialized, - src_loan_key, + src_loan_price_feed_id, src_loan_qty, - src_collateral_key, + src_collateral_price_feed_id, src_collateral_qty, ) = array_refs![src, 1, 32, 8, 32, 8]; @@ -52,37 +54,37 @@ impl Pack for LoanInfo { _ => return Err(ProgramError::InvalidAccountData), }; - Ok(LoanInfo { + Ok(AdminConfig { is_initialized, - loan_key: Pubkey::new_from_array(*src_loan_key), + loan_price_feed_id: Pubkey::new_from_array(*src_loan_price_feed_id), loan_qty: i64::from_le_bytes(*src_loan_qty), - collateral_key: Pubkey::new_from_array(*src_collateral_key), + collateral_price_feed_id: Pubkey::new_from_array(*src_collateral_price_feed_id), collateral_qty: i64::from_le_bytes(*src_collateral_qty), }) } fn pack_into_slice(&self, dst: &mut [u8]) { - let dst = array_mut_ref![dst, 0, LoanInfo::LEN]; + let dst = array_mut_ref![dst, 0, AdminConfig::LEN]; let ( dst_is_initialized, - dst_loan_key, + dst_loan_price_feed_id, dst_loan_qty, - dst_collateral_key, + dst_collateral_price_feed_id, dst_collateral_qty, ) = mut_array_refs![dst, 1, 32, 8, 32, 8]; - let LoanInfo { + let AdminConfig { is_initialized, - loan_key, + loan_price_feed_id, loan_qty, - collateral_key, + collateral_price_feed_id, collateral_qty, } = self; dst_is_initialized[0] = *is_initialized as u8; - dst_loan_key.copy_from_slice(loan_key.as_ref()); + dst_loan_price_feed_id.copy_from_slice(loan_price_feed_id.as_ref()); *dst_loan_qty = loan_qty.to_le_bytes(); - dst_collateral_key.copy_from_slice(collateral_key.as_ref()); + dst_collateral_price_feed_id.copy_from_slice(collateral_price_feed_id.as_ref()); *dst_collateral_qty = collateral_qty.to_le_bytes(); } } From 558dd0c24732eeec915fffca5fed5b704b215849 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 4 Oct 2022 20:04:50 -0400 Subject: [PATCH 58/69] Hardcode loan/collateral quantity in Loan2Value instruction --- examples/sol-contract/scripts/invoke.ts | 2 +- examples/sol-contract/src/processor.rs | 15 ++++++------- examples/sol-contract/src/state.rs | 28 +++++-------------------- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 729431f..829c569 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -29,7 +29,7 @@ export const invoke = async (loan: string, collateral: string) => { /* Prepare the createInst instruction which creates an * account storing the AdminConfig data for the instructions */ - let loanInfoSize = 1 + 32 + 8 + 32 + 8; + let loanInfoSize = 1 + 32 + 32; let dataAccount = web3.Keypair.generate(); let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); const createInst = web3.SystemProgram.createAccount({ diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 8642457..532491d 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -48,9 +48,6 @@ pub fn process_instruction( loan_info.is_initialized = true; loan_info.loan_price_feed_id = *pyth_loan_account.key; loan_info.collateral_price_feed_id = *pyth_collateral_account.key; - // Give some dummy numbers for simplicity of this example. - loan_info.loan_qty = 1; - loan_info.collateral_qty = 3000; AdminConfig::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) @@ -68,16 +65,20 @@ pub fn process_instruction( return Err(ProgramError::Custom(2)); } + // Give some dummy numbers for simplicity of this example. + let loan_qty = 1; + let collateral_qty = 3000; + // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; let loan_value = result1 .price - .checked_mul(loan_info.loan_qty) + .checked_mul(loan_qty) .ok_or(ProgramError::Custom(4))?; let loan_conf = (result1.conf as f64) // confidence * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent - * (loan_info.loan_qty as f64); // * quantity + * (loan_qty as f64); // * quantity let loan_value_max = loan_value as f64 + loan_conf; // Calculate the minimum value of the collateral using Pyth. @@ -85,11 +86,11 @@ pub fn process_instruction( let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(3))?; let collateral_value = result2 .price - .checked_mul(loan_info.collateral_qty) + .checked_mul(collateral_qty) .ok_or(ProgramError::Custom(4))?; let collateral_conf = (result2.conf as f64) // confidence * (10 as f64).powf(result2.expo as f64) // * 10 ^ exponent - * (loan_info.collateral_qty as f64); // * quantity + * (collateral_qty as f64); // * quantity let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index 5337352..db6a979 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -22,9 +22,7 @@ use arrayref::{ pub struct AdminConfig { pub is_initialized: bool, pub loan_price_feed_id: Pubkey, - pub loan_qty: i64, pub collateral_price_feed_id: Pubkey, - pub collateral_qty: i64, } impl Sealed for AdminConfig { @@ -37,16 +35,11 @@ impl IsInitialized for AdminConfig { } impl Pack for AdminConfig { - const LEN: usize = 1 + 32 + 8 + 32 + 8; + const LEN: usize = 1 + 32 + 32; fn unpack_from_slice(src: &[u8]) -> Result { let src = array_ref![src, 0, AdminConfig::LEN]; - let ( - src_is_initialized, - src_loan_price_feed_id, - src_loan_qty, - src_collateral_price_feed_id, - src_collateral_qty, - ) = array_refs![src, 1, 32, 8, 32, 8]; + let (src_is_initialized, src_loan_price_feed_id, src_collateral_price_feed_id) = + array_refs![src, 1, 32, 32]; let is_initialized = match src_is_initialized { [0] => false, @@ -57,34 +50,23 @@ impl Pack for AdminConfig { Ok(AdminConfig { is_initialized, loan_price_feed_id: Pubkey::new_from_array(*src_loan_price_feed_id), - loan_qty: i64::from_le_bytes(*src_loan_qty), collateral_price_feed_id: Pubkey::new_from_array(*src_collateral_price_feed_id), - collateral_qty: i64::from_le_bytes(*src_collateral_qty), }) } fn pack_into_slice(&self, dst: &mut [u8]) { let dst = array_mut_ref![dst, 0, AdminConfig::LEN]; - let ( - dst_is_initialized, - dst_loan_price_feed_id, - dst_loan_qty, - dst_collateral_price_feed_id, - dst_collateral_qty, - ) = mut_array_refs![dst, 1, 32, 8, 32, 8]; + let (dst_is_initialized, dst_loan_price_feed_id, dst_collateral_price_feed_id) = + mut_array_refs![dst, 1, 32, 32]; let AdminConfig { is_initialized, loan_price_feed_id, - loan_qty, collateral_price_feed_id, - collateral_qty, } = self; dst_is_initialized[0] = *is_initialized as u8; dst_loan_price_feed_id.copy_from_slice(loan_price_feed_id.as_ref()); - *dst_loan_qty = loan_qty.to_le_bytes(); dst_collateral_price_feed_id.copy_from_slice(collateral_price_feed_id.as_ref()); - *dst_collateral_qty = collateral_qty.to_le_bytes(); } } From e0d03b7d24cbdbd233725c9d246cd494a2959be1 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Tue, 4 Oct 2022 21:10:05 -0400 Subject: [PATCH 59/69] Make loan_qty and collateral_qty parameters of instruction --- examples/sol-contract/scripts/invoke.ts | 30 +++++++++++++++++------- examples/sol-contract/src/instruction.rs | 5 +++- examples/sol-contract/src/processor.rs | 12 ++++++---- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 829c569..0d2c91d 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -1,6 +1,6 @@ const fs = require('fs'); const web3 = require("@solana/web3.js"); -const {struct, b, u8, u32} = require("@solana/buffer-layout"); +const {struct, b, u8, blob} = require("@solana/buffer-layout"); export const invoke = async (loan: string, collateral: string) => { /* Obtain the contract keypair */ @@ -50,17 +50,22 @@ export const invoke = async (loan: string, collateral: string) => { {pubkey: loanKey, isSigner: false, isWritable: false}, {pubkey: collateralKey, isSigner: false, isWritable: false}, ]; - let dataLayout = struct([ u8('instruction') ]) - let data = Buffer.alloc(dataLayout.span); + + let initLayout = struct([ u8('instruction') ]) + let initData = Buffer.alloc(initLayout.span); + let loan2ValueLayout = struct([ + u8('instruction'), blob(8, 'loan_qty'), blob(8, 'collateral_qty') + ]) + let loan2ValueData = Buffer.alloc(loan2ValueLayout.span); /* Invoke the Init instruction (instruction #0) */ console.log("Creating data account and invoking Init..."); let txInit = new web3.Transaction({ feePayer: payer.publicKey }); - dataLayout.encode(Object.assign({instruction: 0}), data); + initLayout.encode({instruction: 0}, initData); txInit.add( createInst, /* Create data account */ new web3.TransactionInstruction({ /* Initialize data account */ - data: data, + data: initData, keys: accounts, programId: contract.publicKey }) @@ -72,11 +77,19 @@ export const invoke = async (loan: string, collateral: string) => { /* Invoke the Loan2Value instruction (instruction #1) */ console.log("Checking loan to value ratio..."); + /* Encode 0x1 in big ending */ + let loan_qty = Buffer.from('0100000000000000', 'hex'); + /* Encode 0xbb8 (3000) in big ending */ + let collateral_qty = Buffer.from('b80b000000000000', 'hex'); let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); - dataLayout.encode(Object.assign({instruction: 1}), data); + loan2ValueLayout.encode( + {instruction: 1, + loan_qty: blob(8).decode(loan_qty), + collateral_qty: blob(8).decode(collateral_qty)} + , loan2ValueData); txCheck.add( new web3.TransactionInstruction({ - data: data, + data: loan2ValueData, keys: accounts, programId: contract.publicKey }) @@ -101,11 +114,10 @@ export const invoke = async (loan: string, collateral: string) => { }); let txAttacker = new web3.Transaction({ feePayer: payer.publicKey }); - dataLayout.encode(Object.assign({instruction: 0}), data); txAttacker.add( attackerCreateInst, new web3.TransactionInstruction({ - data: data, + data: initData, keys: accounts, programId: contract.publicKey }) diff --git a/examples/sol-contract/src/instruction.rs b/examples/sol-contract/src/instruction.rs index db553df..b4409da 100644 --- a/examples/sol-contract/src/instruction.rs +++ b/examples/sol-contract/src/instruction.rs @@ -12,5 +12,8 @@ use borsh::{ #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub enum ExampleInstructions { Init {}, - Loan2Value {}, + Loan2Value { + loan_qty: i64, + collateral_qty: i64, + }, } diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 532491d..691ff28 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -52,7 +52,13 @@ pub fn process_instruction( AdminConfig::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; Ok(()) } - ExampleInstructions::Loan2Value {} => { + ExampleInstructions::Loan2Value { + loan_qty, + collateral_qty, + } => { + msg!("Loan quantity is {}.", loan_qty); + msg!("Collateral quantity is {}.", collateral_qty); + let loan_info = AdminConfig::unpack_from_slice(&data_account.try_borrow_data()?)?; if !loan_info.is_initialized() { @@ -65,10 +71,6 @@ pub fn process_instruction( return Err(ProgramError::Custom(2)); } - // Give some dummy numbers for simplicity of this example. - let loan_qty = 1; - let collateral_qty = 3000; - // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; From cb4bbcc53e930aabe459c02fab7d5b02ac8902d5 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Wed, 5 Oct 2022 10:46:12 -0400 Subject: [PATCH 60/69] Minor --- examples/sol-contract/scripts/invoke.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 0d2c91d..0019ee1 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -60,8 +60,8 @@ export const invoke = async (loan: string, collateral: string) => { /* Invoke the Init instruction (instruction #0) */ console.log("Creating data account and invoking Init..."); - let txInit = new web3.Transaction({ feePayer: payer.publicKey }); initLayout.encode({instruction: 0}, initData); + let txInit = new web3.Transaction({ feePayer: payer.publicKey }); txInit.add( createInst, /* Create data account */ new web3.TransactionInstruction({ /* Initialize data account */ @@ -81,12 +81,13 @@ export const invoke = async (loan: string, collateral: string) => { let loan_qty = Buffer.from('0100000000000000', 'hex'); /* Encode 0xbb8 (3000) in big ending */ let collateral_qty = Buffer.from('b80b000000000000', 'hex'); - let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); loan2ValueLayout.encode( {instruction: 1, loan_qty: blob(8).decode(loan_qty), collateral_qty: blob(8).decode(collateral_qty)} , loan2ValueData); + + let txCheck = new web3.Transaction({ feePayer: payer.publicKey }); txCheck.add( new web3.TransactionInstruction({ data: loan2ValueData, From 87fe21f617e7ae5f9f35d7e4681d4a77c46fb2ab Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 9 Oct 2022 19:09:49 -0400 Subject: [PATCH 61/69] Change solana version to 1.10.40, same as the Pyth SDK --- examples/sol-contract/Cargo.toml | 7 +++---- examples/sol-contract/scripts/build.sh | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/sol-contract/Cargo.toml b/examples/sol-contract/Cargo.toml index 41e6b4e..f297d09 100644 --- a/examples/sol-contract/Cargo.toml +++ b/examples/sol-contract/Cargo.toml @@ -11,9 +11,8 @@ crate-type = ["cdylib", "lib"] borsh = "0.9" arrayref = "0.3.6" pyth-sdk-solana = "0.5.0" -solana-program = "1.8.1, < 1.11" +solana-program = "1.10.40" [dev-dependencies] -solana-sdk = "1.8.1, < 1.11" -solana-client = "1.8.1, < 1.11" -solana-program-test = "1.8.1, < 1.11" \ No newline at end of file +solana-sdk = "1.10.40" +solana-client = "1.10.40" diff --git a/examples/sol-contract/scripts/build.sh b/examples/sol-contract/scripts/build.sh index f39080b..e65bbde 100755 --- a/examples/sol-contract/scripts/build.sh +++ b/examples/sol-contract/scripts/build.sh @@ -1 +1 @@ -cargo build-bpf --sbf-out-dir ./build +cargo build-bpf --bpf-out-dir ./build From 834aa3d5f1a263b0c630bc98603d385884ad03ca Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 9 Oct 2022 19:18:56 -0400 Subject: [PATCH 62/69] Only use i64 to calculate the values, before normalizing with exponents --- examples/sol-contract/src/processor.rs | 33 ++++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 691ff28..7128179 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -74,31 +74,38 @@ pub fn process_instruction( // Calculate the maximum value of the loan using Pyth. let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; - let loan_value = result1 + let loan_max_price = result1 .price + .checked_add(result1.conf as i64) + .ok_or(ProgramError::Custom(4))?; + let loan_max_value = loan_max_price .checked_mul(loan_qty) .ok_or(ProgramError::Custom(4))?; - let loan_conf = (result1.conf as f64) // confidence - * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent - * (loan_qty as f64); // * quantity - let loan_value_max = loan_value as f64 + loan_conf; // Calculate the minimum value of the collateral using Pyth. let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(3))?; - let collateral_value = result2 + let collateral_min_price = result2 .price + .checked_sub(result2.conf as i64) + .ok_or(ProgramError::Custom(4))?; + let collateral_min_value = collateral_min_price .checked_mul(collateral_qty) .ok_or(ProgramError::Custom(4))?; - let collateral_conf = (result2.conf as f64) // confidence - * (10 as f64).powf(result2.expo as f64) // * 10 ^ exponent - * (collateral_qty as f64); // * quantity - let collateral_value_min = collateral_value as f64 - collateral_conf; // Check whether the value of the collateral is higher. - msg!("The maximum loan value is {}.", loan_value_max); - msg!("The minimum collateral value is {}.", collateral_value_min); - if collateral_value_min > loan_value_max { + msg!( + "The maximum loan value is {} * 10^({}).", + loan_max_value, + result1.expo + ); + msg!( + "The minimum collateral value is {} * 10^({}).", + collateral_min_value, + result2.expo + ); + + if collateral_min_value > loan_max_value { msg!("The value of the collateral is higher."); return Ok(()); } else { From 3cc600fc1fc454014c9e20c0d85590b714add565 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 9 Oct 2022 19:42:24 -0400 Subject: [PATCH 63/69] Normalize the values with the exponents --- examples/sol-contract/src/processor.rs | 45 +++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 7128179..3198d93 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -71,40 +71,63 @@ pub fn process_instruction( return Err(ProgramError::Custom(2)); } - // Calculate the maximum value of the loan using Pyth. + // With high confidence, the maximum value of the loan is + // (price + conf) * loan_qty * 10 ^ (expo). + // Here is more explanation on confidence interval in Pyth: + // https://docs.pyth.network/consume-data/best-practices let feed1 = load_price_feed_from_account_info(pyth_loan_account)?; let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; let loan_max_price = result1 .price .checked_add(result1.conf as i64) .ok_or(ProgramError::Custom(4))?; - let loan_max_value = loan_max_price + let mut loan_max_value = loan_max_price .checked_mul(loan_qty) .ok_or(ProgramError::Custom(4))?; + msg!( + "The maximum loan value is {} * 10^({}).", + loan_max_value, + result1.expo + ); - // Calculate the minimum value of the collateral using Pyth. + // With high confidence, the minimum value of the collateral is + // (price - conf) * collateral_qty * 10 ^ (expo). + // Here is more explanation on confidence interval in Pyth: + // https://docs.pyth.network/consume-data/best-practices let feed2 = load_price_feed_from_account_info(pyth_collateral_account)?; let result2 = feed2.get_current_price().ok_or(ProgramError::Custom(3))?; let collateral_min_price = result2 .price .checked_sub(result2.conf as i64) .ok_or(ProgramError::Custom(4))?; - let collateral_min_value = collateral_min_price + let mut collateral_min_value = collateral_min_price .checked_mul(collateral_qty) .ok_or(ProgramError::Custom(4))?; - - // Check whether the value of the collateral is higher. - msg!( - "The maximum loan value is {} * 10^({}).", - loan_max_value, - result1.expo - ); msg!( "The minimum collateral value is {} * 10^({}).", collateral_min_value, result2.expo ); + // If the loan and collateral prices use different exponent, + // normalize the value. + if result1.expo > result2.expo { + let normalize = (10 as i64) + .checked_pow((result1.expo - result2.expo) as u32) + .ok_or(ProgramError::Custom(4))?; + collateral_min_value = collateral_min_value + .checked_mul(normalize) + .ok_or(ProgramError::Custom(4))?; + } else if result1.expo < result2.expo { + let normalize = (10 as i64) + .checked_pow((result2.expo - result1.expo) as u32) + .ok_or(ProgramError::Custom(4))?; + loan_max_value = loan_max_value + .checked_mul(normalize) + .ok_or(ProgramError::Custom(4))?; + } + + // Check whether the value of the collateral is higher. if collateral_min_value > loan_max_value { msg!("The value of the collateral is higher."); return Ok(()); From ef460958fea253ad2a616aed435422e012398390 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 9 Oct 2022 20:11:15 -0400 Subject: [PATCH 64/69] Use borsh library for state serialize/deserialize --- examples/sol-contract/src/processor.rs | 22 ++++----- examples/sol-contract/src/state.rs | 62 ++------------------------ 2 files changed, 16 insertions(+), 68 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 3198d93..2aaa17d 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -9,13 +9,13 @@ use solana_program::account_info::{ use solana_program::entrypoint::ProgramResult; use solana_program::msg; use solana_program::program_error::ProgramError; -use solana_program::program_pack::{ - IsInitialized, - Pack, -}; +use solana_program::program_memory::sol_memcpy; use solana_program::pubkey::Pubkey; -use borsh::BorshDeserialize; +use borsh::{ + BorshDeserialize, + BorshSerialize, +}; use pyth_sdk_solana::load_price_feed_from_account_info; use crate::instruction::ExampleInstructions; @@ -39,9 +39,9 @@ pub fn process_instruction( return Err(ProgramError::Custom(0)); } - let mut loan_info = AdminConfig::unpack_from_slice(&data_account.try_borrow_data()?)?; + let mut loan_info = AdminConfig::try_from_slice(&data_account.try_borrow_data()?)?; - if loan_info.is_initialized() { + if loan_info.is_initialized { return Err(ProgramError::Custom(1)); } @@ -49,7 +49,9 @@ pub fn process_instruction( loan_info.loan_price_feed_id = *pyth_loan_account.key; loan_info.collateral_price_feed_id = *pyth_collateral_account.key; - AdminConfig::pack(loan_info, &mut data_account.try_borrow_mut_data()?)?; + let loan_data = loan_info.try_to_vec()?; + let data_dst = &mut data_account.try_borrow_mut_data()?; + sol_memcpy(data_dst, &loan_data, 1 + 32 + 32); Ok(()) } ExampleInstructions::Loan2Value { @@ -59,9 +61,9 @@ pub fn process_instruction( msg!("Loan quantity is {}.", loan_qty); msg!("Collateral quantity is {}.", collateral_qty); - let loan_info = AdminConfig::unpack_from_slice(&data_account.try_borrow_data()?)?; + let loan_info = AdminConfig::try_from_slice(&data_account.try_borrow_data()?)?; - if !loan_info.is_initialized() { + if !loan_info.is_initialized { return Err(ProgramError::Custom(1)); } diff --git a/examples/sol-contract/src/state.rs b/examples/sol-contract/src/state.rs index db6a979..686e5b7 100644 --- a/examples/sol-contract/src/state.rs +++ b/examples/sol-contract/src/state.rs @@ -2,71 +2,17 @@ //! A data account would store an AdminConfig structure for instructions. //! This file contains the serialization / deserialization of AdminConfig. -use solana_program::program_error::ProgramError; -use solana_program::program_pack::{ - IsInitialized, - Pack, - Sealed, +use borsh::{ + BorshDeserialize, + BorshSerialize, }; use solana_program::pubkey::Pubkey; -use arrayref::{ - array_mut_ref, - array_ref, - array_refs, - mut_array_refs, -}; - // loan_price_feed_id and collateral_price_feed_id are the // Pyth price accounts for the loan and collateral tokens +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)] pub struct AdminConfig { pub is_initialized: bool, pub loan_price_feed_id: Pubkey, pub collateral_price_feed_id: Pubkey, } - -impl Sealed for AdminConfig { -} - -impl IsInitialized for AdminConfig { - fn is_initialized(&self) -> bool { - self.is_initialized - } -} - -impl Pack for AdminConfig { - const LEN: usize = 1 + 32 + 32; - fn unpack_from_slice(src: &[u8]) -> Result { - let src = array_ref![src, 0, AdminConfig::LEN]; - let (src_is_initialized, src_loan_price_feed_id, src_collateral_price_feed_id) = - array_refs![src, 1, 32, 32]; - - let is_initialized = match src_is_initialized { - [0] => false, - [1] => true, - _ => return Err(ProgramError::InvalidAccountData), - }; - - Ok(AdminConfig { - is_initialized, - loan_price_feed_id: Pubkey::new_from_array(*src_loan_price_feed_id), - collateral_price_feed_id: Pubkey::new_from_array(*src_collateral_price_feed_id), - }) - } - - fn pack_into_slice(&self, dst: &mut [u8]) { - let dst = array_mut_ref![dst, 0, AdminConfig::LEN]; - let (dst_is_initialized, dst_loan_price_feed_id, dst_collateral_price_feed_id) = - mut_array_refs![dst, 1, 32, 32]; - - let AdminConfig { - is_initialized, - loan_price_feed_id, - collateral_price_feed_id, - } = self; - - dst_is_initialized[0] = *is_initialized as u8; - dst_loan_price_feed_id.copy_from_slice(loan_price_feed_id.as_ref()); - dst_collateral_price_feed_id.copy_from_slice(collateral_price_feed_id.as_ref()); - } -} From 833a3fcdc00fab720fb0c451423d40b4cfd04151 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 9 Oct 2022 20:33:21 -0400 Subject: [PATCH 65/69] Resolve a few minor issues, such as renaming, from Ali's comments --- examples/sol-contract/Cargo.toml | 2 +- examples/sol-contract/scripts/deploy.sh | 3 ++ examples/sol-contract/scripts/invoke.ts | 2 +- examples/sol-contract/src/processor.rs | 37 +++++++++++++------------ 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/examples/sol-contract/Cargo.toml b/examples/sol-contract/Cargo.toml index f297d09..38e2ba5 100644 --- a/examples/sol-contract/Cargo.toml +++ b/examples/sol-contract/Cargo.toml @@ -10,8 +10,8 @@ crate-type = ["cdylib", "lib"] [dependencies] borsh = "0.9" arrayref = "0.3.6" -pyth-sdk-solana = "0.5.0" solana-program = "1.10.40" +pyth-sdk-solana = { path = "../../pyth-sdk-solana", version = "0.6.1" } [dev-dependencies] solana-sdk = "1.10.40" diff --git a/examples/sol-contract/scripts/deploy.sh b/examples/sol-contract/scripts/deploy.sh index adc1169..080eb74 100755 --- a/examples/sol-contract/scripts/deploy.sh +++ b/examples/sol-contract/scripts/deploy.sh @@ -1 +1,4 @@ +echo "Airdropping..." +solana airdrop 1 --url https://api.devnet.solana.com +echo "Deploying the program..." solana program deploy --program-id build/example_sol_contract-keypair.json build/example_sol_contract.so diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index 0019ee1..a3a268e 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -128,7 +128,7 @@ export const invoke = async (loan: string, collateral: string) => { let txAttackerSig = await web3.sendAndConfirmTransaction( conn, txAttacker, [payer, attackerDataAccount, attacker] ); - console.error("Attacker succeeded with TxHash: " + txAttackerSig); + throw new Error("Attacker succeeded. TxHash: " + txAttackerSig); } catch (error) { console.log("Attacker failed to invoke unauthorized Init."); } diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 2aaa17d..71e7938 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -22,38 +22,41 @@ use crate::instruction::ExampleInstructions; use crate::state::AdminConfig; pub fn process_instruction( - _program_id: &Pubkey, - _accounts: &[AccountInfo], + program_id: &Pubkey, + accounts: &[AccountInfo], input: &[u8], ) -> ProgramResult { - let account_iter = &mut _accounts.iter(); + let account_iter = &mut accounts.iter(); let signer = next_account_info(account_iter)?; - let data_account = next_account_info(account_iter)?; + let admin_config_account = next_account_info(account_iter)?; let pyth_loan_account = next_account_info(account_iter)?; let pyth_collateral_account = next_account_info(account_iter)?; let instruction = ExampleInstructions::try_from_slice(input)?; match instruction { ExampleInstructions::Init {} => { - if !(signer.key == _program_id && signer.is_signer) { + // Only an authorized / trusted key should be able to configure the price feed id for + // each asset + if !(signer.key == program_id && signer.is_signer) { return Err(ProgramError::Custom(0)); } - let mut loan_info = AdminConfig::try_from_slice(&data_account.try_borrow_data()?)?; + let mut config = AdminConfig::try_from_slice(&admin_config_account.try_borrow_data()?)?; - if loan_info.is_initialized { + if config.is_initialized { return Err(ProgramError::Custom(1)); } - loan_info.is_initialized = true; - loan_info.loan_price_feed_id = *pyth_loan_account.key; - loan_info.collateral_price_feed_id = *pyth_collateral_account.key; + config.is_initialized = true; + config.loan_price_feed_id = *pyth_loan_account.key; + config.collateral_price_feed_id = *pyth_collateral_account.key; - let loan_data = loan_info.try_to_vec()?; - let data_dst = &mut data_account.try_borrow_mut_data()?; - sol_memcpy(data_dst, &loan_data, 1 + 32 + 32); + let config_data = config.try_to_vec()?; + let config_dst = &mut admin_config_account.try_borrow_mut_data()?; + sol_memcpy(config_dst, &config_data, 1 + 32 + 32); Ok(()) } + ExampleInstructions::Loan2Value { loan_qty, collateral_qty, @@ -61,14 +64,14 @@ pub fn process_instruction( msg!("Loan quantity is {}.", loan_qty); msg!("Collateral quantity is {}.", collateral_qty); - let loan_info = AdminConfig::try_from_slice(&data_account.try_borrow_data()?)?; + let config = AdminConfig::try_from_slice(&admin_config_account.try_borrow_data()?)?; - if !loan_info.is_initialized { + if !config.is_initialized { return Err(ProgramError::Custom(1)); } - if loan_info.loan_price_feed_id != *pyth_loan_account.key - || loan_info.collateral_price_feed_id != *pyth_collateral_account.key + if config.loan_price_feed_id != *pyth_loan_account.key + || config.collateral_price_feed_id != *pyth_collateral_account.key { return Err(ProgramError::Custom(2)); } From 9fc162d1f9a7c007d01568bdbf5a48fcafceafe3 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Sun, 9 Oct 2022 20:37:56 -0400 Subject: [PATCH 66/69] Add more check in the Init instruction --- examples/sol-contract/src/processor.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/sol-contract/src/processor.rs b/examples/sol-contract/src/processor.rs index 71e7938..cf100d6 100644 --- a/examples/sol-contract/src/processor.rs +++ b/examples/sol-contract/src/processor.rs @@ -35,8 +35,7 @@ pub fn process_instruction( let instruction = ExampleInstructions::try_from_slice(input)?; match instruction { ExampleInstructions::Init {} => { - // Only an authorized / trusted key should be able to configure the price feed id for - // each asset + // Only an authorized key should be able to configure the price feed id for each asset if !(signer.key == program_id && signer.is_signer) { return Err(ProgramError::Custom(0)); } @@ -51,6 +50,10 @@ pub fn process_instruction( config.loan_price_feed_id = *pyth_loan_account.key; config.collateral_price_feed_id = *pyth_collateral_account.key; + // Make sure these Pyth price accounts can be loaded + load_price_feed_from_account_info(pyth_loan_account)?; + load_price_feed_from_account_info(pyth_collateral_account)?; + let config_data = config.try_to_vec()?; let config_dst = &mut admin_config_account.try_borrow_mut_data()?; sol_memcpy(config_dst, &config_data, 1 + 32 + 32); From 6045e745d4053d60397912b03d5370c29dff9dd8 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 10 Oct 2022 09:12:34 -0400 Subject: [PATCH 67/69] Fix a bug in client --- examples/sol-contract/scripts/invoke.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/sol-contract/scripts/invoke.ts b/examples/sol-contract/scripts/invoke.ts index a3a268e..3d53a07 100644 --- a/examples/sol-contract/scripts/invoke.ts +++ b/examples/sol-contract/scripts/invoke.ts @@ -124,14 +124,18 @@ export const invoke = async (loan: string, collateral: string) => { }) ); + var attacker_succeed = false, txAttackerSig; try { - let txAttackerSig = await web3.sendAndConfirmTransaction( + txAttackerSig = await web3.sendAndConfirmTransaction( conn, txAttacker, [payer, attackerDataAccount, attacker] ); - throw new Error("Attacker succeeded. TxHash: " + txAttackerSig); + attacker_succeed = true; } catch (error) { console.log("Attacker failed to invoke unauthorized Init."); } + + if (attacker_succeed) + throw new Error("Attacker succeeded! TxHash: " + txAttackerSig); } /* Pyth price accounts on the solana devnet */ From ba7217a35bc2be9861d8dc1fa4f9b45a1634561e Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 10 Oct 2022 10:22:29 -0400 Subject: [PATCH 68/69] Update the sol-contract example readme --- examples/sol-contract/README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index fed35f8..e514c71 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -22,17 +22,16 @@ let result1 = feed1.get_current_price().ok_or(ProgramError::Custom(3))?; And then calculate the loan value given the quantity of the loan. ```rust -let loan_value = result1 +let loan_max_price = result1 .price - .checked_mul(loan_info.loan_qty) + .checked_add(result1.conf as i64) + .ok_or(ProgramError::Custom(4))?; +let loan_max_value = loan_max_price + .checked_mul(loan_qty) .ok_or(ProgramError::Custom(4))?; -let loan_conf = (result1.conf as f64) // confidence - * (10 as f64).powf(result1.expo as f64) // * 10 ^ exponent - * (loan_info.loan_qty as f64); // * quantity -let loan_value_max = loan_value as f64 + loan_conf; ``` -This code says that, with high confidence, the maximum value of the loan does not exceed `loan_value_max`. +This code says that, with high confidence, the maximum value of the loan does not exceed `loan_max_value * 10^(result1.expo)` at the time of the query. In a similar way, the code then calculates the minimum value of the collateral and compare the two. ## Run this program From 91950014b063ea4ec6ecdf99fc8c0e98b7068ecd Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Mon, 10 Oct 2022 10:25:29 -0400 Subject: [PATCH 69/69] Minor in readme --- examples/sol-contract/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/sol-contract/README.md b/examples/sol-contract/README.md index e514c71..9ff52e6 100644 --- a/examples/sol-contract/README.md +++ b/examples/sol-contract/README.md @@ -34,6 +34,8 @@ let loan_max_value = loan_max_price This code says that, with high confidence, the maximum value of the loan does not exceed `loan_max_value * 10^(result1.expo)` at the time of the query. In a similar way, the code then calculates the minimum value of the collateral and compare the two. +More on Pyth best practice and price confidence interval can be found [here](https://docs.pyth.network/consume-data/best-practices). + ## Run this program We assume that you have installed `cargo`, `solana`, `npm` and `node`.