Skip to content
Merged
Show file tree
Hide file tree
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 Sep 14, 2022
8aed7a0
Add scripts for invocation in examples/sol-contract
yhzhang0128 Sep 14, 2022
0677a3e
Add readme in examples/sol-contract
yhzhang0128 Sep 14, 2022
1b9a91e
Add overflow checks in examples/sol-contract
yhzhang0128 Sep 14, 2022
685bffd
Cleanup and add comments in examples/sol-contract
yhzhang0128 Sep 14, 2022
b045b70
Remove hard-coded contract hash in examples/sol-contract
yhzhang0128 Sep 14, 2022
7432028
Improve comments wording in examples/sol-contract
yhzhang0128 Sep 14, 2022
d5f6d75
Update readme of examples/sol-contract
yhzhang0128 Sep 14, 2022
eba9da5
Remove some unnecessary dependencies in examples/sol-contract
yhzhang0128 Sep 14, 2022
aac5b9d
Remove the unwrap() in the code of examples/sol-contract
yhzhang0128 Sep 14, 2022
d0b28d9
Fix a grammar error
yhzhang0128 Sep 14, 2022
8ff6ee1
Succeed in adding state.rs and an initialization instruction
yhzhang0128 Oct 3, 2022
004b136
Cleanup
yhzhang0128 Oct 3, 2022
39d8300
Cleanup
yhzhang0128 Oct 3, 2022
32692b8
Cleanup
yhzhang0128 Oct 3, 2022
30ef43f
Add confidence interval
yhzhang0128 Oct 3, 2022
7235d20
Minor
yhzhang0128 Oct 3, 2022
d026e35
Minor
yhzhang0128 Oct 3, 2022
715de4d
Cleanup the contract
yhzhang0128 Oct 3, 2022
017030b
Cleanup the client-side code
yhzhang0128 Oct 3, 2022
9458fc0
Cleanup the contract code
yhzhang0128 Oct 3, 2022
ed7791e
Update readme
yhzhang0128 Oct 3, 2022
59dff00
Minor
yhzhang0128 Oct 3, 2022
829d38b
Minor
yhzhang0128 Oct 3, 2022
6d580bc
Add package-lock.json
yhzhang0128 Oct 4, 2022
eb7462b
Some renaming
yhzhang0128 Oct 4, 2022
9d86e9f
Hardcode loan/collateral quantity in Loan2Value instruction
yhzhang0128 Oct 5, 2022
0bdb6c1
Make loan_qty and collateral_qty parameters of instruction
yhzhang0128 Oct 5, 2022
bca85ae
Minor
yhzhang0128 Oct 5, 2022
6f4a4c0
Update dependency requirements for Solana related dependencies (#75)
dadepo Oct 6, 2022
37604d2
Update the price schema (#76)
ali-behjati Oct 6, 2022
4b7008c
Add example contract for solana
yhzhang0128 Sep 14, 2022
3a03ac3
Add scripts for invocation in examples/sol-contract
yhzhang0128 Sep 14, 2022
33c8afa
Add readme in examples/sol-contract
yhzhang0128 Sep 14, 2022
969b8ff
Add overflow checks in examples/sol-contract
yhzhang0128 Sep 14, 2022
81a4442
Cleanup and add comments in examples/sol-contract
yhzhang0128 Sep 14, 2022
ae47349
Remove hard-coded contract hash in examples/sol-contract
yhzhang0128 Sep 14, 2022
dc4d163
Improve comments wording in examples/sol-contract
yhzhang0128 Sep 14, 2022
908a3bc
Update readme of examples/sol-contract
yhzhang0128 Sep 14, 2022
d2ccf1a
Remove some unnecessary dependencies in examples/sol-contract
yhzhang0128 Sep 14, 2022
b718bb5
Remove the unwrap() in the code of examples/sol-contract
yhzhang0128 Sep 14, 2022
063f20a
Fix a grammar error
yhzhang0128 Sep 14, 2022
a6f9473
Succeed in adding state.rs and an initialization instruction
yhzhang0128 Oct 3, 2022
8f3f76d
Cleanup
yhzhang0128 Oct 3, 2022
e9911f7
Cleanup
yhzhang0128 Oct 3, 2022
0c62d7f
Cleanup
yhzhang0128 Oct 3, 2022
8ceb10d
Add confidence interval
yhzhang0128 Oct 3, 2022
0ffe5d5
Minor
yhzhang0128 Oct 3, 2022
633f266
Minor
yhzhang0128 Oct 3, 2022
a695622
Cleanup the contract
yhzhang0128 Oct 3, 2022
5e6d075
Cleanup the client-side code
yhzhang0128 Oct 3, 2022
5093ca1
Cleanup the contract code
yhzhang0128 Oct 3, 2022
38867b6
Update readme
yhzhang0128 Oct 3, 2022
26ed735
Minor
yhzhang0128 Oct 3, 2022
3406cff
Minor
yhzhang0128 Oct 3, 2022
b185b2c
Add package-lock.json
yhzhang0128 Oct 4, 2022
cb1451b
Some renaming
yhzhang0128 Oct 4, 2022
558dd0c
Hardcode loan/collateral quantity in Loan2Value instruction
yhzhang0128 Oct 5, 2022
e0d03b7
Make loan_qty and collateral_qty parameters of instruction
yhzhang0128 Oct 5, 2022
cb4bbcc
Minor
yhzhang0128 Oct 5, 2022
ebbaa16
Merge branch 'example-rs' of github.com:yhzhang0128/pyth-sdk-rs into …
yhzhang0128 Oct 8, 2022
87fe21f
Change solana version to 1.10.40, same as the Pyth SDK
yhzhang0128 Oct 9, 2022
834aa3d
Only use i64 to calculate the values, before normalizing with exponents
yhzhang0128 Oct 9, 2022
3cc600f
Normalize the values with the exponents
yhzhang0128 Oct 9, 2022
ef46095
Use borsh library for state serialize/deserialize
yhzhang0128 Oct 10, 2022
833a3fc
Resolve a few minor issues, such as renaming, from Ali's comments
yhzhang0128 Oct 10, 2022
9fc162d
Add more check in the Init instruction
yhzhang0128 Oct 10, 2022
6045e74
Fix a bug in client
yhzhang0128 Oct 10, 2022
ba7217a
Update the sol-contract example readme
yhzhang0128 Oct 10, 2022
9195001
Minor in readme
yhzhang0128 Oct 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pyth-sdk-solana.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ members = [
"pyth-sdk-solana/test-contract",
"pyth-sdk-cw",
"examples/cw-contract",
"examples/sol-contract"
]
5 changes: 5 additions & 0 deletions examples/sol-contract/.gitignore
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/
18 changes: 18 additions & 0 deletions examples/sol-contract/Cargo.toml
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"
53 changes: 53 additions & 0 deletions examples/sol-contract/README.md
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
# Invoke the example contract
> scripts/invoke.ts
```
1 change: 1 addition & 0 deletions examples/sol-contract/build/README.md
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.
1 change: 1 addition & 0 deletions examples/sol-contract/scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cargo build-bpf --bpf-out-dir ./build
4 changes: 4 additions & 0 deletions examples/sol-contract/scripts/deploy.sh
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
1 change: 1 addition & 0 deletions examples/sol-contract/scripts/invoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cd scripts; npm install typescript; npm run build; node invoke.js
144 changes: 144 additions & 0 deletions examples/sol-contract/scripts/invoke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const fs = require('fs');
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'
);
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);
Loading