Skip to content

feat(price_feeds/starknet): simplify contract, use STRK, add CI #14

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 3 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions .github/workflows/ci-starknet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Starknet tests
on:
pull_request:
paths:
- price_feeds/starknet/**
push:
branches:
- main
paths:
- price_feeds/starknet/**
jobs:
check:
name: Starknet contract tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: price_feeds/starknet/send_usd/contract
steps:
- uses: actions/checkout@v3
- name: Install Scarb
uses: software-mansion/setup-scarb@v1
with:
tool-versions: price_feeds/starknet/send_usd/contract/.tool-versions
- name: Install Starknet Foundry
uses: foundry-rs/setup-snfoundry@v3
with:
tool-versions: price_feeds/starknet/send_usd/contract/.tool-versions
- name: Install Starkli
run: curl https://get.starkli.sh | sh && . ~/.config/.starkli/env && starkliup -v $(awk '/starkli/{print $2}' .tool-versions)
- name: Install Katana
run: curl -L https://install.dojoengine.org | bash && PATH="$PATH:$HOME/.config/.dojo/bin" dojoup -v $(awk '/dojo/{print $2}' .tool-versions)
- name: Check formatting
run: scarb fmt --check
- name: Build contract
run: scarb build
- name: Check ABI files
run: |
cat target/dev/send_usd_send_usd.contract_class.json | jq .abi > /tmp/send_usd.json
if ! cmp --silent /tmp/send_usd.json ../client/src/abi/send_usd.json ; then
>&2 echo ABI file mismatch for client/src/abi/send_usd.json
exit 1
fi
40 changes: 7 additions & 33 deletions price_feeds/starknet/send_usd/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion price_feeds/starknet/send_usd/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"dependencies": {
"@pythnetwork/price-service-client": "^1.9.0",
"@pythnetwork/pyth-starknet-js": "file:../../../../../pyth-crosschain/target_chains/starknet/sdk/js",
"@pythnetwork/pyth-starknet-js": "^0.1.0",
"starknet": "^6.9.0"
}
}
6 changes: 1 addition & 5 deletions price_feeds/starknet/send_usd/client/src/abi/send_usd.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,8 @@
"type": "core::starknet::contract_address::ContractAddress"
},
{
"name": "eth_erc20_address",
"name": "strk_erc20_address",
"type": "core::starknet::contract_address::ContractAddress"
},
{
"name": "eth_usd_price_id",
"type": "core::integer::u256"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion price_feeds/starknet/send_usd/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async function main() {
});

const priceId =
'0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace'; // ETH/USD
'0x6a182399ff70ccf3e06024898942028204125a819e519a335ffa4579e66cd870'; // STRK/USD

console.log('querying pyth update');
// Get the latest values of the price feeds as json objects.
Expand Down
20 changes: 7 additions & 13 deletions price_feeds/starknet/send_usd/client/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
{
"compilerOptions": {
"target": "esnext",
"target": "es2016",
"module": "commonjs",
"outDir": "./lib",
"rootDir": "src/",
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": [
"src",
"src/**/*.json",
],
"exclude": [
"node_modules",
"**/__tests__/*"
]
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"outDir": "./lib"
}
}
2 changes: 0 additions & 2 deletions price_feeds/starknet/send_usd/contract/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ edition = "2023_11"
[dependencies]
starknet = ">=2.5.4"
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.10.0" }

# TODO: replace with tag after release
pyth = { git = "https://github.com/pyth-network/pyth-crosschain.git", tag = "pyth-starknet-contract-v0.1.0" }

[[target.starknet-contract]]
12 changes: 2 additions & 10 deletions price_feeds/starknet/send_usd/contract/deploy/local_deploy
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,12 @@ fi
send_usd_hash=$(starkli declare --watch target/dev/send_usd_send_usd.contract_class.json)
${sleep}

# ETH/USD
price_feed_id=0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace

price_feed_u128_low=$(
python3 -c "print(${price_feed_id} % (1<<128))"
)
price_feed_u128_high=$(
python3 -c "print(${price_feed_id} // (1<<128))"
)
# STRK/USD
price_feed_id=0x6a182399ff70ccf3e06024898942028204125a819e519a335ffa4579e66cd870

send_usd_address=$(starkli deploy --watch "${send_usd_hash}" \
"${PYTH_ADDRESS}" \
"${fee_token_address}" \
"${price_feed_u128_low}" "${price_feed_u128_high}" \
)
${sleep}

Expand Down
42 changes: 19 additions & 23 deletions price_feeds/starknet/send_usd/contract/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use pyth::ByteBuffer;

#[starknet::interface]
pub trait ISendUsd<T> {
/// Sends ETH from the caller to the destination. The amount of ETH will be equivalent
/// to the specified amount of USD, converted using the last available ETH/USD price from Pyth.
/// `price_update` should be the latest available price update for the ETH/USD price feed.
/// Sends STRK from the caller to the destination. The amount of STRK will be equivalent
/// to the specified amount of USD, converted using the last available STRK/USD price from Pyth.
/// `price_update` should be the latest available price update for the STRK/USD price feed.
/// The caller needs to set up sufficient allowance for this contract.
fn send_usd(
ref self: T, destination: ContractAddress, amount_in_usd: u256, price_update: ByteBuffer
Expand All @@ -20,29 +20,23 @@ mod send_usd {
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcherTrait, IERC20CamelDispatcher};

const MAX_PRICE_AGE: u64 = 3600; // 1 hour
const WEI_PER_ETH: u256 = 1000000000000000000;
const TOKENS_PER_STRK: u256 = 1000000000000000000;

#[storage]
struct Storage {
pyth_address: ContractAddress,
eth_erc20_address: ContractAddress,
eth_usd_price_id: u256,
strk_erc20_address: ContractAddress,
}

/// Initializes the contract.
/// `pyth_address` is the address of the deployed Pyth account.
/// `eth_erc20_address` is the address of the ERC20 token account for the ETH token.
/// `eth_usd_price_id` is the ID of Pyth's price feed for ETH/USD.
/// `strk_erc20_address` is the address of the ERC20 token account for the STRK token.
#[constructor]
fn constructor(
ref self: ContractState,
pyth_address: ContractAddress,
eth_erc20_address: ContractAddress,
eth_usd_price_id: u256,
ref self: ContractState, pyth_address: ContractAddress, strk_erc20_address: ContractAddress,
) {
self.pyth_address.write(pyth_address);
self.eth_erc20_address.write(eth_erc20_address);
self.eth_usd_price_id.write(eth_usd_price_id);
self.strk_erc20_address.write(strk_erc20_address);
}

#[abi(embed_v0)]
Expand All @@ -54,34 +48,36 @@ mod send_usd {
price_update: ByteBuffer
) {
let pyth = IPythDispatcher { contract_address: self.pyth_address.read() };
let eth_erc20 = IERC20CamelDispatcher {
contract_address: self.eth_erc20_address.read()
let strk_erc20 = IERC20CamelDispatcher {
contract_address: self.strk_erc20_address.read()
};
let caller = get_caller_address();
let contract = get_contract_address();

let pyth_fee = pyth.get_update_fee(price_update.clone(), eth_erc20.contract_address);
if !eth_erc20.transferFrom(caller, contract, pyth_fee) {
let pyth_fee = pyth.get_update_fee(price_update.clone(), strk_erc20.contract_address);
if !strk_erc20.transferFrom(caller, contract, pyth_fee) {
panic_with_felt252('insufficient allowance for fee');
}
if !eth_erc20.approve(pyth.contract_address, pyth_fee) {
if !strk_erc20.approve(pyth.contract_address, pyth_fee) {
panic_with_felt252('approve failed');
}

pyth.update_price_feeds(price_update);

/// `strk_usd_price_id` is the ID of Pyth's price feed for STRK/USD.
let strk_usd_price_id =
0x6a182399ff70ccf3e06024898942028204125a819e519a335ffa4579e66cd870;
let price = pyth
.get_price_no_older_than(self.eth_usd_price_id.read(), MAX_PRICE_AGE)
.get_price_no_older_than(strk_usd_price_id, MAX_PRICE_AGE)
.unwrap_with_felt252();

let price_u64: u64 = price.price.try_into().unwrap();
let amount_in_wei = WEI_PER_ETH
let amount_in_strk = TOKENS_PER_STRK
* exp10((-price.expo).try_into().unwrap())
* amount_in_usd
/ price_u64.into();

let transfer_ok = eth_erc20
.transferFrom(caller, destination, amount_in_wei);
let transfer_ok = strk_erc20.transferFrom(caller, destination, amount_in_strk);
if !transfer_ok {
panic_with_felt252('insufficient allowance');
}
Expand Down
Loading