-
Notifications
You must be signed in to change notification settings - Fork 66
An example contract for Solana #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
957f735
Add example contract for solana
yhzhang0128 8aed7a0
Add scripts for invocation in examples/sol-contract
yhzhang0128 0677a3e
Add readme in examples/sol-contract
yhzhang0128 1b9a91e
Add overflow checks in examples/sol-contract
yhzhang0128 685bffd
Cleanup and add comments in examples/sol-contract
yhzhang0128 b045b70
Remove hard-coded contract hash in examples/sol-contract
yhzhang0128 7432028
Improve comments wording in examples/sol-contract
yhzhang0128 d5f6d75
Update readme of examples/sol-contract
yhzhang0128 eba9da5
Remove some unnecessary dependencies in examples/sol-contract
yhzhang0128 aac5b9d
Remove the unwrap() in the code of examples/sol-contract
yhzhang0128 d0b28d9
Fix a grammar error
yhzhang0128 8ff6ee1
Succeed in adding state.rs and an initialization instruction
yhzhang0128 004b136
Cleanup
yhzhang0128 39d8300
Cleanup
yhzhang0128 32692b8
Cleanup
yhzhang0128 30ef43f
Add confidence interval
yhzhang0128 7235d20
Minor
yhzhang0128 d026e35
Minor
yhzhang0128 715de4d
Cleanup the contract
yhzhang0128 017030b
Cleanup the client-side code
yhzhang0128 9458fc0
Cleanup the contract code
yhzhang0128 ed7791e
Update readme
yhzhang0128 59dff00
Minor
yhzhang0128 829d38b
Minor
yhzhang0128 6d580bc
Add package-lock.json
yhzhang0128 eb7462b
Some renaming
yhzhang0128 9d86e9f
Hardcode loan/collateral quantity in Loan2Value instruction
yhzhang0128 0bdb6c1
Make loan_qty and collateral_qty parameters of instruction
yhzhang0128 bca85ae
Minor
yhzhang0128 6f4a4c0
Update dependency requirements for Solana related dependencies (#75)
dadepo 37604d2
Update the price schema (#76)
ali-behjati 4b7008c
Add example contract for solana
yhzhang0128 3a03ac3
Add scripts for invocation in examples/sol-contract
yhzhang0128 33c8afa
Add readme in examples/sol-contract
yhzhang0128 969b8ff
Add overflow checks in examples/sol-contract
yhzhang0128 81a4442
Cleanup and add comments in examples/sol-contract
yhzhang0128 ae47349
Remove hard-coded contract hash in examples/sol-contract
yhzhang0128 dc4d163
Improve comments wording in examples/sol-contract
yhzhang0128 908a3bc
Update readme of examples/sol-contract
yhzhang0128 d2ccf1a
Remove some unnecessary dependencies in examples/sol-contract
yhzhang0128 b718bb5
Remove the unwrap() in the code of examples/sol-contract
yhzhang0128 063f20a
Fix a grammar error
yhzhang0128 a6f9473
Succeed in adding state.rs and an initialization instruction
yhzhang0128 8f3f76d
Cleanup
yhzhang0128 e9911f7
Cleanup
yhzhang0128 0c62d7f
Cleanup
yhzhang0128 8ceb10d
Add confidence interval
yhzhang0128 0ffe5d5
Minor
yhzhang0128 633f266
Minor
yhzhang0128 a695622
Cleanup the contract
yhzhang0128 5e6d075
Cleanup the client-side code
yhzhang0128 5093ca1
Cleanup the contract code
yhzhang0128 38867b6
Update readme
yhzhang0128 26ed735
Minor
yhzhang0128 3406cff
Minor
yhzhang0128 b185b2c
Add package-lock.json
yhzhang0128 cb1451b
Some renaming
yhzhang0128 558dd0c
Hardcode loan/collateral quantity in Loan2Value instruction
yhzhang0128 e0d03b7
Make loan_qty and collateral_qty parameters of instruction
yhzhang0128 cb4bbcc
Minor
yhzhang0128 ebbaa16
Merge branch 'example-rs' of github.com:yhzhang0128/pyth-sdk-rs into …
yhzhang0128 87fe21f
Change solana version to 1.10.40, same as the Pyth SDK
yhzhang0128 834aa3d
Only use i64 to calculate the values, before normalizing with exponents
yhzhang0128 3cc600f
Normalize the values with the exponents
yhzhang0128 ef46095
Use borsh library for state serialize/deserialize
yhzhang0128 833a3fc
Resolve a few minor issues, such as renaming, from Ali's comments
yhzhang0128 9fc162d
Add more check in the Init instruction
yhzhang0128 6045e74
Fix a bug in client
yhzhang0128 ba7217a
Update the sol-contract example readme
yhzhang0128 9195001
Minor in readme
yhzhang0128 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,4 +6,5 @@ members = [ | |
| "pyth-sdk-solana/test-contract", | ||
| "pyth-sdk-cw", | ||
| "examples/cw-contract", | ||
| "examples/sol-contract" | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| build/example_sol_contract.so | ||
| build/example_sol_contract-keypair.json | ||
| scripts/invoke.js | ||
| scripts/invoke.js.map | ||
| scripts/node_modules/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| [package] | ||
| name = "example-sol-contract" | ||
| version = "0.1.0" | ||
| authors = ["Pyth Data Foundation"] | ||
| edition = "2021" | ||
|
|
||
| [lib] | ||
| crate-type = ["cdylib", "lib"] | ||
|
|
||
| [dependencies] | ||
| borsh = "0.9" | ||
| arrayref = "0.3.6" | ||
| solana-program = "1.10.40" | ||
| pyth-sdk-solana = { path = "../../pyth-sdk-solana", version = "0.6.1" } | ||
|
|
||
| [dev-dependencies] | ||
| solana-sdk = "1.10.40" | ||
| solana-client = "1.10.40" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Pyth SDK Example Program for Solana | ||
|
|
||
| This is an example demonstrating how to read prices from Pyth on Solana. | ||
|
|
||
| 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. | ||
|
|
||
| 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 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 | ||
| 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_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))?; | ||
| ``` | ||
|
|
||
| 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`. | ||
|
|
||
| ```shell | ||
| # Enter the root directory of this example | ||
| > 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 | ||
yhzhang0128 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # Invoke the example contract | ||
| > scripts/invoke.ts | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| This directory holds the output of build-bpf. See scripts/build.sh. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| cargo build-bpf --bpf-out-dir ./build |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +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 | ||
yhzhang0128 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| cd scripts; npm install typescript; npm run build; node invoke.js |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| const fs = require('fs'); | ||
yhzhang0128 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const web3 = require("@solana/web3.js"); | ||
| const {struct, b, u8, blob} = require("@solana/buffer-layout"); | ||
|
|
||
| export const invoke = async (loan: string, collateral: string) => { | ||
| /* Obtain the contract keypair */ | ||
| var contract; | ||
| try { | ||
| let data = fs.readFileSync( | ||
| '../build/example_sol_contract-keypair.json' | ||
yhzhang0128 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ); | ||
| 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; | ||
| } | ||
|
|
||
| /* Prepare the 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 which creates an | ||
| * account storing the AdminConfig data for the instructions */ | ||
| let loanInfoSize = 1 + 32 + 32; | ||
| let dataAccount = web3.Keypair.generate(); | ||
| let dataCost = await conn.getMinimumBalanceForRentExemption(loanInfoSize); | ||
| const createInst = web3.SystemProgram.createAccount({ | ||
| lamports: dataCost, | ||
| space: loanInfoSize, | ||
| programId: contract.publicKey, | ||
| fromPubkey: payer.publicKey, | ||
| newAccountPubkey: dataAccount.publicKey, | ||
| }); | ||
|
|
||
| /* Prepare the accounts and instruction data for transactions */ | ||
| const dataKey = dataAccount.publicKey; | ||
| const loanKey = new web3.PublicKey(loan); | ||
| const collateralKey = new web3.PublicKey(collateral); | ||
| let accounts = | ||
| [{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}, | ||
| ]; | ||
|
|
||
| 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..."); | ||
| 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 */ | ||
| data: initData, | ||
| keys: accounts, | ||
| programId: contract.publicKey | ||
| }) | ||
| ); | ||
| let txInitSig = await web3.sendAndConfirmTransaction( | ||
| conn, txInit, [payer, dataAccount, contract] | ||
| ); | ||
| console.log("TxHash: " + txInitSig); | ||
|
|
||
| /* 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'); | ||
| 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, | ||
| keys: accounts, | ||
| programId: contract.publicKey | ||
| }) | ||
| ); | ||
| let txCheckSig = await web3.sendAndConfirmTransaction( | ||
| conn, txCheck, [payer, contract] | ||
| ); | ||
| 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: dataCost, | ||
| space: loanInfoSize, | ||
| programId: contract.publicKey, | ||
| fromPubkey: payer.publicKey, | ||
| newAccountPubkey: attackerDataAccount.publicKey, | ||
| }); | ||
|
|
||
| let txAttacker = new web3.Transaction({ feePayer: payer.publicKey }); | ||
| txAttacker.add( | ||
| attackerCreateInst, | ||
| new web3.TransactionInstruction({ | ||
| data: initData, | ||
| keys: accounts, | ||
| programId: contract.publicKey | ||
| }) | ||
| ); | ||
|
|
||
| var attacker_succeed = false, txAttackerSig; | ||
| try { | ||
| txAttackerSig = await web3.sendAndConfirmTransaction( | ||
| conn, txAttacker, [payer, attackerDataAccount, attacker] | ||
| ); | ||
| 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 */ | ||
| let ethToUSD = "EdVCmQ9FSPcVe5YySXDPCRmc8aDQLKJ9xvYBMZPie1Vw"; | ||
| let usdtToUSD = "38xoQ4oeJCBrcVvca2cGk7iV1dAfrmTR1kmhSCJQ8Jto"; | ||
| invoke(ethToUSD, usdtToUSD); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.