Skip to content

Commit

Permalink
feat(target_chains/starknet): support SetFeeInToken instruction (#1669)
Browse files Browse the repository at this point in the history
  • Loading branch information
Riateche authored Jun 7, 2024
1 parent 58c5b76 commit 0f7141e
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 19 deletions.
27 changes: 22 additions & 5 deletions target_chains/starknet/contracts/src/pyth.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ mod pyth {
pub struct FeeSet {
pub old_fee: u256,
pub new_fee: u256,
pub token: ContractAddress,
}

#[derive(Drop, Clone, Debug, PartialEq, Serde, starknet::Event)]
Expand Down Expand Up @@ -414,11 +415,10 @@ mod pyth {
}
match instruction.payload {
GovernancePayload::SetFee(payload) => {
let new_fee = apply_decimal_expo(payload.value, payload.expo);
let old_fee = self.single_update_fee1.read();
self.single_update_fee1.write(new_fee);
let event = FeeSet { old_fee, new_fee };
self.emit(event);
self.set_fee(payload.value, payload.expo, self.fee_token_address1.read());
},
GovernancePayload::SetFeeInToken(payload) => {
self.set_fee(payload.value, payload.expo, payload.token);
},
GovernancePayload::SetDataSources(payload) => {
let new_data_sources = payload.sources;
Expand Down Expand Up @@ -716,6 +716,23 @@ mod pyth {
};
output_array
}

fn set_fee(ref self: ContractState, value: u64, expo: u64, token: ContractAddress) {
let new_fee = apply_decimal_expo(value, expo);
let old_fee = if token == self.fee_token_address1.read() {
let old_fee = self.single_update_fee1.read();
self.single_update_fee1.write(new_fee);
old_fee
} else if token == self.fee_token_address2.read() {
let old_fee = self.single_update_fee2.read();
self.single_update_fee2.write(new_fee);
old_fee
} else {
panic_with_felt252(GovernanceActionError::InvalidGovernanceMessage.into())
};
let event = FeeSet { old_fee, new_fee, token };
self.emit(event);
}
}

fn apply_decimal_expo(value: u64, expo: u64) -> u256 {
Expand Down
26 changes: 26 additions & 0 deletions target_chains/starknet/contracts/src/pyth/governance.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub enum GovernanceAction {
SetValidPeriod,
RequestGovernanceDataSourceTransfer,
SetWormholeAddress,
SetFeeInToken,
}

impl U8TryIntoGovernanceAction of TryInto<u8, GovernanceAction> {
Expand All @@ -31,6 +32,7 @@ impl U8TryIntoGovernanceAction of TryInto<u8, GovernanceAction> {
4 => GovernanceAction::SetValidPeriod,
5 => GovernanceAction::RequestGovernanceDataSourceTransfer,
6 => GovernanceAction::SetWormholeAddress,
7 => GovernanceAction::SetFeeInToken,
_ => { return Option::None; }
};
Option::Some(v)
Expand All @@ -52,6 +54,7 @@ pub enum GovernancePayload {
// SetValidPeriod is unsupported
RequestGovernanceDataSourceTransfer: RequestGovernanceDataSourceTransfer,
SetWormholeAddress: SetWormholeAddress,
SetFeeInToken: SetFeeInToken,
}

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

#[derive(Drop, Clone, Debug, PartialEq, Serde)]
pub struct SetFeeInToken {
pub value: u64,
pub expo: u64,
pub token: ContractAddress,
}

#[derive(Drop, Clone, Debug, PartialEq, Serde)]
pub struct SetDataSources {
pub sources: Array<DataSource>,
Expand Down Expand Up @@ -155,6 +165,22 @@ pub fn parse_instruction(payload: ByteBuffer) -> GovernanceInstruction {
let expo = reader.read_u64();
GovernancePayload::SetFee(SetFee { value, expo })
},
GovernanceAction::SetFeeInToken => {
let value = reader.read_u64();
let expo = reader.read_u64();
let token_len = reader.read_u8();
if token_len != 32 {
panic_with_felt252(GovernanceActionError::InvalidGovernanceMessage.into());
}
let token: felt252 = reader
.read_u256()
.try_into()
.expect(GovernanceActionError::InvalidGovernanceMessage.into());
let token = token
.try_into()
.expect(GovernanceActionError::InvalidGovernanceMessage.into());
GovernancePayload::SetFeeInToken(SetFeeInToken { value, expo, token })
},
GovernanceAction::SetValidPeriod => { panic_with_felt252('unimplemented') },
GovernanceAction::SetWormholeAddress => {
let address: felt252 = reader
Expand Down
13 changes: 13 additions & 0 deletions target_chains/starknet/contracts/tests/data.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,19 @@ pub fn pyth_set_fee() -> ByteBuffer {
ByteBufferImpl::new(bytes, 23)
}

// A Pyth governance instruction to set fee signed by the test guardian #1.
pub fn pyth_set_fee_in_token() -> ByteBuffer {
let bytes = array![
1766847064779994694408617232155063622446317599437785683244896979152308796,
41831183904504604246915376354509245030219494606222324288494126672855141875,
245200731728170526984869527586075617087934630006881191137945784647849869312,
49565958604199796163020368,
148907253456468655193350049927026865683852796092680336764850032905682767430,
1535109346439504966152199052711447625482878604913825938427335,
];
ByteBufferImpl::new(bytes, 25)
}

// A Pyth governance instruction to set data sources signed by the test guardian #1.
pub fn pyth_set_data_sources() -> ByteBuffer {
let bytes = array![
Expand Down
100 changes: 86 additions & 14 deletions target_chains/starknet/contracts/tests/pyth.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ fn decode_event(mut event: Event) -> PythEvent {
};
PythEvent::PriceFeedUpdated(event)
} else if key0 == event_name_hash('FeeSet') {
let event = FeeSet { old_fee: event.data.pop_u256(), new_fee: event.data.pop_u256(), };
let event = FeeSet {
old_fee: event.data.pop_u256(), new_fee: event.data.pop_u256(), token: event.data.pop(),
};
PythEvent::FeeSet(event)
} else if key0 == event_name_hash('DataSourcesSet') {
let event = DataSourcesSet {
Expand Down Expand Up @@ -692,11 +694,16 @@ fn test_governance_set_fee_works() {
let (from, event) = spy.events.pop_front().unwrap();
assert!(from == pyth.contract_address);
let event = decode_event(event);
let expected = FeeSet { old_fee: 1000, new_fee: 4200, };
let expected = FeeSet {
old_fee: 1000, new_fee: 4200, token: ctx.fee_contract.contract_address
};
assert!(event == PythEvent::FeeSet(expected));

let fee2 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
assert!(fee2 == 4200);
let fee2_alt = pyth
.get_update_fee(data::test_price_update2(), ctx.fee_contract2.contract_address);
assert!(fee2_alt == 2000);

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

#[test]
fn test_governance_set_fee_in_token_works() {
let ctx = deploy_test();
let pyth = ctx.pyth;
let fee_contract = ctx.fee_contract;
let user = ctx.user;

let fee1 = pyth.get_update_fee(data::test_price_update1(), ctx.fee_contract.contract_address);
assert!(fee1 == 1000);
ctx.approve_fee(1000);

let mut balance = fee_contract.balanceOf(user);
start_prank(CheatTarget::One(pyth.contract_address), user);
pyth.update_price_feeds(data::test_price_update1());
stop_prank(CheatTarget::One(pyth.contract_address));
let new_balance = fee_contract.balanceOf(user);
assert!(balance - new_balance == 1000);
balance = new_balance;
let last_price = pyth
.get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
.unwrap_with_felt252();
assert!(last_price.price == 6281060000000);

let mut spy = spy_events(SpyOn::One(pyth.contract_address));

pyth.execute_governance_instruction(data::pyth_set_fee_in_token());

spy.fetch_events();
assert!(spy.events.len() == 1);
let (from, event) = spy.events.pop_front().unwrap();
assert!(from == pyth.contract_address);
let event = decode_event(event);
let expected = FeeSet {
old_fee: 2000, new_fee: 4200, token: ctx.fee_contract2.contract_address
};
assert!(event == PythEvent::FeeSet(expected));

let fee2 = pyth.get_update_fee(data::test_price_update2(), ctx.fee_contract.contract_address);
assert!(fee2 == 1000);
let fee2_alt = pyth
.get_update_fee(data::test_price_update2(), ctx.fee_contract2.contract_address);
assert!(fee2_alt == 4200);
ctx.approve_fee2(4200);

let balance2 = ctx.fee_contract2.balanceOf(user);
start_prank(CheatTarget::One(pyth.contract_address), user);
pyth.update_price_feeds(data::test_price_update2());
stop_prank(CheatTarget::One(pyth.contract_address));
let new_balance2 = ctx.fee_contract2.balanceOf(user);
assert!(balance2 - new_balance2 == 4200);
let last_price = pyth
.get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
.unwrap_with_felt252();
assert!(last_price.price == 6281522520745);
}

#[test]
#[fuzzer(runs: 100, seed: 0)]
#[should_panic]
Expand Down Expand Up @@ -806,8 +869,8 @@ fn test_governance_set_wormhole_works() {

let user = 'user'.try_into().unwrap();
let fee_class = declare("ERC20");
let fee_contract = deploy_fee_contract(fee_class, user);
let fee_contract2 = deploy_fee_contract(fee_class, user);
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
let pyth = deploy_pyth_default(
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
);
Expand Down Expand Up @@ -892,8 +955,8 @@ fn test_rejects_set_wormhole_without_deploying() {

let user = 'user'.try_into().unwrap();
let fee_class = declare("ERC20");
let fee_contract = deploy_fee_contract(fee_class, user);
let fee_contract2 = deploy_fee_contract(fee_class, user);
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
let pyth = deploy_pyth_default(
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
);
Expand All @@ -912,8 +975,8 @@ fn test_rejects_set_wormhole_with_incompatible_guardians() {

let user = 'user'.try_into().unwrap();
let fee_class = declare("ERC20");
let fee_contract = deploy_fee_contract(fee_class, user);
let fee_contract2 = deploy_fee_contract(fee_class, user);
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
let pyth = deploy_pyth_default(
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
);
Expand Down Expand Up @@ -1058,8 +1121,8 @@ fn deploy_test() -> Context {
let user = 'user'.try_into().unwrap();
let wormhole = super::wormhole::deploy_with_test_guardian();
let fee_class = declare("ERC20");
let fee_contract = deploy_fee_contract(fee_class, user);
let fee_contract2 = deploy_fee_contract(fee_class, user);
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
let pyth = deploy_pyth_default(
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
);
Expand All @@ -1070,8 +1133,8 @@ fn deploy_mainnet() -> Context {
let user = 'user'.try_into().unwrap();
let wormhole = super::wormhole::deploy_with_mainnet_guardians();
let fee_class = declare("ERC20");
let fee_contract = deploy_fee_contract(fee_class, user);
let fee_contract2 = deploy_fee_contract(fee_class, user);
let fee_contract = deploy_fee_contract(fee_class, fee_address1(), user);
let fee_contract2 = deploy_fee_contract(fee_class, fee_address2(), user);
let pyth = deploy_pyth_default(
wormhole.contract_address, fee_contract.contract_address, fee_contract2.contract_address
);
Expand Down Expand Up @@ -1125,12 +1188,21 @@ fn deploy_pyth(
IPythDispatcher { contract_address }
}

fn deploy_fee_contract(class: ContractClass, recipient: ContractAddress) -> IERC20CamelDispatcher {
fn fee_address1() -> ContractAddress {
0x1010.try_into().unwrap()
}
fn fee_address2() -> ContractAddress {
0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7.try_into().unwrap()
}

fn deploy_fee_contract(
class: ContractClass, at: ContractAddress, recipient: ContractAddress
) -> IERC20CamelDispatcher {
let mut args = array![];
let name: ByteArray = "eth";
let symbol: ByteArray = "eth";
(name, symbol, 100000_u256, recipient).serialize(ref args);
let contract_address = match class.deploy(@args) {
let contract_address = match class.deploy_at(@args, at) {
Result::Ok(v) => { v },
Result::Err(err) => { panic(err.panic_data) },
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,29 @@ fn main() {
"A Pyth governance instruction to set fee signed by the test guardian #1.",
);

let pyth_set_fee_in_token_payload = vec![
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,
157, 54, 87, 13, 78, 70, 244, 142, 153, 103, 75, 211, 252, 200, 70, 68, 221, 214, 185, 111,
124, 116, 27, 21, 98, 184, 47, 158, 0, 77, 199,
];
let pyth_set_fee_in_token = serialize_vaa(guardians.sign_vaa(
&[0],
VaaBody {
timestamp: 1,
nonce: 2,
emitter_chain: 1,
emitter_address: u256_to_be(41.into()).into(),
sequence: 1.try_into().unwrap(),
consistency_level: 6,
payload: PayloadKind::Binary(pyth_set_fee_in_token_payload.clone()),
},
));
print_as_cairo_fn(
&pyth_set_fee_in_token,
"pyth_set_fee_in_token",
"A Pyth governance instruction to set fee signed by the test guardian #1.",
);

let pyth_set_data_sources = serialize_vaa(guardians.sign_vaa(
&[0],
VaaBody {
Expand Down

0 comments on commit 0f7141e

Please sign in to comment.