Skip to content

Commit 0f7141e

Browse files
authored
feat(target_chains/starknet): support SetFeeInToken instruction (#1669)
1 parent 58c5b76 commit 0f7141e

File tree

5 files changed

+170
-19
lines changed

5 files changed

+170
-19
lines changed

target_chains/starknet/contracts/src/pyth.cairo

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ mod pyth {
9090
pub struct FeeSet {
9191
pub old_fee: u256,
9292
pub new_fee: u256,
93+
pub token: ContractAddress,
9394
}
9495

9596
#[derive(Drop, Clone, Debug, PartialEq, Serde, starknet::Event)]
@@ -414,11 +415,10 @@ mod pyth {
414415
}
415416
match instruction.payload {
416417
GovernancePayload::SetFee(payload) => {
417-
let new_fee = apply_decimal_expo(payload.value, payload.expo);
418-
let old_fee = self.single_update_fee1.read();
419-
self.single_update_fee1.write(new_fee);
420-
let event = FeeSet { old_fee, new_fee };
421-
self.emit(event);
418+
self.set_fee(payload.value, payload.expo, self.fee_token_address1.read());
419+
},
420+
GovernancePayload::SetFeeInToken(payload) => {
421+
self.set_fee(payload.value, payload.expo, payload.token);
422422
},
423423
GovernancePayload::SetDataSources(payload) => {
424424
let new_data_sources = payload.sources;
@@ -716,6 +716,23 @@ mod pyth {
716716
};
717717
output_array
718718
}
719+
720+
fn set_fee(ref self: ContractState, value: u64, expo: u64, token: ContractAddress) {
721+
let new_fee = apply_decimal_expo(value, expo);
722+
let old_fee = if token == self.fee_token_address1.read() {
723+
let old_fee = self.single_update_fee1.read();
724+
self.single_update_fee1.write(new_fee);
725+
old_fee
726+
} else if token == self.fee_token_address2.read() {
727+
let old_fee = self.single_update_fee2.read();
728+
self.single_update_fee2.write(new_fee);
729+
old_fee
730+
} else {
731+
panic_with_felt252(GovernanceActionError::InvalidGovernanceMessage.into())
732+
};
733+
let event = FeeSet { old_fee, new_fee, token };
734+
self.emit(event);
735+
}
719736
}
720737

721738
fn apply_decimal_expo(value: u64, expo: u64) -> u256 {

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub enum GovernanceAction {
1919
SetValidPeriod,
2020
RequestGovernanceDataSourceTransfer,
2121
SetWormholeAddress,
22+
SetFeeInToken,
2223
}
2324

2425
impl U8TryIntoGovernanceAction of TryInto<u8, GovernanceAction> {
@@ -31,6 +32,7 @@ impl U8TryIntoGovernanceAction of TryInto<u8, GovernanceAction> {
3132
4 => GovernanceAction::SetValidPeriod,
3233
5 => GovernanceAction::RequestGovernanceDataSourceTransfer,
3334
6 => GovernanceAction::SetWormholeAddress,
35+
7 => GovernanceAction::SetFeeInToken,
3436
_ => { return Option::None; }
3537
};
3638
Option::Some(v)
@@ -52,6 +54,7 @@ pub enum GovernancePayload {
5254
// SetValidPeriod is unsupported
5355
RequestGovernanceDataSourceTransfer: RequestGovernanceDataSourceTransfer,
5456
SetWormholeAddress: SetWormholeAddress,
57+
SetFeeInToken: SetFeeInToken,
5558
}
5659

5760
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
@@ -60,6 +63,13 @@ pub struct SetFee {
6063
pub expo: u64,
6164
}
6265

66+
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
67+
pub struct SetFeeInToken {
68+
pub value: u64,
69+
pub expo: u64,
70+
pub token: ContractAddress,
71+
}
72+
6373
#[derive(Drop, Clone, Debug, PartialEq, Serde)]
6474
pub struct SetDataSources {
6575
pub sources: Array<DataSource>,
@@ -155,6 +165,22 @@ pub fn parse_instruction(payload: ByteBuffer) -> GovernanceInstruction {
155165
let expo = reader.read_u64();
156166
GovernancePayload::SetFee(SetFee { value, expo })
157167
},
168+
GovernanceAction::SetFeeInToken => {
169+
let value = reader.read_u64();
170+
let expo = reader.read_u64();
171+
let token_len = reader.read_u8();
172+
if token_len != 32 {
173+
panic_with_felt252(GovernanceActionError::InvalidGovernanceMessage.into());
174+
}
175+
let token: felt252 = reader
176+
.read_u256()
177+
.try_into()
178+
.expect(GovernanceActionError::InvalidGovernanceMessage.into());
179+
let token = token
180+
.try_into()
181+
.expect(GovernanceActionError::InvalidGovernanceMessage.into());
182+
GovernancePayload::SetFeeInToken(SetFeeInToken { value, expo, token })
183+
},
158184
GovernanceAction::SetValidPeriod => { panic_with_felt252('unimplemented') },
159185
GovernanceAction::SetWormholeAddress => {
160186
let address: felt252 = reader

target_chains/starknet/contracts/tests/data.cairo

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,19 @@ pub fn pyth_set_fee() -> ByteBuffer {
382382
ByteBufferImpl::new(bytes, 23)
383383
}
384384

385+
// A Pyth governance instruction to set fee signed by the test guardian #1.
386+
pub fn pyth_set_fee_in_token() -> ByteBuffer {
387+
let bytes = array![
388+
1766847064779994694408617232155063622446317599437785683244896979152308796,
389+
41831183904504604246915376354509245030219494606222324288494126672855141875,
390+
245200731728170526984869527586075617087934630006881191137945784647849869312,
391+
49565958604199796163020368,
392+
148907253456468655193350049927026865683852796092680336764850032905682767430,
393+
1535109346439504966152199052711447625482878604913825938427335,
394+
];
395+
ByteBufferImpl::new(bytes, 25)
396+
}
397+
385398
// A Pyth governance instruction to set data sources signed by the test guardian #1.
386399
pub fn pyth_set_data_sources() -> ByteBuffer {
387400
let bytes = array![

target_chains/starknet/contracts/tests/pyth.cairo

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ fn decode_event(mut event: Event) -> PythEvent {
5252
};
5353
PythEvent::PriceFeedUpdated(event)
5454
} else if key0 == event_name_hash('FeeSet') {
55-
let event = FeeSet { old_fee: event.data.pop_u256(), new_fee: event.data.pop_u256(), };
55+
let event = FeeSet {
56+
old_fee: event.data.pop_u256(), new_fee: event.data.pop_u256(), token: event.data.pop(),
57+
};
5658
PythEvent::FeeSet(event)
5759
} else if key0 == event_name_hash('DataSourcesSet') {
5860
let event = DataSourcesSet {
@@ -692,11 +694,16 @@ fn test_governance_set_fee_works() {
692694
let (from, event) = spy.events.pop_front().unwrap();
693695
assert!(from == pyth.contract_address);
694696
let event = decode_event(event);
695-
let expected = FeeSet { old_fee: 1000, new_fee: 4200, };
697+
let expected = FeeSet {
698+
old_fee: 1000, new_fee: 4200, token: ctx.fee_contract.contract_address
699+
};
696700
assert!(event == PythEvent::FeeSet(expected));
697701

698702
let fee2 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
699703
assert!(fee2 == 4200);
704+
let fee2_alt = pyth
705+
.get_update_fee(data::test_price_update2(), ctx.fee_contract2.contract_address);
706+
assert!(fee2_alt == 2000);
700707

701708
start_prank(CheatTarget::One(pyth.contract_address), user);
702709
pyth.update_price_feeds(data::test_price_update2());
@@ -709,6 +716,62 @@ fn test_governance_set_fee_works() {
709716
assert!(last_price.price == 6281522520745);
710717
}
711718

719+
#[test]
720+
fn test_governance_set_fee_in_token_works() {
721+
let ctx = deploy_test();
722+
let pyth = ctx.pyth;
723+
let fee_contract = ctx.fee_contract;
724+
let user = ctx.user;
725+
726+
let fee1 = pyth.get_update_fee(data::test_price_update1(), ctx.fee_contract.contract_address);
727+
assert!(fee1 == 1000);
728+
ctx.approve_fee(1000);
729+
730+
let mut balance = fee_contract.balanceOf(user);
731+
start_prank(CheatTarget::One(pyth.contract_address), user);
732+
pyth.update_price_feeds(data::test_price_update1());
733+
stop_prank(CheatTarget::One(pyth.contract_address));
734+
let new_balance = fee_contract.balanceOf(user);
735+
assert!(balance - new_balance == 1000);
736+
balance = new_balance;
737+
let last_price = pyth
738+
.get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
739+
.unwrap_with_felt252();
740+
assert!(last_price.price == 6281060000000);
741+
742+
let mut spy = spy_events(SpyOn::One(pyth.contract_address));
743+
744+
pyth.execute_governance_instruction(data::pyth_set_fee_in_token());
745+
746+
spy.fetch_events();
747+
assert!(spy.events.len() == 1);
748+
let (from, event) = spy.events.pop_front().unwrap();
749+
assert!(from == pyth.contract_address);
750+
let event = decode_event(event);
751+
let expected = FeeSet {
752+
old_fee: 2000, new_fee: 4200, token: ctx.fee_contract2.contract_address
753+
};
754+
assert!(event == PythEvent::FeeSet(expected));
755+
756+
let fee2 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
757+
assert!(fee2 == 1000);
758+
let fee2_alt = pyth
759+
.get_update_fee(data::test_price_update2(), ctx.fee_contract2.contract_address);
760+
assert!(fee2_alt == 4200);
761+
ctx.approve_fee2(4200);
762+
763+
let balance2 = ctx.fee_contract2.balanceOf(user);
764+
start_prank(CheatTarget::One(pyth.contract_address), user);
765+
pyth.update_price_feeds(data::test_price_update2());
766+
stop_prank(CheatTarget::One(pyth.contract_address));
767+
let new_balance2 = ctx.fee_contract2.balanceOf(user);
768+
assert!(balance2 - new_balance2 == 4200);
769+
let last_price = pyth
770+
.get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
771+
.unwrap_with_felt252();
772+
assert!(last_price.price == 6281522520745);
773+
}
774+
712775
#[test]
713776
#[fuzzer(runs: 100, seed: 0)]
714777
#[should_panic]
@@ -806,8 +869,8 @@ fn test_governance_set_wormhole_works() {
806869

807870
let user = 'user'.try_into().unwrap();
808871
let fee_class = declare("ERC20");
809-
let fee_contract = deploy_fee_contract(fee_class, user);
810-
let fee_contract2 = deploy_fee_contract(fee_class, user);
872+
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
873+
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
811874
let pyth = deploy_pyth_default(
812875
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
813876
);
@@ -892,8 +955,8 @@ fn test_rejects_set_wormhole_without_deploying() {
892955

893956
let user = 'user'.try_into().unwrap();
894957
let fee_class = declare("ERC20");
895-
let fee_contract = deploy_fee_contract(fee_class, user);
896-
let fee_contract2 = deploy_fee_contract(fee_class, user);
958+
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
959+
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
897960
let pyth = deploy_pyth_default(
898961
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
899962
);
@@ -912,8 +975,8 @@ fn test_rejects_set_wormhole_with_incompatible_guardians() {
912975

913976
let user = 'user'.try_into().unwrap();
914977
let fee_class = declare("ERC20");
915-
let fee_contract = deploy_fee_contract(fee_class, user);
916-
let fee_contract2 = deploy_fee_contract(fee_class, user);
978+
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
979+
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
917980
let pyth = deploy_pyth_default(
918981
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
919982
);
@@ -1058,8 +1121,8 @@ fn deploy_test() -> Context {
10581121
let user = 'user'.try_into().unwrap();
10591122
let wormhole = super::wormhole::deploy_with_test_guardian();
10601123
let fee_class = declare("ERC20");
1061-
let fee_contract = deploy_fee_contract(fee_class, user);
1062-
let fee_contract2 = deploy_fee_contract(fee_class, user);
1124+
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
1125+
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
10631126
let pyth = deploy_pyth_default(
10641127
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
10651128
);
@@ -1070,8 +1133,8 @@ fn deploy_mainnet() -> Context {
10701133
let user = 'user'.try_into().unwrap();
10711134
let wormhole = super::wormhole::deploy_with_mainnet_guardians();
10721135
let fee_class = declare("ERC20");
1073-
let fee_contract = deploy_fee_contract(fee_class, user);
1074-
let fee_contract2 = deploy_fee_contract(fee_class, user);
1136+
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
1137+
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
10751138
let pyth = deploy_pyth_default(
10761139
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
10771140
);
@@ -1125,12 +1188,21 @@ fn deploy_pyth(
11251188
IPythDispatcher { contract_address }
11261189
}
11271190

1128-
fn deploy_fee_contract(class: ContractClass, recipient: ContractAddress) -> IERC20CamelDispatcher {
1191+
fn fee_address1() -> ContractAddress {
1192+
0x1010.try_into().unwrap()
1193+
}
1194+
fn fee_address2() -> ContractAddress {
1195+
0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7.try_into().unwrap()
1196+
}
1197+
1198+
fn deploy_fee_contract(
1199+
class: ContractClass, at: ContractAddress, recipient: ContractAddress
1200+
) -> IERC20CamelDispatcher {
11291201
let mut args = array![];
11301202
let name: ByteArray = "eth";
11311203
let symbol: ByteArray = "eth";
11321204
(name, symbol, 100000_u256, recipient).serialize(ref args);
1133-
let contract_address = match class.deploy(@args) {
1205+
let contract_address = match class.deploy_at(@args, at) {
11341206
Result::Ok(v) => { v },
11351207
Result::Err(err) => { panic(err.panic_data) },
11361208
};

target_chains/starknet/tools/test_vaas/src/bin/generate_test_data.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,29 @@ fn main() {
200200
"A Pyth governance instruction to set fee signed by the test guardian #1.",
201201
);
202202

203+
let pyth_set_fee_in_token_payload = vec![
204+
80, 84, 71, 77, 1, 7, 234, 147, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 2, 32, 4,
205+
157, 54, 87, 13, 78, 70, 244, 142, 153, 103, 75, 211, 252, 200, 70, 68, 221, 214, 185, 111,
206+
124, 116, 27, 21, 98, 184, 47, 158, 0, 77, 199,
207+
];
208+
let pyth_set_fee_in_token = serialize_vaa(guardians.sign_vaa(
209+
&[0],
210+
VaaBody {
211+
timestamp: 1,
212+
nonce: 2,
213+
emitter_chain: 1,
214+
emitter_address: u256_to_be(41.into()).into(),
215+
sequence: 1.try_into().unwrap(),
216+
consistency_level: 6,
217+
payload: PayloadKind::Binary(pyth_set_fee_in_token_payload.clone()),
218+
},
219+
));
220+
print_as_cairo_fn(
221+
&pyth_set_fee_in_token,
222+
"pyth_set_fee_in_token",
223+
"A Pyth governance instruction to set fee signed by the test guardian #1.",
224+
);
225+
203226
let pyth_set_data_sources = serialize_vaa(guardians.sign_vaa(
204227
&[0],
205228
VaaBody {

0 commit comments

Comments
 (0)