Skip to content

Commit 07306ee

Browse files
authored
feat(target_chains/starknet): add remaining pyth contract getters (#1631)
* feat(target_chains/starknet): add price_feed_exists() * feat(target_chains/starknet): add remaining pyth contract getters * refactor(target_chains/starknet): rename fee_contract_address to fee_token_address
1 parent eb393ed commit 07306ee

File tree

4 files changed

+167
-14
lines changed

4 files changed

+167
-14
lines changed

target_chains/starknet/contracts/deploy/local_deploy

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ scarb build
2020
wormhole_hash=$(starkli declare target/dev/pyth_wormhole.contract_class.json)
2121

2222
# predeployed fee token contract in katana
23-
fee_contract_address=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7
23+
fee_token_address=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7
2424

2525
# deploying wormhole with mainnet guardians
2626

@@ -63,7 +63,7 @@ pyth_hash=$(starkli declare target/dev/pyth_pyth.contract_class.json)
6363
${sleep}
6464
pyth_address=$(starkli deploy "${pyth_hash}" \
6565
"${wormhole_address}" \
66-
"${fee_contract_address}" \
66+
"${fee_token_address}" \
6767
1000 0 `# fee amount` \
6868
1 `# num_data_sources` \
6969
26 `# emitter_chain_id` \
@@ -74,7 +74,7 @@ pyth_address=$(starkli deploy "${pyth_hash}" \
7474
)
7575

7676
${sleep}
77-
starkli invoke "${fee_contract_address}" approve "${pyth_address}" 1000 0
77+
starkli invoke "${fee_token_address}" approve "${pyth_address}" 1000 0
7878

7979
${sleep}
8080
starkli invoke --watch --log-traffic "${pyth_address}" update_price_feeds \

target_chains/starknet/contracts/src/pyth.cairo

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod errors;
22
mod interface;
33
mod price_update;
4-
mod governance;
4+
pub mod governance;
55

66
mod fake_upgrades;
77

@@ -97,7 +97,7 @@ mod pyth {
9797
#[storage]
9898
struct Storage {
9999
wormhole_address: ContractAddress,
100-
fee_contract_address: ContractAddress,
100+
fee_token_address: ContractAddress,
101101
single_update_fee: u256,
102102
data_sources: LegacyMap<usize, DataSource>,
103103
num_data_sources: usize,
@@ -115,28 +115,28 @@ mod pyth {
115115
///
116116
/// `wormhole_address` is the address of the deployed Wormhole contract implemented in the `wormhole` module.
117117
///
118-
/// `fee_contract_address` is the address of the ERC20 token used to pay fees to Pyth
118+
/// `fee_token_address` is the address of the ERC20 token used to pay fees to Pyth
119119
/// for price updates. There is no native token on Starknet so an ERC20 contract has to be used.
120120
/// On Katana, an ETH fee contract is pre-deployed. On Starknet testnet, ETH and STRK fee tokens are
121121
/// available. Any other ERC20-compatible token can also be used.
122122
/// In a Starknet Forge testing environment, a fee contract must be deployed manually.
123123
///
124-
/// `single_update_fee` is the number of tokens of `fee_contract_address` charged for a single price update.
124+
/// `single_update_fee` is the number of tokens of `fee_token_address` charged for a single price update.
125125
///
126126
/// `data_sources` is the list of Wormhole data sources accepted by this contract.
127127
#[constructor]
128128
fn constructor(
129129
ref self: ContractState,
130130
wormhole_address: ContractAddress,
131-
fee_contract_address: ContractAddress,
131+
fee_token_address: ContractAddress,
132132
single_update_fee: u256,
133133
data_sources: Array<DataSource>,
134134
governance_emitter_chain_id: u16,
135135
governance_emitter_address: u256,
136136
governance_initial_sequence: u64,
137137
) {
138138
self.wormhole_address.write(wormhole_address);
139-
self.fee_contract_address.write(fee_contract_address);
139+
self.fee_token_address.write(fee_token_address);
140140
self.single_update_fee.write(single_update_fee);
141141
self.write_data_sources(@data_sources);
142142
self
@@ -239,6 +239,16 @@ mod pyth {
239239
Result::Ok(feed)
240240
}
241241

242+
fn price_feed_exists(self: @ContractState, price_id: u256) -> bool {
243+
let info = self.latest_price_info.read(price_id);
244+
info.publish_time != 0
245+
}
246+
247+
fn latest_price_info_publish_time(self: @ContractState, price_id: u256) -> u64 {
248+
let info = self.latest_price_info.read(price_id);
249+
info.publish_time
250+
}
251+
242252
fn update_price_feeds(ref self: ContractState, data: ByteArray) {
243253
self.update_price_feeds_internal(data, array![], 0, 0, false);
244254
}
@@ -300,6 +310,54 @@ mod pyth {
300310
)
301311
}
302312

313+
fn wormhole_address(self: @ContractState) -> ContractAddress {
314+
self.wormhole_address.read()
315+
}
316+
317+
fn fee_token_address(self: @ContractState) -> ContractAddress {
318+
self.fee_token_address.read()
319+
}
320+
321+
fn get_single_update_fee(self: @ContractState) -> u256 {
322+
self.single_update_fee.read()
323+
}
324+
325+
fn valid_data_sources(self: @ContractState) -> Array<DataSource> {
326+
let count = self.num_data_sources.read();
327+
let mut i = 0;
328+
let mut output = array![];
329+
while i < count {
330+
output.append(self.data_sources.read(i));
331+
i += 1;
332+
};
333+
output
334+
}
335+
336+
fn is_valid_data_source(self: @ContractState, source: DataSource) -> bool {
337+
self.is_valid_data_source.read(source)
338+
}
339+
340+
fn governance_data_source(self: @ContractState) -> DataSource {
341+
self.governance_data_source.read()
342+
}
343+
344+
fn is_valid_governance_data_source(self: @ContractState, source: DataSource) -> bool {
345+
self.governance_data_source.read() == source
346+
}
347+
348+
fn last_executed_governance_sequence(self: @ContractState) -> u64 {
349+
self.last_executed_governance_sequence.read()
350+
}
351+
352+
fn governance_data_source_index(self: @ContractState) -> u32 {
353+
self.governance_data_source_index.read()
354+
}
355+
356+
fn chain_id(self: @ContractState) -> u16 {
357+
let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() };
358+
wormhole.chain_id()
359+
}
360+
303361
fn execute_governance_instruction(ref self: ContractState, data: ByteArray) {
304362
let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() };
305363
let vm = wormhole.parse_and_verify_vm(data.clone());
@@ -555,7 +613,7 @@ mod pyth {
555613
let num_updates = reader.read_u8();
556614
let total_fee = self.get_total_fee(num_updates);
557615
let fee_contract = IERC20CamelDispatcher {
558-
contract_address: self.fee_contract_address.read()
616+
contract_address: self.fee_token_address.read()
559617
};
560618
let execution_info = get_execution_info().unbox();
561619
let caller = execution_info.caller_address;

target_chains/starknet/contracts/src/pyth/interface.cairo

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::{GetPriceUnsafeError, GetPriceNoOlderThanError};
22
use pyth::byte_array::ByteArray;
3+
use core::starknet::ContractAddress;
34

45
#[starknet::interface]
56
pub trait IPyth<T> {
@@ -15,6 +16,9 @@ pub trait IPyth<T> {
1516
self: @T, price_id: u256, age: u64
1617
) -> Result<PriceFeed, GetPriceNoOlderThanError>;
1718
fn query_price_feed_unsafe(self: @T, price_id: u256) -> Result<PriceFeed, GetPriceUnsafeError>;
19+
fn price_feed_exists(self: @T, price_id: u256) -> bool;
20+
fn latest_price_info_publish_time(self: @T, price_id: u256) -> u64;
21+
1822
fn update_price_feeds(ref self: T, data: ByteArray);
1923
fn update_price_feeds_if_necessary(
2024
ref self: T, update: ByteArray, required_publish_times: Array<PriceFeedPublishTime>
@@ -30,6 +34,17 @@ pub trait IPyth<T> {
3034
ref self: T, data: ByteArray, price_ids: Array<u256>, publish_time: u64, max_staleness: u64,
3135
) -> Array<PriceFeed>;
3236
fn get_update_fee(self: @T, data: ByteArray) -> u256;
37+
fn wormhole_address(self: @T) -> ContractAddress;
38+
fn fee_token_address(self: @T) -> ContractAddress;
39+
fn get_single_update_fee(self: @T) -> u256;
40+
fn valid_data_sources(self: @T) -> Array<DataSource>;
41+
fn is_valid_data_source(self: @T, source: DataSource) -> bool;
42+
fn governance_data_source(self: @T) -> DataSource;
43+
fn is_valid_governance_data_source(self: @T, source: DataSource) -> bool;
44+
fn last_executed_governance_sequence(self: @T) -> u64;
45+
fn governance_data_source_index(self: @T) -> u32;
46+
fn chain_id(self: @T) -> u16;
47+
3348
fn execute_governance_instruction(ref self: T, data: ByteArray);
3449
fn version(self: @T) -> felt252;
3550
fn pyth_upgradable_magic(self: @T) -> u32;

target_chains/starknet/contracts/tests/pyth.cairo

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,83 @@ fn decode_event(mut event: Event) -> PythEvent {
8383
output
8484
}
8585

86+
#[test]
87+
fn test_getters_work() {
88+
let user = 'user'.try_into().unwrap();
89+
let wormhole = super::wormhole::deploy_with_mainnet_guardians();
90+
let fee_contract = deploy_fee_contract(user);
91+
let pyth = deploy_default(wormhole.contract_address, fee_contract.contract_address);
92+
93+
assert!(pyth.wormhole_address() == wormhole.contract_address);
94+
assert!(pyth.fee_token_address() == fee_contract.contract_address);
95+
assert!(pyth.get_single_update_fee() == 1000);
96+
assert!(
97+
pyth
98+
.valid_data_sources() == array![
99+
DataSource {
100+
emitter_chain_id: 26,
101+
emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
102+
}
103+
]
104+
);
105+
assert!(
106+
pyth
107+
.is_valid_data_source(
108+
DataSource {
109+
emitter_chain_id: 26,
110+
emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
111+
}
112+
)
113+
);
114+
assert!(
115+
!pyth.is_valid_data_source(DataSource { emitter_chain_id: 26, emitter_address: 0xbad, })
116+
);
117+
assert!(
118+
!pyth
119+
.is_valid_data_source(
120+
DataSource {
121+
emitter_chain_id: 27,
122+
emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
123+
}
124+
)
125+
);
126+
assert!(
127+
pyth.governance_data_source() == DataSource { emitter_chain_id: 1, emitter_address: 41, }
128+
);
129+
assert!(
130+
pyth
131+
.is_valid_governance_data_source(
132+
DataSource { emitter_chain_id: 1, emitter_address: 41, }
133+
)
134+
);
135+
assert!(
136+
!pyth
137+
.is_valid_governance_data_source(
138+
DataSource { emitter_chain_id: 1, emitter_address: 42, }
139+
)
140+
);
141+
assert!(pyth.last_executed_governance_sequence() == 0);
142+
assert!(pyth.governance_data_source_index() == 0);
143+
assert!(pyth.chain_id() == 60051);
144+
}
145+
86146
#[test]
87147
fn update_price_feeds_works() {
88148
let user = 'user'.try_into().unwrap();
89149
let wormhole = super::wormhole::deploy_with_mainnet_guardians();
90150
let fee_contract = deploy_fee_contract(user);
91151
let pyth = deploy_default(wormhole.contract_address, fee_contract.contract_address);
92152

153+
assert!(
154+
!pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
155+
);
156+
assert!(
157+
pyth
158+
.latest_price_info_publish_time(
159+
0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
160+
) == 0
161+
);
162+
93163
let fee = pyth.get_update_fee(data::good_update1());
94164
assert!(fee == 1000);
95165

@@ -144,6 +214,16 @@ fn update_price_feeds_works() {
144214
assert!(feed.ema_price.conf == 4096812700);
145215
assert!(feed.ema_price.expo == -8);
146216
assert!(feed.ema_price.publish_time == 1712589206);
217+
218+
assert!(
219+
pyth.price_feed_exists(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
220+
);
221+
assert!(
222+
pyth
223+
.latest_price_info_publish_time(
224+
0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
225+
) == 1712589206
226+
);
147227
}
148228

149229
#[test]
@@ -867,11 +947,11 @@ fn test_upgrade_rejects_wrong_magic() {
867947

868948

869949
fn deploy_default(
870-
wormhole_address: ContractAddress, fee_contract_address: ContractAddress
950+
wormhole_address: ContractAddress, fee_token_address: ContractAddress
871951
) -> IPythDispatcher {
872952
deploy(
873953
wormhole_address,
874-
fee_contract_address,
954+
fee_token_address,
875955
1000,
876956
array![
877957
DataSource {
@@ -887,15 +967,15 @@ fn deploy_default(
887967

888968
fn deploy(
889969
wormhole_address: ContractAddress,
890-
fee_contract_address: ContractAddress,
970+
fee_token_address: ContractAddress,
891971
single_update_fee: u256,
892972
data_sources: Array<DataSource>,
893973
governance_emitter_chain_id: u16,
894974
governance_emitter_address: u256,
895975
governance_initial_sequence: u64,
896976
) -> IPythDispatcher {
897977
let mut args = array![];
898-
(wormhole_address, fee_contract_address, single_update_fee).serialize(ref args);
978+
(wormhole_address, fee_token_address, single_update_fee).serialize(ref args);
899979
(data_sources, governance_emitter_chain_id).serialize(ref args);
900980
(governance_emitter_address, governance_initial_sequence).serialize(ref args);
901981
let contract = declare("pyth");

0 commit comments

Comments
 (0)