Skip to content

Commit

Permalink
feat(target_chains/starknet): verify pyth governance vm (#1569)
Browse files Browse the repository at this point in the history
  • Loading branch information
Riateche authored May 14, 2024
1 parent c34cea0 commit 9854a5d
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 38 deletions.
3 changes: 3 additions & 0 deletions target_chains/starknet/contracts/deploy/local_deploy
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ pyth_address=$(starkli deploy "${pyth_hash}" \
1 `# num_data_sources` \
26 `# emitter_chain_id` \
58051393581875729396504816158954007153 299086580781278228892874716333010393740 `# emitter_address` \
1 `# governance_emitter_chain_id` \
41 0 `# governance_emitter_address` \
0 `# governance_initial_sequence` \
)

${sleep}
Expand Down
40 changes: 38 additions & 2 deletions target_chains/starknet/contracts/src/pyth.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mod pyth {
use pyth::byte_array::{ByteArray, ByteArrayImpl};
use core::panic_with_felt252;
use core::starknet::{ContractAddress, get_caller_address, get_execution_info};
use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait};
use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait, VerifiedVM};
use super::{
DataSource, UpdatePriceFeedsError, GovernanceActionError, Price, GetPriceUnsafeError
};
Expand Down Expand Up @@ -48,6 +48,8 @@ mod pyth {
// For fast validation.
is_valid_data_source: LegacyMap<DataSource, bool>,
latest_price_info: LegacyMap<u256, PriceInfo>,
governance_data_source: DataSource,
last_executed_governance_sequence: u64,
}

/// Initializes the Pyth contract.
Expand All @@ -73,13 +75,25 @@ mod pyth {
wormhole_address: ContractAddress,
fee_contract_address: ContractAddress,
single_update_fee: u256,
data_sources: Array<DataSource>
data_sources: Array<DataSource>,
governance_emitter_chain_id: u16,
governance_emitter_address: u256,
governance_initial_sequence: u64,
) {
self.owner.write(wormhole_address);
self.wormhole_address.write(wormhole_address);
self.fee_contract_address.write(fee_contract_address);
self.single_update_fee.write(single_update_fee);
self.write_data_sources(data_sources);
self
.governance_data_source
.write(
DataSource {
emitter_chain_id: governance_emitter_chain_id,
emitter_address: governance_emitter_address,
}
);
self.last_executed_governance_sequence.write(governance_initial_sequence);
}

#[abi(embed_v0)]
Expand Down Expand Up @@ -171,6 +185,15 @@ mod pyth {
panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into());
}
}

fn execute_governance_instruction(ref self: ContractState, data: ByteArray) {
let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() };
let vm = wormhole.parse_and_verify_vm(data);
self.verify_governance_vm(@vm);
//...
self.last_executed_governance_sequence.write(vm.sequence);
panic_with_felt252('todo');
}
}

#[generate_trait]
Expand Down Expand Up @@ -221,5 +244,18 @@ mod pyth {
fn get_total_fee(ref self: ContractState, num_updates: u8) -> u256 {
self.single_update_fee.read() * num_updates.into()
}

fn verify_governance_vm(self: @ContractState, vm: @VerifiedVM) {
let governance_data_source = self.governance_data_source.read();
if governance_data_source.emitter_chain_id != *vm.emitter_chain_id {
panic_with_felt252(GovernanceActionError::InvalidGovernanceDataSource.into());
}
if governance_data_source.emitter_address != *vm.emitter_address {
panic_with_felt252(GovernanceActionError::InvalidGovernanceDataSource.into());
}
if *vm.sequence <= self.last_executed_governance_sequence.read() {
panic_with_felt252(GovernanceActionError::OldGovernanceMessage.into());
}
}
}
}
10 changes: 10 additions & 0 deletions target_chains/starknet/contracts/src/pyth/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,22 @@ impl GetPriceUnsafeErrorIntoFelt252 of Into<GetPriceUnsafeError, felt252> {
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
pub enum GovernanceActionError {
AccessDenied,
Wormhole: pyth::wormhole::ParseAndVerifyVmError,
InvalidGovernanceDataSource,
OldGovernanceMessage,
InvalidGovernanceTarget,
InvalidGovernanceMessage,
}

impl GovernanceActionErrorIntoFelt252 of Into<GovernanceActionError, felt252> {
fn into(self: GovernanceActionError) -> felt252 {
match self {
GovernanceActionError::AccessDenied => 'access denied',
GovernanceActionError::Wormhole(err) => err.into(),
GovernanceActionError::InvalidGovernanceDataSource => 'invalid governance data source',
GovernanceActionError::OldGovernanceMessage => 'old governance message',
GovernanceActionError::InvalidGovernanceTarget => 'invalid governance target',
GovernanceActionError::InvalidGovernanceMessage => 'invalid governance message',
}
}
}
Expand Down
1 change: 1 addition & 0 deletions target_chains/starknet/contracts/src/pyth/interface.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub trait IPyth<T> {
fn set_data_sources(ref self: T, sources: Array<DataSource>);
fn set_fee(ref self: T, single_update_fee: u256);
fn update_price_feeds(ref self: T, data: ByteArray);
fn execute_governance_instruction(ref self: T, data: ByteArray);
}

#[derive(Drop, Debug, Clone, Copy, Hash, Default, Serde, starknet::Store)]
Expand Down
69 changes: 54 additions & 15 deletions target_chains/starknet/contracts/tests/pyth.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,9 @@ fn decode_event(event: @Event) -> PythEvent {
fn update_price_feeds_works() {
let owner = 'owner'.try_into().unwrap();
let user = 'user'.try_into().unwrap();
let wormhole = super::wormhole::deploy_and_init();
let wormhole = super::wormhole::deploy_with_mainnet_guardians();
let fee_contract = deploy_fee_contract(user);
let pyth = deploy(
owner,
wormhole.contract_address,
fee_contract.contract_address,
1000,
array![
DataSource {
emitter_chain_id: 26,
emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
}
]
);
let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);

start_prank(CheatTarget::One(fee_contract.contract_address), user.try_into().unwrap());
fee_contract.approve(pyth.contract_address, 10000);
Expand Down Expand Up @@ -88,16 +77,66 @@ fn update_price_feeds_works() {
assert!(last_ema_price.publish_time == 1712589206);
}

#[test]
#[should_panic(expected: ('todo',))]
fn test_governance_instruction() {
let owner = 'owner'.try_into().unwrap();
let wormhole = super::wormhole::deploy_with_test_guardian();
let fee_contract = deploy_fee_contract('fee_minter'.try_into().unwrap());
let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);

pyth
.execute_governance_instruction(
ByteArrayImpl::new(
array_try_into(
array![
1766847064779996587365624568667043246291590523274145657502751050464410443,
182147492092754791035110789954683447734750444021289139488583154515468770101,
260016405939643347307460729490551925646487038190069904485177441375406260224,
49565958604199796163020368,
8072278384728444780182694421117884443886221966887092232,
]
),
23
)
);
}

fn deploy_default(
owner: ContractAddress, wormhole_address: ContractAddress, fee_contract_address: ContractAddress
) -> IPythDispatcher {
deploy(
owner,
wormhole_address,
fee_contract_address,
1000,
array![
DataSource {
emitter_chain_id: 26,
emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71,
}
],
1,
41,
0,
)
}


fn deploy(
owner: ContractAddress,
wormhole_address: ContractAddress,
fee_contract_address: ContractAddress,
single_update_fee: u256,
data_sources: Array<DataSource>
data_sources: Array<DataSource>,
governance_emitter_chain_id: u16,
governance_emitter_address: u256,
governance_initial_sequence: u64,
) -> IPythDispatcher {
let mut args = array![];
(owner, wormhole_address, fee_contract_address, single_update_fee).serialize(ref args);
data_sources.serialize(ref args);
(data_sources, governance_emitter_chain_id).serialize(ref args);
(governance_emitter_address, governance_initial_sequence).serialize(ref args);
let contract = declare("pyth");
let contract_address = match contract.deploy(@args) {
Result::Ok(v) => { v },
Expand Down
36 changes: 15 additions & 21 deletions target_chains/starknet/contracts/tests/wormhole.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use core::panic_with_felt252;

#[test]
fn test_parse_and_verify_vm_works() {
let dispatcher = deploy_and_init();
let dispatcher = deploy_with_mainnet_guardians();

let vm = dispatcher.parse_and_verify_vm(good_vm1());
assert!(vm.version == 1);
Expand Down Expand Up @@ -37,7 +37,7 @@ fn test_parse_and_verify_vm_works() {
#[fuzzer(runs: 100, seed: 0)]
#[should_panic]
fn test_parse_and_verify_vm_rejects_corrupted_vm(pos: usize, random1: usize, random2: usize) {
let dispatcher = deploy_and_init();
let dispatcher = deploy_with_mainnet_guardians();

let input = corrupted_vm(good_vm1(), pos, random1, random2);
let vm = dispatcher.parse_and_verify_vm(input);
Expand All @@ -47,12 +47,7 @@ fn test_parse_and_verify_vm_rejects_corrupted_vm(pos: usize, random1: usize, ran
#[test]
#[should_panic(expected: ('wrong governance contract',))]
fn test_submit_guardian_set_rejects_invalid_emitter() {
let dispatcher = deploy(
array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]),
CHAIN_ID,
GOVERNANCE_CHAIN_ID,
GOVERNANCE_CONTRACT
);
let dispatcher = deploy_with_test_guardian();

dispatcher
.submit_new_guardian_set(
Expand Down Expand Up @@ -84,12 +79,7 @@ fn test_submit_guardian_set_rejects_wrong_index_in_signer() {
#[test]
#[should_panic(expected: ('invalid guardian set sequence',))]
fn test_submit_guardian_set_rejects_wrong_index_in_payload() {
let dispatcher = deploy(
array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]),
CHAIN_ID,
GOVERNANCE_CHAIN_ID,
GOVERNANCE_CONTRACT
);
let dispatcher = deploy_with_test_guardian();

dispatcher
.submit_new_guardian_set(
Expand Down Expand Up @@ -118,12 +108,7 @@ fn test_deploy_rejects_empty() {
#[test]
#[should_panic(expected: ('no guardians specified',))]
fn test_submit_guardian_set_rejects_empty() {
let dispatcher = deploy(
array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]),
CHAIN_ID,
GOVERNANCE_CHAIN_ID,
GOVERNANCE_CONTRACT
);
let dispatcher = deploy_with_test_guardian();

dispatcher
.submit_new_guardian_set(
Expand Down Expand Up @@ -184,7 +169,7 @@ fn deploy(
IWormholeDispatcher { contract_address }
}

pub fn deploy_and_init() -> IWormholeDispatcher {
pub fn deploy_with_mainnet_guardians() -> IWormholeDispatcher {
let dispatcher = deploy(guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT);

dispatcher.submit_new_guardian_set(governance_upgrade_vm1());
Expand All @@ -195,6 +180,15 @@ pub fn deploy_and_init() -> IWormholeDispatcher {
dispatcher
}

pub fn deploy_with_test_guardian() -> IWormholeDispatcher {
deploy(
array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]),
CHAIN_ID,
GOVERNANCE_CHAIN_ID,
GOVERNANCE_CONTRACT
)
}

fn corrupted_vm(mut real_data: ByteArray, pos: usize, random1: usize, random2: usize) -> ByteArray {
let mut new_data = array![];

Expand Down

0 comments on commit 9854a5d

Please sign in to comment.