diff --git a/.github/workflows/ci-starknet.yml b/.github/workflows/ci-starknet.yml new file mode 100644 index 0000000..d7ce5c4 --- /dev/null +++ b/.github/workflows/ci-starknet.yml @@ -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 diff --git a/price_feeds/starknet/send_usd/client/package-lock.json b/price_feeds/starknet/send_usd/client/package-lock.json index bb7c936..3d2c3bb 100644 --- a/price_feeds/starknet/send_usd/client/package-lock.json +++ b/price_feeds/starknet/send_usd/client/package-lock.json @@ -6,7 +6,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" }, "devDependencies": { @@ -17,23 +17,6 @@ "typescript": "^5.4.5" } }, - "../../../../../pyth-crosschain/target_chains/starknet/sdk/js": { - "name": "@pythnetwork/pyth-starknet-js", - "version": "1.4.2", - "license": "Apache-2.0", - "devDependencies": { - "@pythnetwork/price-service-client": "^1.9.0", - "@types/node": "^18.11.18", - "@typescript-eslint/eslint-plugin": "^5.21.0", - "@typescript-eslint/parser": "^5.21.0", - "eslint": "^8.14.0", - "jest": "^29.4.1", - "prettier": "^2.6.2", - "starknet": "^6.9.0", - "ts-jest": "^29.0.5", - "typescript": "^4.6.3" - } - }, "node_modules/@babel/code-frame": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", @@ -372,8 +355,9 @@ } }, "node_modules/@pythnetwork/pyth-starknet-js": { - "resolved": "../../../../../pyth-crosschain/target_chains/starknet/sdk/js", - "link": true + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-starknet-js/-/pyth-starknet-js-0.1.0.tgz", + "integrity": "sha512-ObuxpHf5xwRO1Cc4TkDUFwGr8ORYTLUHiMyihez5WyS/WBqc6nb8mA3MXGyiv8I6dEEoqz+Hsw7Syx7q49wc4w==" }, "node_modules/@scure/base": { "version": "1.1.6", @@ -3962,19 +3946,9 @@ } }, "@pythnetwork/pyth-starknet-js": { - "version": "file:../../../../../pyth-crosschain/target_chains/starknet/sdk/js", - "requires": { - "@pythnetwork/price-service-client": "^1.9.0", - "@types/node": "^18.11.18", - "@typescript-eslint/eslint-plugin": "^5.21.0", - "@typescript-eslint/parser": "^5.21.0", - "eslint": "^8.14.0", - "jest": "^29.4.1", - "prettier": "^2.6.2", - "starknet": "^6.9.0", - "ts-jest": "^29.0.5", - "typescript": "^4.6.3" - } + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-starknet-js/-/pyth-starknet-js-0.1.0.tgz", + "integrity": "sha512-ObuxpHf5xwRO1Cc4TkDUFwGr8ORYTLUHiMyihez5WyS/WBqc6nb8mA3MXGyiv8I6dEEoqz+Hsw7Syx7q49wc4w==" }, "@scure/base": { "version": "1.1.6", diff --git a/price_feeds/starknet/send_usd/client/package.json b/price_feeds/starknet/send_usd/client/package.json index 2d35f2e..1ce6e3b 100644 --- a/price_feeds/starknet/send_usd/client/package.json +++ b/price_feeds/starknet/send_usd/client/package.json @@ -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" } } diff --git a/price_feeds/starknet/send_usd/client/src/abi/send_usd.json b/price_feeds/starknet/send_usd/client/src/abi/send_usd.json index f151c02..50e8ea7 100644 --- a/price_feeds/starknet/send_usd/client/src/abi/send_usd.json +++ b/price_feeds/starknet/send_usd/client/src/abi/send_usd.json @@ -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" } ] }, diff --git a/price_feeds/starknet/send_usd/client/src/index.ts b/price_feeds/starknet/send_usd/client/src/index.ts index 4fca6a3..5daeee2 100644 --- a/price_feeds/starknet/send_usd/client/src/index.ts +++ b/price_feeds/starknet/send_usd/client/src/index.ts @@ -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. diff --git a/price_feeds/starknet/send_usd/client/tsconfig.json b/price_feeds/starknet/send_usd/client/tsconfig.json index 5c7c759..b25dbff 100644 --- a/price_feeds/starknet/send_usd/client/tsconfig.json +++ b/price_feeds/starknet/send_usd/client/tsconfig.json @@ -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" + } } diff --git a/price_feeds/starknet/send_usd/contract/Scarb.toml b/price_feeds/starknet/send_usd/contract/Scarb.toml index 73c5563..0585abe 100644 --- a/price_feeds/starknet/send_usd/contract/Scarb.toml +++ b/price_feeds/starknet/send_usd/contract/Scarb.toml @@ -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]] diff --git a/price_feeds/starknet/send_usd/contract/deploy/local_deploy b/price_feeds/starknet/send_usd/contract/deploy/local_deploy index 3980448..3d6d968 100755 --- a/price_feeds/starknet/send_usd/contract/deploy/local_deploy +++ b/price_feeds/starknet/send_usd/contract/deploy/local_deploy @@ -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} diff --git a/price_feeds/starknet/send_usd/contract/src/lib.cairo b/price_feeds/starknet/send_usd/contract/src/lib.cairo index 824182d..5a49f60 100644 --- a/price_feeds/starknet/send_usd/contract/src/lib.cairo +++ b/price_feeds/starknet/send_usd/contract/src/lib.cairo @@ -3,9 +3,9 @@ use pyth::ByteBuffer; #[starknet::interface] pub trait ISendUsd { - /// 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 @@ -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)] @@ -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'); }