Compare commits

...

18 Commits

Author SHA1 Message Date
Daniel Chew cf7987f4c5
feat(target_chains/fuel): add governance contract (#1518)
* add governance contract

* add fuel ci

* add rust-toolchain

* add executes_governance_instruction test

* add test for SetValidPeriod

* add test for AuthorizeGovernanceDataSourceTransfer

* remove SetWormholeAddress

* add test for SetDataSources

* remove WormholeAddressSetEvent

* remove SetWormholeAddress

* remove SetWormholeAddressPayload

* remove SetWormholeAddressPayload and SetWormholeAddress imports

* remove GovernanceAction::SetWormholeAddress

* address comments

* refactor test

* add comments
2024-05-09 17:42:28 +01:00
Amin Moghaddam 6e0bd0569b
feat(contract_manager)Add option to test for all entropy contracts (#1559) 2024-05-09 12:36:14 +01:00
Pavel Strakhov 1e1be9dbeb
refactor(target_chains/starknet): remove old config instructions and owner from wormhole (#1558) 2024-05-09 09:42:03 +01:00
Pavel Strakhov d105a7aa86
refactor(target_chains/starknet): use EthAddress and is_eth_signature_valid (#1556) 2024-05-08 07:20:30 +01:00
Pavel Strakhov dd9b07b5e4
refactor(target_chains/starknet): generalize array_try_into (#1555) 2024-05-07 16:18:18 +01:00
Pavel Strakhov e26c9d1a30
refactor(target_chains/starknet): split pyth module (#1554) 2024-05-07 14:20:59 +01:00
Pavel Strakhov 9dddd3d1e7
feat(target_chains/starknet): handle wormhole guardian set upgrade VAA (#1550)
* feat(target_chains/starknet): handle wormhole guardian set upgrade VAA

* test(target_chains/starknet): add failing tests for governance

* doc(target_chains/starknet): add comment about wormhole governance
2024-05-07 10:33:09 +01:00
Dev Kalra 77c68c5069
fix(fortuna): watch blocks from start and infinite get queries (#1551)
* fix: watch blocks from start and infinite get queries

* formatting

* fix

* undo small change
2024-05-07 14:21:52 +05:30
Pavel Strakhov bf2c8b5d43
refactor(target_chains/starknet): blanket impl for unwrap_with_felt252 (#1549) 2024-05-07 08:04:55 +01:00
Reisen 3f07c27243
chore(aptos): bump to 3.1.0 toolchain/cli (#1543) 2024-05-06 16:36:01 +01:00
Pavel Strakhov 42b64ac09f
refactor(target_chains/starknet): remove Result from merkle_tree and pyth setters (#1548)
* refactor(target_chains/starknet): remove Result from merkle_tree

* refactor(target_chains/starknet): remove Result from pyth contract setters
2024-05-06 16:21:36 +01:00
Pavel Strakhov 55cbe62997
feat(target_chains/starknet): wormhole governance VAA verification (#1547)
* feat(target_chains/starknet): wormhole governance VAA verification

* refactor(target_chains/starknet): rename VM to VerifiedVM
2024-05-06 14:07:49 +01:00
Pavel Strakhov 94b36c4961
refactor(target_chains/starknet): remove Result from wormhole (#1541) 2024-05-06 11:27:28 +01:00
Jayant Krishnamurthy ff6b11023c
[price_pusher] Option to ignore gas objects (#1545)
* gr

* bump version
2024-05-03 21:41:14 -07:00
Jayant Krishnamurthy 4966b956df
[price_pusher] Sui pusher debugging messages (#1544)
* add logging

* version

* gr
2024-05-03 18:04:43 -07:00
Dev Kalra 10dc4a05b8
feat(fortuna_staging): use spans to create a hierarchy of logs (#1540)
* use spans to create a hierarchy of logs

* minor imp

* remove chain id

* add a sequence processing

* added comments

* consistent with other threads

* extract method out

* add field to process block range

* add field to watch events logs

* rename method

* extract process batch method

* tidy

* update log for eth

* remove comment

* update version

* address feedback
2024-05-03 21:27:42 +05:30
Ali Behjati 586a4398bd
feat(price_pusher): add more options to evm pusher (#1538)
This change makes gasLimit configurable and also adds an
updateFeeMultiplier which is useful in Hedera as they have an
inconsistency between the `value` passed in tx and the `value` on-chain.
2024-05-03 09:59:19 +02:00
guibescos 020ecdf5da
Solve (#1539) 2024-05-03 09:46:09 +02:00
72 changed files with 3534 additions and 1691 deletions

View File

@ -21,10 +21,10 @@ jobs:
- uses: actions/checkout@v3
- name: Download CLI
run: wget https://github.com/aptos-labs/aptos-core/releases/download/aptos-cli-v1.0.4/aptos-cli-1.0.4-Ubuntu-22.04-x86_64.zip
run: wget https://github.com/aptos-labs/aptos-core/releases/download/aptos-cli-v3.1.0/aptos-cli-3.1.0-Ubuntu-22.04-x86_64.zip
- name: Unzip CLI
run: unzip aptos-cli-1.0.4-Ubuntu-22.04-x86_64.zip
run: unzip aptos-cli-3.1.0-Ubuntu-22.04-x86_64.zip
- name: Run tests
run: ./aptos move test

35
.github/workflows/ci-fuel-contract.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Test Fuel Contract
on:
pull_request:
paths:
- target_chains/fuel/**
push:
branches:
- main
paths:
- target_chains/fuel/**
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: target_chains/fuel/contracts/
steps:
- uses: actions/checkout@v2
- name: Install Fuel toolchain
run: |
curl https://install.fuel.network | sh
echo "$HOME/.fuelup/bin" >> $GITHUB_PATH
- name: Build with Forc
run: forc build --verbose
- name: Run tests with Forc
run: forc test --verbose
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

View File

@ -1488,7 +1488,7 @@ dependencies = [
[[package]]
name = "fortuna"
version = "5.1.1"
version = "5.2.2"
dependencies = [
"anyhow",
"axum",
@ -2822,7 +2822,7 @@ dependencies = [
[[package]]
name = "pythnet-sdk"
version = "2.0.0"
version = "2.1.0"
dependencies = [
"bincode",
"borsh",

View File

@ -1,6 +1,6 @@
[package]
name = "fortuna"
version = "5.1.1"
version = "5.2.2"
edition = "2021"
[dependencies]

View File

@ -277,7 +277,11 @@ impl EntropyReader for PythContract {
Err(e) => match e {
ContractError::ProviderError { e } => Err(anyhow!(e)),
_ => {
tracing::info!("Gas estimation for reveal with callback failed: {:?}", e);
tracing::info!(
sequence_number = sequence_number,
"Gas estimation failed. error: {:?}",
e
);
Ok(None)
}
},

View File

@ -13,7 +13,10 @@ use {
},
config::EthereumConfig,
},
anyhow::Result,
anyhow::{
anyhow,
Result,
},
ethers::{
contract::ContractError,
providers::{
@ -33,9 +36,17 @@ use {
Duration,
},
},
tracing,
tracing::{
self,
Instrument,
},
};
#[derive(Debug)]
pub struct BlockRange {
pub from: BlockNumber,
pub to: BlockNumber,
}
/// How much to wait before retrying in case of an RPC error
const RETRY_INTERVAL: Duration = Duration::from_secs(5);
@ -56,36 +67,31 @@ async fn get_latest_safe_block(chain_state: &BlockchainState) -> BlockNumber {
.await
{
Ok(latest_confirmed_block) => {
return latest_confirmed_block - chain_state.reveal_delay_blocks
tracing::info!(
"Fetched latest safe block {}",
latest_confirmed_block - chain_state.reveal_delay_blocks
);
return latest_confirmed_block - chain_state.reveal_delay_blocks;
}
Err(e) => {
tracing::error!(
"Chain: {} - error while getting block number. error: {:?}",
&chain_state.id,
e
);
tracing::error!("Error while getting block number. error: {:?}", e);
time::sleep(RETRY_INTERVAL).await;
}
}
}
}
/// Run threads to handle events for the last `BACKLOG_RANGE` blocks. Watch for new blocks and
/// Run threads to handle events for the last `BACKLOG_RANGE` blocks, watch for new blocks and
/// handle any events for the new blocks.
#[tracing::instrument(name="keeper", skip_all, fields(chain_id=chain_state.id))]
pub async fn run_keeper_threads(
private_key: String,
chain_eth_config: EthereumConfig,
chain_state: BlockchainState,
) {
tracing::info!("Chain: {} - starting keeper", &chain_state.id);
let latest_safe_block = get_latest_safe_block(&chain_state).await;
tracing::info!(
"Chain: {} - latest safe block: {}",
&chain_state.id,
&latest_safe_block
);
tracing::info!("starting keeper");
let latest_safe_block = get_latest_safe_block(&chain_state).in_current_span().await;
tracing::info!("latest safe block: {}", &latest_safe_block);
let contract = Arc::new(
SignablePythContract::from_config(&chain_eth_config, &private_key)
@ -93,64 +99,48 @@ pub async fn run_keeper_threads(
.expect("Chain config should be valid"),
);
let backlog_chain_state = chain_state.clone();
let backlog_contract = contract.clone();
// Spawn a thread to handle the events from last BACKLOG_RANGE blocks.
spawn(async move {
let from_block = latest_safe_block.saturating_sub(BACKLOG_RANGE);
process_block_range(
spawn(
process_backlog(
BlockRange {
from: from_block,
from: latest_safe_block.saturating_sub(BACKLOG_RANGE),
to: latest_safe_block,
},
backlog_contract,
contract.clone(),
chain_eth_config.gas_limit,
backlog_chain_state.clone(),
chain_state.clone(),
)
.await;
tracing::info!(
"Chain: {} - backlog processing completed",
&backlog_chain_state.id
);
});
.in_current_span(),
);
let (tx, rx) = mpsc::channel::<BlockRange>(1000);
let watch_blocks_chain_state = chain_state.clone();
// Spawn a thread to watch for new blocks and send the range of blocks for which events has not been handled to the `tx` channel.
spawn(async move {
loop {
if let Err(e) = watch_blocks(
watch_blocks_chain_state.clone(),
latest_safe_block,
tx.clone(),
chain_eth_config.geth_rpc_wss.clone(),
)
.await
{
tracing::error!(
"Chain: {} - error in watching blocks. error: {:?}",
&watch_blocks_chain_state.id,
e
);
time::sleep(RETRY_INTERVAL).await;
}
}
});
spawn(
watch_blocks_wrapper(
chain_state.clone(),
latest_safe_block,
tx,
chain_eth_config.geth_rpc_wss.clone(),
)
.in_current_span(),
);
// Spawn a thread that listens for block ranges on the `rx` channel and processes the events for those blocks.
spawn(process_new_blocks(
chain_state.clone(),
rx,
Arc::clone(&contract),
chain_eth_config.gas_limit,
));
spawn(
process_new_blocks(
chain_state.clone(),
rx,
Arc::clone(&contract),
chain_eth_config.gas_limit,
)
.in_current_span(),
);
}
// Process an event for a chain. It estimates the gas for the reveal with callback and
// submits the transaction if the gas estimate is below the gas limit.
// It will return an Error if the gas estimation failed with a provider error or if the
// reveal with callback failed with a provider error.
/// Process an event for a chain. It estimates the gas for the reveal with callback and
/// submits the transaction if the gas estimate is below the gas limit.
/// It will return an Error if the gas estimation failed with a provider error or if the
/// reveal with callback failed with a provider error.
pub async fn process_event(
event: RequestedWithCallbackEvent,
chain_config: &BlockchainState,
@ -164,10 +154,8 @@ pub async fn process_event(
Ok(result) => result,
Err(e) => {
tracing::error!(
"Chain: {} - error while revealing for provider: {} and sequence number: {} with error: {:?}",
&chain_config.id,
event.provider_address,
event.sequence_number,
sequence_number = &event.sequence_number,
"Error while revealing with error: {:?}",
e
);
return Ok(());
@ -182,6 +170,7 @@ pub async fn process_event(
event.user_random_number,
provider_revelation,
)
.in_current_span()
.await;
match gas_estimate_res {
@ -194,8 +183,8 @@ pub async fn process_event(
if gas_estimate > gas_limit {
tracing::error!(
"Chain: {} - gas estimate for reveal with callback is higher than the gas limit",
&chain_config.id
sequence_number = &event.sequence_number,
"Gas estimate for reveal with callback is higher than the gas limit"
);
return Ok(());
}
@ -222,12 +211,10 @@ pub async fn process_event(
// and concluded that its Ok to not reveal.
_ => {
tracing::error!(
"Chain: {} - error while revealing for provider: {} and sequence number: {} with error: {:?}",
&chain_config.id,
event.provider_address,
event.sequence_number,
e
);
sequence_number = &event.sequence_number,
"Error while revealing with error: {:?}",
e
);
return Ok(());
}
},
@ -236,34 +223,34 @@ pub async fn process_event(
match pending_tx.await {
Ok(res) => {
tracing::info!(
"Chain: {} - revealed for provider: {} and sequence number: {} with res: {:?}",
&chain_config.id,
event.provider_address,
event.sequence_number,
sequence_number = &event.sequence_number,
"Revealed with res: {:?}",
res
);
Ok(())
}
Err(e) => {
tracing::error!(
"Chain: {} - error while revealing for provider: {} and sequence number: {} with error: {:?}",
&chain_config.id,
event.provider_address,
event.sequence_number,
sequence_number = &event.sequence_number,
"Error while revealing with error: {:?}",
e
);
Err(e.into())
}
}
}
None => Ok(()),
None => {
tracing::info!(
sequence_number = &event.sequence_number,
"Not processing event"
);
Ok(())
}
},
Err(e) => {
tracing::error!(
"Chain: {} - error while simulating reveal for provider: {} and sequence number: {} \n error: {:?}",
&chain_config.id,
event.provider_address,
event.sequence_number,
sequence_number = &event.sequence_number,
"Error while simulating reveal with error: {:?}",
e
);
Err(e)
@ -272,21 +259,14 @@ pub async fn process_event(
}
/// Process a range of blocks for a chain. It will fetch events for the blocks in the provided range
/// and then try to process them one by one. If the process fails, it will retry indefinitely.
/// Process a range of blocks in batches. It calls the `process_single_block_batch` method for each batch.
#[tracing::instrument(skip_all, fields(range_from_block=block_range.from, range_to_block=block_range.to))]
pub async fn process_block_range(
block_range: BlockRange,
contract: Arc<SignablePythContract>,
gas_limit: U256,
chain_state: api::BlockchainState,
) {
tracing::info!(
"Chain: {} - processing blocks from: {} to: {}",
&chain_state.id,
block_range.from,
block_range.to
);
let BlockRange {
from: first_block,
to: last_block,
@ -297,41 +277,64 @@ pub async fn process_block_range(
if to_block > last_block {
to_block = last_block;
}
process_single_block_batch(
BlockRange {
from: current_block,
to: to_block,
},
contract.clone(),
gas_limit,
chain_state.clone(),
)
.in_current_span()
.await;
current_block = to_block + 1;
}
}
/// Process a batch of blocks for a chain. It will fetch events for all the blocks in a single call for the provided batch
/// and then try to process them one by one. If the process fails, it will retry indefinitely.
#[tracing::instrument(name="batch", skip_all, fields(batch_from_block=block_range.from, batch_to_block=block_range.to))]
pub async fn process_single_block_batch(
block_range: BlockRange,
contract: Arc<SignablePythContract>,
gas_limit: U256,
chain_state: api::BlockchainState,
) {
loop {
let events_res = chain_state
.contract
.get_request_with_callback_events(current_block, to_block)
.get_request_with_callback_events(block_range.from, block_range.to)
.await;
match events_res {
Ok(events) => {
for event in events {
tracing::info!(num_of_events = &events.len(), "Processing",);
for event in &events {
tracing::info!(sequence_number = &event.sequence_number, "Processing event",);
while let Err(e) =
process_event(event.clone(), &chain_state, &contract, gas_limit).await
process_event(event.clone(), &chain_state, &contract, gas_limit)
.in_current_span()
.await
{
tracing::error!(
"Chain: {} - error while processing event for sequence number: {}. Waiting for {} seconds before retry. error: {:?}",
&chain_state.id,
&event.sequence_number,
sequence_number = &event.sequence_number,
"Error while processing event. Waiting for {} seconds before retry. error: {:?}",
RETRY_INTERVAL.as_secs(),
e
);
time::sleep(RETRY_INTERVAL).await;
}
tracing::info!(sequence_number = &event.sequence_number, "Processed event",);
}
tracing::info!(
"Chain: {} - backlog processed from block: {} to block: {}",
&chain_state.id,
&current_block,
&to_block
);
current_block = to_block + 1;
tracing::info!(num_of_events = &events.len(), "Processed",);
break;
}
Err(e) => {
tracing::error!(
"Chain: {} - error while getting events from block: {} to block: {}. Waiting for {} seconds before retry. error: {:?}",
&chain_state.id,
&current_block,
&to_block,
"Error while getting events. Waiting for {} seconds before retry. error: {:?}",
RETRY_INTERVAL.as_secs(),
e
);
@ -341,9 +344,30 @@ pub async fn process_block_range(
}
}
pub struct BlockRange {
pub from: BlockNumber,
pub to: BlockNumber,
/// Wrapper for the `watch_blocks` method. If there was an error while watching, it will retry after a delay.
/// It retries indefinitely.
#[tracing::instrument(name="watch_blocks", skip_all, fields(initial_safe_block=latest_safe_block))]
pub async fn watch_blocks_wrapper(
chain_state: BlockchainState,
latest_safe_block: BlockNumber,
tx: mpsc::Sender<BlockRange>,
geth_rpc_wss: Option<String>,
) {
let mut last_safe_block_processed = latest_safe_block;
loop {
if let Err(e) = watch_blocks(
chain_state.clone(),
&mut last_safe_block_processed,
tx.clone(),
geth_rpc_wss.clone(),
)
.in_current_span()
.await
{
tracing::error!("watching blocks. error: {:?}", e);
time::sleep(RETRY_INTERVAL).await;
}
}
}
/// Watch for new blocks and send the range of blocks for which events have not been handled to the `tx` channel.
@ -352,74 +376,72 @@ pub struct BlockRange {
/// know about it.
pub async fn watch_blocks(
chain_state: BlockchainState,
latest_safe_block: BlockNumber,
last_safe_block_processed: &mut BlockNumber,
tx: mpsc::Sender<BlockRange>,
geth_rpc_wss: Option<String>,
) -> Result<()> {
tracing::info!(
"Chain: {} - watching blocks to handle new events",
&chain_state.id
);
let mut last_safe_block_processed = latest_safe_block;
tracing::info!("Watching blocks to handle new events");
let provider_option = match geth_rpc_wss {
Some(wss) => Some(match Provider::<Ws>::connect(wss.clone()).await {
Ok(provider) => provider,
Err(e) => {
tracing::error!(
"Chain: {} - error while connecting to wss: {}. error: {:?}",
&chain_state.id,
wss,
e
);
tracing::error!("Error while connecting to wss: {}. error: {:?}", wss, e);
return Err(e.into());
}
}),
None => {
tracing::info!("Chain: {} - no wss provided", &chain_state.id);
tracing::info!("No wss provided");
None
}
};
let mut stream_option = match provider_option {
Some(ref provider) => Some(provider.subscribe_blocks().await?),
Some(ref provider) => Some(match provider.subscribe_blocks().await {
Ok(client) => client,
Err(e) => {
tracing::error!("Error while subscribing to blocks. error {:?}", e);
return Err(e.into());
}
}),
None => None,
};
loop {
match stream_option {
Some(ref mut stream) => {
stream.next().await;
if let None = stream.next().await {
tracing::error!("Error blocks subscription stream ended");
return Err(anyhow!("Error blocks subscription stream ended"));
}
}
None => {
time::sleep(POLL_INTERVAL).await;
}
}
let latest_safe_block = get_latest_safe_block(&chain_state).await;
if latest_safe_block > last_safe_block_processed {
let latest_safe_block = get_latest_safe_block(&chain_state).in_current_span().await;
if latest_safe_block > *last_safe_block_processed {
match tx
.send(BlockRange {
from: last_safe_block_processed + 1,
from: *last_safe_block_processed + 1,
to: latest_safe_block,
})
.await
{
Ok(_) => {
tracing::info!(
"Chain: {} - block range sent to handle events from: {} to: {}",
&chain_state.id,
&last_safe_block_processed + 1,
&latest_safe_block
from_block = *last_safe_block_processed + 1,
to_block = &latest_safe_block,
"Block range sent to handle events",
);
last_safe_block_processed = latest_safe_block;
*last_safe_block_processed = latest_safe_block;
}
Err(e) => {
tracing::error!(
"Chain: {} - error while sending block range to handle events. These will be handled in next call. error: {:?}",
&chain_state.id,
e
);
"Error while sending block range to handle events. These will be handled in next call. error: {:?}",
e
);
}
};
}
@ -427,17 +449,15 @@ pub async fn watch_blocks(
}
/// It waits on rx channel to receive block ranges and then calls process_block_range to process them.
#[tracing::instrument(skip_all)]
pub async fn process_new_blocks(
chain_state: BlockchainState,
mut rx: mpsc::Receiver<BlockRange>,
contract: Arc<SignablePythContract>,
gas_limit: U256,
) {
tracing::info!("Waiting for new block ranges to process");
loop {
tracing::info!(
"Chain: {} - waiting for new block ranges to process",
&chain_state.id
);
if let Some(block_range) = rx.recv().await {
process_block_range(
block_range,
@ -445,7 +465,23 @@ pub async fn process_new_blocks(
gas_limit,
chain_state.clone(),
)
.in_current_span()
.await;
}
}
}
/// Processes the backlog_range for a chain.
#[tracing::instrument(skip_all)]
pub async fn process_backlog(
backlog_range: BlockRange,
contract: Arc<SignablePythContract>,
gas_limit: U256,
chain_state: BlockchainState,
) {
tracing::info!("Processing backlog");
process_block_range(backlog_range, contract, gas_limit, chain_state)
.in_current_span()
.await;
tracing::info!("Backlog processed");
}

View File

@ -3138,7 +3138,7 @@ dependencies = [
[[package]]
name = "pythnet-sdk"
version = "2.0.0"
version = "2.1.0"
dependencies = [
"bincode",
"borsh 0.10.3",

View File

@ -1,6 +1,6 @@
{
"name": "@pythnetwork/price-pusher",
"version": "6.6.3",
"version": "6.7.2",
"description": "Pyth Price Pusher",
"homepage": "https://pyth.network",
"main": "lib/index.js",

View File

@ -52,6 +52,20 @@ export default {
required: false,
default: 5,
} as Options,
"gas-limit": {
description: "Gas limit for the transaction",
type: "number",
required: false,
} as Options,
"update-fee-multiplier": {
description:
"Multiplier for the fee to update the price. It is useful in networks " +
"such as Hedera where setting on-chain getUpdateFee as the transaction value " +
"won't work. Default to 1",
type: "number",
required: false,
default: 1,
} as Options,
...options.priceConfigFile,
...options.priceServiceEndpoint,
...options.mnemonicFile,
@ -73,6 +87,8 @@ export default {
txSpeed,
overrideGasPriceMultiplier,
overrideGasPriceMultiplierCap,
gasLimit,
updateFeeMultiplier,
} = argv;
const priceConfigs = readPriceConfigFile(priceConfigFile);
@ -119,6 +135,8 @@ export default {
pythContractFactory,
overrideGasPriceMultiplier,
overrideGasPriceMultiplierCap,
updateFeeMultiplier,
gasLimit,
gasStation
);

View File

@ -130,6 +130,8 @@ export class EvmPricePusher implements IPricePusher {
pythContractFactory: PythContractFactory,
private overrideGasPriceMultiplier: number,
private overrideGasPriceMultiplierCap: number,
private updateFeeMultiplier: number,
private gasLimit?: number,
customGasStation?: CustomGasStation
) {
this.customGasStation = customGasStation;
@ -168,6 +170,7 @@ export class EvmPricePusher implements IPricePusher {
updateFee = await this.pythContract.methods
.getUpdateFee(priceFeedUpdateData)
.call();
updateFee = Number(updateFee) * (this.updateFeeMultiplier || 1);
console.log(`Update fee: ${updateFee}`);
} catch (e: any) {
console.error(
@ -217,7 +220,12 @@ export class EvmPricePusher implements IPricePusher {
priceIdsWith0x,
pubTimesToPush
)
.send({ value: updateFee, gasPrice, nonce: txNonce })
.send({
value: updateFee,
gasPrice,
nonce: txNonce,
gasLimit: this.gasLimit,
})
.on("transactionHash", (hash: string) => {
console.log(`Successful. Tx hash: ${hash}`);
})

View File

@ -9,6 +9,9 @@ import near from "./near/command";
import solana from "./solana/command";
yargs(hideBin(process.argv))
.parserConfiguration({
"parse-numbers": false,
})
.config("config")
.global("config")
.command(evm)

View File

@ -44,6 +44,13 @@ export default {
required: true,
default: 30,
} as Options,
"ignore-gas-objects": {
description:
"Gas objects to ignore when merging gas objects on startup -- use this for locked objects.",
type: "array",
required: false,
default: [],
} as Options,
"gas-budget": {
description: "Gas budget for each price update",
type: "number",
@ -73,6 +80,7 @@ export default {
pythStateId,
wormholeStateId,
numGasObjects,
ignoreGasObjects,
gasBudget,
accountIndex,
} = argv;
@ -126,7 +134,8 @@ export default {
endpoint,
keypair,
gasBudget,
numGasObjects
numGasObjects,
ignoreGasObjects
);
const controller = new Controller(

View File

@ -162,7 +162,8 @@ export class SuiPricePusher implements IPricePusher {
endpoint: string,
keypair: Ed25519Keypair,
gasBudget: number,
numGasObjects: number
numGasObjects: number,
ignoreGasObjects: string[]
): Promise<SuiPricePusher> {
if (numGasObjects > MAX_NUM_OBJECTS_IN_ARGUMENT) {
throw new Error(
@ -183,7 +184,8 @@ export class SuiPricePusher implements IPricePusher {
const gasPool = await SuiPricePusher.initializeGasPool(
keypair,
provider,
numGasObjects
numGasObjects,
ignoreGasObjects
);
const pythClient = new SuiPythClient(
@ -318,17 +320,26 @@ export class SuiPricePusher implements IPricePusher {
// This function will smash all coins owned by the signer into one, and then
// split them equally into numGasObjects.
// ignoreGasObjects is a list of gas objects that will be ignored during the
// merging -- use this to store any locked objects on initialization.
private static async initializeGasPool(
signer: Ed25519Keypair,
provider: SuiClient,
numGasObjects: number
numGasObjects: number,
ignoreGasObjects: string[]
): Promise<SuiObjectRef[]> {
const signerAddress = await signer.toSuiAddress();
if (ignoreGasObjects.length > 0) {
console.log("Ignoring some gas objects for coin merging:");
console.log(ignoreGasObjects);
}
const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne(
signer,
provider,
signerAddress
signerAddress,
ignoreGasObjects
);
const coinResult = await provider.getObject({
id: consolidatedCoin.objectId,
@ -458,7 +469,8 @@ export class SuiPricePusher implements IPricePusher {
private static async mergeGasCoinsIntoOne(
signer: Ed25519Keypair,
provider: SuiClient,
owner: SuiAddress
owner: SuiAddress,
initialLockedAddresses: string[]
): Promise<SuiObjectRef> {
const gasCoins = await SuiPricePusher.getAllGasCoins(provider, owner);
// skip merging if there is only one coin
@ -472,6 +484,7 @@ export class SuiPricePusher implements IPricePusher {
);
let finalCoin;
const lockedAddresses: Set<string> = new Set();
initialLockedAddresses.forEach((value) => lockedAddresses.add(value));
for (let i = 0; i < gasCoinsChunks.length; i++) {
const mergeTx = new TransactionBlock();
let coins = gasCoinsChunks[i];
@ -488,6 +501,10 @@ export class SuiPricePusher implements IPricePusher {
options: { showEffects: true },
});
} catch (e) {
console.log("Merge transaction failed with error:");
console.log(e);
console.log((e as any).data);
console.log(JSON.stringify(e));
if (
String(e).includes(
"quorum of validators because of locked objects. Retried a conflicting transaction"

View File

@ -1,6 +1,11 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { toPrivateKey } from "../src";
import {
DefaultStore,
EvmEntropyContract,
PrivateKey,
toPrivateKey,
} from "../src";
import {
COMMON_DEPLOY_OPTIONS,
findEntropyContract,
@ -12,26 +17,29 @@ const parser = yargs(hideBin(process.argv))
.usage(
"Requests a random number from an entropy contract and measures the\n" +
"latency between request submission and fulfillment by the Fortuna keeper service.\n" +
"Usage: $0 --chain-id <chain-id> --private-key <private-key>"
"Usage: $0 --private-key <private-key> --chain <chain-id> | --all-chains <testnet|mainnet>"
)
.options({
chain: {
type: "string",
demandOption: true,
desc: "test latency for the contract on this chain",
conflicts: "all-chains",
},
"all-chains": {
type: "string",
conflicts: "chain",
choices: ["testnet", "mainnet"],
desc: "test latency for all entropy contracts deployed either on mainnet or testnet",
},
"private-key": COMMON_DEPLOY_OPTIONS["private-key"],
});
async function main() {
const argv = await parser.argv;
const chain = findEvmChain(argv.chain);
const contract = findEntropyContract(chain);
async function testLatency(
contract: EvmEntropyContract,
privateKey: PrivateKey
) {
const provider = await contract.getDefaultProvider();
const userRandomNumber = contract.generateUserRandomNumber();
const privateKey = toPrivateKey(argv.privateKey);
const requestResponse = await contract.requestRandomness(
userRandomNumber,
provider,
@ -84,4 +92,27 @@ async function main() {
}
}
async function main() {
const argv = await parser.argv;
if (!argv.chain && !argv["all-chains"]) {
throw new Error("Must specify either --chain or --all-chains");
}
const privateKey = toPrivateKey(argv.privateKey);
if (argv["all-chains"]) {
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
if (
contract.getChain().isMainnet() ===
(argv["all-chains"] === "mainnet")
) {
console.log(`Testing latency for ${contract.getId()}...`);
await testLatency(contract, privateKey);
}
}
} else if (argv.chain) {
const chain = findEvmChain(argv.chain);
const contract = findEntropyContract(chain);
await testLatency(contract, privateKey);
}
}
main();

1173
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,11 @@ use {
merkle::MerkleTree,
Accumulator,
},
hashers::keccak256_160::Keccak160,
hashers::{
keccak256::Keccak256,
keccak256_160::Keccak160,
Hasher,
},
messages::{
FeedId,
Message,
@ -27,6 +31,7 @@ use {
byteorder::BigEndian,
libsecp256k1::{
Message as libsecp256k1Message,
PublicKey,
RecoveryId,
SecretKey,
Signature,
@ -96,6 +101,19 @@ pub fn dummy_guardians() -> Vec<SecretKey> {
result
}
pub fn dummy_guardians_addresses() -> Vec<[u8; 20]> {
let guardians = dummy_guardians();
guardians
.iter()
.map(|x| {
let mut result: [u8; 20] = [0u8; 20];
let pubkey = &PublicKey::from_secret_key(x).serialize()[1..];
result.copy_from_slice(&Keccak256::hashv(&[&pubkey])[12..]);
result
})
.collect()
}
pub fn create_dummy_feed_id(value: i64) -> FeedId {
let mut dummy_id = [0; 32];
dummy_id[0] = value as u8;
@ -271,7 +289,6 @@ pub fn create_vaa_from_payload(
..Default::default()
};
(header, body).into()
}

View File

@ -4,12 +4,12 @@ version = "0.0.1"
upgrade_policy = "compatible"
[dependencies]
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework/", rev = "2c74a456298fcd520241a562119b6fe30abdaae2" }
MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib/", rev = "2c74a456298fcd520241a562119b6fe30abdaae2" }
AptosStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-stdlib/", rev = "2c74a456298fcd520241a562119b6fe30abdaae2" }
AptosToken = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-token/", rev = "2c74a456298fcd520241a562119b6fe30abdaae2" }
Wormhole = { git = "https://github.com/wormhole-foundation/wormhole.git", subdir = "aptos/wormhole", rev = "f29c8c935123f3b3a917ba5dc930ec68737463c7" }
Deployer = { git = "https://github.com/wormhole-foundation/wormhole.git", subdir = "aptos/deployer", rev = "f29c8c935123f3b3a917ba5dc930ec68737463c7" }
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework/", rev = "6f83bc6d02207298b2dee91133d75538789bf582" }
MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib/", rev = "6f83bc6d02207298b2dee91133d75538789bf582" }
AptosStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-stdlib/", rev = "6f83bc6d02207298b2dee91133d75538789bf582" }
AptosToken = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-token/", rev = "6f83bc6d02207298b2dee91133d75538789bf582" }
Wormhole = { git = "https://github.com/wormhole-foundation/wormhole.git", subdir = "aptos/wormhole", rev = "b8676f09a6e4a92bbaecb5f3d59b5e9b778de082" }
Deployer = { git = "https://github.com/wormhole-foundation/wormhole.git", subdir = "aptos/deployer", rev = "b8676f09a6e4a92bbaecb5f3d59b5e9b778de082" }
[addresses]
pyth = "_"

2
target_chains/aptos/start_node.sh Normal file → Executable file
View File

@ -1,2 +1,2 @@
#!/bin/bash
aptos node run-local-testnet --with-faucet --force-restart
aptos node run-local-testnet --with-faucet --force-restart --bind-to 0.0.0.0

View File

@ -1640,7 +1640,7 @@ dependencies = [
[[package]]
name = "pythnet-sdk"
version = "2.0.0"
version = "2.1.0"
dependencies = [
"bincode",
"borsh 0.10.3",

View File

@ -134,6 +134,12 @@ version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]]
name = "arrayref"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
[[package]]
name = "ascii"
version = "0.9.3"
@ -182,7 +188,7 @@ dependencies = [
"Inflector",
"async-graphql-parser",
"darling 0.14.4",
"proc-macro-crate",
"proc-macro-crate 1.3.1",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -360,6 +366,15 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -387,6 +402,15 @@ dependencies = [
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -396,22 +420,98 @@ dependencies = [
"generic-array",
]
[[package]]
name = "borsh"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [
"borsh-derive",
"hashbrown 0.13.2",
]
[[package]]
name = "borsh-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7"
dependencies = [
"borsh-derive-internal",
"borsh-schema-derive-internal",
"proc-macro-crate 0.1.5",
"proc-macro2",
"syn 1.0.109",
]
[[package]]
name = "borsh-derive-internal"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "borsh-schema-derive-internal"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bs58"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [
"sha2",
"sha2 0.10.8",
"tinyvec",
]
[[package]]
name = "bstr"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "byteorder"
version = "1.5.0"
@ -517,11 +617,11 @@ checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3"
dependencies = [
"bs58",
"coins-core",
"digest",
"hmac",
"digest 0.10.7",
"hmac 0.12.1",
"k256",
"serde",
"sha2",
"sha2 0.10.8",
"thiserror",
]
@ -533,11 +633,11 @@ checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528"
dependencies = [
"bitvec",
"coins-bip32",
"hmac",
"hmac 0.12.1",
"once_cell",
"pbkdf2 0.12.2",
"rand",
"sha2",
"sha2 0.10.8",
"thiserror",
]
@ -550,13 +650,13 @@ dependencies = [
"base64 0.21.7",
"bech32",
"bs58",
"digest",
"digest 0.10.7",
"generic-array",
"hex",
"ripemd",
"serde",
"serde_derive",
"sha2",
"sha2 0.10.8",
"sha3",
"thiserror",
]
@ -713,6 +813,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "crypto-mac"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "ct-logs"
version = "0.8.0"
@ -740,7 +850,7 @@ dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"digest 0.10.7",
"fiat-crypto",
"platforms",
"rustc_version",
@ -911,13 +1021,22 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"block-buffer 0.10.4",
"const-oid",
"crypto-common",
"subtle",
@ -935,6 +1054,12 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
[[package]]
name = "dyn-clone"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ecdsa"
version = "0.16.9"
@ -942,7 +1067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest",
"digest 0.10.7",
"elliptic-curve",
"rfc6979",
"signature",
@ -966,7 +1091,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
dependencies = [
"curve25519-dalek",
"ed25519",
"sha2",
"sha2 0.10.8",
"subtle",
]
@ -984,7 +1109,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
"digest",
"digest 0.10.7",
"ff",
"generic-array",
"group",
@ -1054,15 +1179,15 @@ checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab"
dependencies = [
"aes",
"ctr",
"digest",
"digest 0.10.7",
"hex",
"hmac",
"hmac 0.12.1",
"pbkdf2 0.11.0",
"rand",
"scrypt",
"serde",
"serde_json",
"sha2",
"sha2 0.10.8",
"sha3",
"thiserror",
"uuid 0.8.2",
@ -1090,10 +1215,19 @@ dependencies = [
]
[[package]]
name = "fastrand"
version = "2.0.2"
name = "fast-math"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
checksum = "2465292146cdfc2011350fe3b1c616ac83cf0faeedb33463ba1c332ed8948d66"
dependencies = [
"ieee754",
]
[[package]]
name = "fastrand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "ff"
@ -1451,7 +1585,7 @@ dependencies = [
"rand",
"secp256k1",
"serde",
"sha2",
"sha2 0.10.8",
"zeroize",
]
@ -1474,12 +1608,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89143dd80b29dda305fbb033bc7f868834445ef6b361bf920f0077938fb6c0bc"
dependencies = [
"derive_more",
"digest",
"digest 0.10.7",
"fuel-storage",
"hashbrown 0.13.2",
"hex",
"serde",
"sha2",
"sha2 0.10.8",
]
[[package]]
@ -1633,7 +1767,7 @@ dependencies = [
"itertools 0.12.1",
"serde",
"serde_json",
"sha2",
"sha2 0.10.8",
"thiserror",
"uint",
]
@ -1694,7 +1828,7 @@ dependencies = [
"rand",
"serde",
"serde_json",
"serde_with 3.7.0",
"serde_with 3.8.0",
"tempfile",
"tokio",
"which",
@ -1939,13 +2073,34 @@ dependencies = [
"serde",
]
[[package]]
name = "hmac"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
dependencies = [
"crypto-mac",
"digest 0.9.0",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
"digest 0.10.7",
]
[[package]]
name = "hmac-drbg"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1"
dependencies = [
"digest 0.9.0",
"generic-array",
"hmac 0.8.1",
]
[[package]]
@ -2048,7 +2203,7 @@ dependencies = [
"http",
"hyper",
"log",
"rustls 0.21.11",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
"tokio",
"tokio-rustls 0.24.1",
@ -2129,6 +2284,12 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "ieee754"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9007da9cacbd3e6343da136e98b0d2df013f553d35bdec8b518f07bea768e19c"
[[package]]
name = "indexmap"
version = "1.9.3"
@ -2208,7 +2369,7 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"once_cell",
"sha2",
"sha2 0.10.8",
"signature",
]
@ -2239,6 +2400,54 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "libsecp256k1"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1"
dependencies = [
"arrayref",
"base64 0.13.1",
"digest 0.9.0",
"hmac-drbg",
"libsecp256k1-core",
"libsecp256k1-gen-ecmult",
"libsecp256k1-gen-genmult",
"rand",
"serde",
"sha2 0.9.9",
"typenum",
]
[[package]]
name = "libsecp256k1-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451"
dependencies = [
"crunchy",
"digest 0.9.0",
"subtle",
]
[[package]]
name = "libsecp256k1-gen-ecmult"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809"
dependencies = [
"libsecp256k1-core",
]
[[package]]
name = "libsecp256k1-gen-genmult"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c"
dependencies = [
"libsecp256k1-core",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
@ -2247,9 +2456,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
@ -2335,12 +2544,78 @@ dependencies = [
"tempfile",
]
[[package]]
name = "num"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6"
dependencies = [
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.18"
@ -2375,6 +2650,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.64"
@ -2428,14 +2709,14 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2",
"sha2 0.10.8",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [
"lock_api",
"parking_lot_core",
@ -2443,15 +2724,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.9"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.48.5",
"windows-targets 0.52.5",
]
[[package]]
@ -2466,7 +2747,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"digest 0.10.7",
]
[[package]]
@ -2475,8 +2756,8 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
"digest 0.10.7",
"hmac 0.12.1",
]
[[package]]
@ -2602,6 +2883,15 @@ dependencies = [
"uint",
]
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
@ -2691,11 +2981,37 @@ dependencies = [
"dotenv",
"fuels",
"hex",
"libsecp256k1",
"pythnet-sdk",
"rand",
"reqwest",
"serde",
"serde_json",
"serde_wormhole",
"sha3",
"tokio",
"wormhole-vaas-serde",
]
[[package]]
name = "pythnet-sdk"
version = "2.0.0"
dependencies = [
"bincode",
"borsh",
"bytemuck",
"byteorder",
"fast-math",
"hex",
"libsecp256k1",
"rand",
"rustc_version",
"serde",
"serde_wormhole",
"sha3",
"slow_primes",
"thiserror",
"wormhole-vaas-serde",
]
[[package]]
@ -2765,11 +3081,11 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.4.1"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.5.0",
]
[[package]]
@ -2828,7 +3144,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls 0.21.11",
"rustls 0.21.12",
"rustls-pemfile",
"serde",
"serde_json",
@ -2853,7 +3169,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [
"hmac",
"hmac 0.12.1",
"subtle",
]
@ -2893,7 +3209,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
dependencies = [
"digest",
"digest 0.10.7",
]
[[package]]
@ -2913,9 +3229,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.33"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.5.0",
"errno",
@ -2939,9 +3255,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.21.11"
version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring 0.17.8",
@ -3048,6 +3364,30 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "schemars"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 1.0.109",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -3060,10 +3400,10 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d"
dependencies = [
"hmac",
"hmac 0.12.1",
"pbkdf2 0.11.0",
"salsa20",
"sha2",
"sha2 0.10.8",
]
[[package]]
@ -3159,24 +3499,44 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "serde"
version = "1.0.198"
version = "1.0.199"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.198"
name = "serde_bytes"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.199"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "serde_derive_internals"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "serde_json"
version = "1.0.116"
@ -3212,9 +3572,9 @@ dependencies = [
[[package]]
name = "serde_with"
version = "3.7.0"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
checksum = "2c85f8e96d1d6857f13768fcbd895fcb06225510022a2774ed8b5150581847b0"
dependencies = [
"serde",
"serde_derive",
@ -3232,6 +3592,32 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "serde_wormhole"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b022bf813578a06341fd453c3fd6e64945d9975191193d5d45e8dbd97d1d84"
dependencies = [
"base64 0.13.1",
"itoa",
"serde",
"serde_bytes",
"thiserror",
]
[[package]]
name = "sha2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.10.8"
@ -3240,7 +3626,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
"digest 0.10.7",
]
[[package]]
@ -3249,7 +3635,7 @@ version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
"digest",
"digest 0.10.7",
"keccak",
]
@ -3268,7 +3654,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"digest 0.10.7",
"rand_core",
]
@ -3281,6 +3667,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "slow_primes"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58267dd2fbaa6dceecba9e3e106d2d90a2b02497c0e8b01b8759beccf5113938"
dependencies = [
"num",
]
[[package]]
name = "smallvec"
version = "1.13.2"
@ -3604,7 +3999,7 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.11",
"rustls 0.21.12",
"tokio",
]
@ -3634,6 +4029,15 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
@ -4179,6 +4583,32 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "wormhole-supported-chains"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f42a80a24212937cc7d7b0ab8115bb87d82f949a1a42f75d500807072c94ba4"
dependencies = [
"serde",
"thiserror",
]
[[package]]
name = "wormhole-vaas-serde"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240c5a6136dc66ecc65097bb6d159e849b5df4ecbbbb220868d0edbdcc568ed3"
dependencies = [
"anyhow",
"bstr",
"schemars",
"serde",
"serde_wormhole",
"sha3",
"thiserror",
"wormhole-supported-chains",
]
[[package]]
name = "wyz"
version = "0.5.1"

View File

@ -16,6 +16,12 @@ reqwest = "0.11.27"
serde_json = "1.0.114"
serde = "1.0.197"
dotenv = "0.15.0"
libsecp256k1 = "0.7.1"
pythnet-sdk = { path = "../../../pythnet/pythnet_sdk", features = ["test-utils"] }
sha3 = "0.10.8"
serde_wormhole = { version ="0.1.0" }
wormhole-vaas-serde = { version = "0.1.0" }
[[bin]]
name = "deploy_pyth"

View File

@ -1,6 +1,6 @@
[[package]]
name = "core"
source = "path+from-root-C3992B43B72ADB8C"
source = "path+from-root-566CA1D5F8BEAFBF"
[[package]]
name = "ownership"
@ -40,5 +40,5 @@ dependencies = ["std"]
[[package]]
name = "std"
source = "git+https://github.com/fuellabs/sway?tag=v0.49.1#2ac7030570f22510b0ac2a7b5ddf7baa20bdc0e1"
source = "git+https://github.com/fuellabs/sway?tag=v0.49.3#0dc6570377ee9c4a6359ade597fa27351e02a728"
dependencies = ["core"]

View File

@ -1,6 +1,6 @@
[toolchain]
channel = "nightly-2024-01-24"
channel = "beta-5"
[components]
forc = "0.49.1"
fuel-core = "0.22.0"
forc = "0.49.3"
fuel-core = "0.22.4"

View File

@ -6,3 +6,4 @@ pub mod price;
pub mod accumulator_update;
pub mod batch_attestation_update;
pub mod update_type;
pub mod governance_instruction;

View File

@ -19,7 +19,7 @@ impl DataSource {
}
#[storage(read)]
pub fn is_valid(
pub fn is_valid_data_source(
self,
is_valid_data_source: StorageKey<StorageMap<DataSource, bool>>,
) -> bool {
@ -28,4 +28,8 @@ impl DataSource {
None => false,
}
}
pub fn is_valid_governance_data_source(self, chain_id: u16, emitter_address: b256) -> bool {
self.chain_id == chain_id && self.emitter_address == emitter_address
}
}

View File

@ -0,0 +1,183 @@
library;
use ::errors::PythError;
use ::data_structures::{data_source::*, price::*, wormhole_light::{StorageGuardianSet, WormholeVM}};
use pyth_interface::data_structures::{data_source::DataSource, price::{PriceFeed, PriceFeedId}, governance_payload::{UpgradeContractPayload, AuthorizeGovernanceDataSourceTransferPayload, RequestGovernanceDataSourceTransferPayload, SetDataSourcesPayload, SetFeePayload, SetValidPeriodPayload}, governance_instruction::{GovernanceInstruction, GovernanceModule, GovernanceAction}};
use std::{bytes::Bytes, hash::Hash};
use std::math::*;
use std::primitive_conversions::{u32::*, u64::*};
pub const MAGIC: u32 = 0x5054474d;
impl GovernanceInstruction {
pub fn new(magic: u32,
module: GovernanceModule,
action: GovernanceAction,
target_chain_id: u16,
payload: Bytes
) -> Self {
Self { magic, module, action, target_chain_id, payload }
}
pub fn parse_governance_instruction(encoded_instruction: Bytes) -> Self {
let mut index = 0;
let magic = u32::from_be_bytes([
encoded_instruction.get(index).unwrap(),
encoded_instruction.get(index + 1).unwrap(),
encoded_instruction.get(index + 2).unwrap(),
encoded_instruction.get(index + 3).unwrap(),
]);
require(magic == MAGIC, PythError::InvalidMagic);
index += 4;
let mod_number = encoded_instruction.get(index).unwrap();
let module = match mod_number {
0 => GovernanceModule::Executor,
1 => GovernanceModule::Target,
2 => GovernanceModule::EvmExecutor,
3 => GovernanceModule::StacksTarget,
_ => GovernanceModule::Invalid,
};
require(match module {
GovernanceModule::Target => true,
_ => false,
}, PythError::InvalidGovernanceTarget);
index += 1;
let action_number = encoded_instruction.get(index).unwrap();
let governance_action = match action_number {
0 => GovernanceAction::UpgradeContract, // Not implemented
1 => GovernanceAction::AuthorizeGovernanceDataSourceTransfer,
2 => GovernanceAction::SetDataSources,
3 => GovernanceAction::SetFee,
4 => GovernanceAction::SetValidPeriod,
5 => GovernanceAction::RequestGovernanceDataSourceTransfer,
_ => GovernanceAction::Invalid,
};
require(match governance_action {
GovernanceAction::Invalid => false,
_ => true,
}, PythError::InvalidGovernanceAction);
index += 1;
let target_chain_id = u16::from_be_bytes([
encoded_instruction.get(index).unwrap(),
encoded_instruction.get(index + 1).unwrap(),
]);
index += 2;
let (_, payload) = encoded_instruction.split_at(index);
GovernanceInstruction::new(
magic,
module,
governance_action,
target_chain_id,
payload,
)
}
/// Parse an AuthorizeGovernanceDataSourceTransferPayload (action 2) with minimal validation
pub fn parse_authorize_governance_data_source_transfer_payload(encoded_payload: Bytes) -> AuthorizeGovernanceDataSourceTransferPayload {
AuthorizeGovernanceDataSourceTransferPayload {
claim_vaa: encoded_payload,
}
}
pub fn parse_request_governance_data_source_transfer_payload(encoded_payload: Bytes) -> RequestGovernanceDataSourceTransferPayload {
let mut index = 0;
let governance_data_source_index = u32::from_be_bytes([
encoded_payload.get(index).unwrap(),
encoded_payload.get(index + 1).unwrap(),
encoded_payload.get(index + 2).unwrap(),
encoded_payload.get(index + 3).unwrap(),
]);
index += 4;
require(index == encoded_payload.len(), PythError::InvalidGovernanceMessage);
let rdgst = RequestGovernanceDataSourceTransferPayload {
governance_data_source_index,
};
rdgst
}
pub fn parse_set_data_sources_payload(encoded_payload: Bytes) -> SetDataSourcesPayload {
let mut index = 0;
let data_sources_length = encoded_payload.get(index).unwrap().as_u64();
index += 1;
let mut data_sources = Vec::with_capacity(data_sources_length);
let mut i = 0;
while i < data_sources_length {
let (_, slice) = encoded_payload.split_at(index);
let (slice, _) = slice.split_at(2);
let chain_id = u16::from_be_bytes([slice.get(0).unwrap(), slice.get(1).unwrap()]);
index += 2;
let (_, slice) = encoded_payload.split_at(index);
let (slice, _) = slice.split_at(32);
let emitter_address: b256 = slice.into();
index += 32;
data_sources.push(DataSource {
chain_id,
emitter_address,
});
i += 1
}
require(index == encoded_payload.len(), PythError::InvalidGovernanceMessage);
let sds = SetDataSourcesPayload { data_sources };
sds
}
pub fn parse_set_fee_payload(encoded_payload: Bytes) -> SetFeePayload {
let mut index = 0;
let val = u64::from_be_bytes([
encoded_payload.get(index).unwrap(),
encoded_payload.get(index + 1).unwrap(),
encoded_payload.get(index + 2).unwrap(),
encoded_payload.get(index + 3).unwrap(),
encoded_payload.get(index + 4).unwrap(),
encoded_payload.get(index + 5).unwrap(),
encoded_payload.get(index + 6).unwrap(),
encoded_payload.get(index + 7).unwrap(),
]);
index += 8;
let expo = u64::from_be_bytes([
encoded_payload.get(index).unwrap(),
encoded_payload.get(index + 1).unwrap(),
encoded_payload.get(index + 2).unwrap(),
encoded_payload.get(index + 3).unwrap(),
encoded_payload.get(index + 4).unwrap(),
encoded_payload.get(index + 5).unwrap(),
encoded_payload.get(index + 6).unwrap(),
encoded_payload.get(index + 7).unwrap(),
]);
index += 8;
require(encoded_payload.len() == index, PythError::InvalidGovernanceMessage);
let sf = SetFeePayload {
new_fee: val * 10u64.pow(expo.try_as_u32().unwrap()),
};
sf
}
pub fn parse_set_valid_period_payload(encoded_payload: Bytes) -> SetValidPeriodPayload {
let mut index = 0;
let valid_time_period_seconds = u64::from_be_bytes([
encoded_payload.get(index).unwrap(),
encoded_payload.get(index + 1).unwrap(),
encoded_payload.get(index + 2).unwrap(),
encoded_payload.get(index + 3).unwrap(),
encoded_payload.get(index + 4).unwrap(),
encoded_payload.get(index + 5).unwrap(),
encoded_payload.get(index + 6).unwrap(),
encoded_payload.get(index + 7).unwrap(),
]);
index += 8;
require(index == encoded_payload.len(), PythError::InvalidGovernanceMessage);
let svp = SetValidPeriodPayload {
new_valid_period: valid_time_period_seconds,
};
svp
}
}

View File

@ -6,7 +6,6 @@ use pyth_interface::data_structures::{
data_source::DataSource,
wormhole_light::{
GuardianSet,
WormholeProvider,
},
};
use std::{
@ -145,15 +144,6 @@ impl GuardianSetUpgrade {
}
}
impl WormholeProvider {
pub fn new(governance_chain_id: u16, governance_contract: b256) -> Self {
WormholeProvider {
governance_chain_id,
governance_contract,
}
}
}
pub struct GuardianSignature {
guardian_index: u8,
r: b256,
@ -582,7 +572,7 @@ impl WormholeVM {
);
require(
DataSource::new(vm.emitter_chain_id, vm.emitter_address)
.is_valid(is_valid_data_source),
.is_valid_data_source(is_valid_data_source),
WormholeError::InvalidUpdateDataSource,
);
vm

View File

@ -9,6 +9,11 @@ pub enum PythError {
InvalidAttestationSize: (),
InvalidDataSourcesLength: (),
InvalidExponent: (),
InvalidGovernanceDataSource: (),
InvalidGovernanceAction: (),
InvalidGovernanceMessage: (),
InvalidGovernanceModule: (),
InvalidGovernanceTarget: (),
InvalidHeaderSize: (),
InvalidMagic: (),
InvalidMajorVersion: (),
@ -21,9 +26,11 @@ pub enum PythError {
InvalidUpdateDataLength: (),
InvalidUpdateDataSource: (),
InvalidUpgradeModule: (),
InvalidWormholeAddressToSet: (),
LengthOfPriceFeedIdsAndPublishTimesMustMatch: (),
NewGuardianSetIsEmpty: (),
NumberOfUpdatesIrretrievable: (),
OldGovernanceMessage: (),
/// Emitted when a Price's `publish_time` is stale.
OutdatedPrice: (),
/// Emitted when a PriceFeed could not be retrieved.

View File

@ -3,7 +3,6 @@ library;
use pyth_interface::data_structures::{
data_source::DataSource,
price::PriceFeedId,
wormhole_light::WormholeProvider,
};
pub struct ConstructedEvent {
@ -19,3 +18,29 @@ pub struct NewGuardianSetEvent {
pub struct UpdatedPriceFeedsEvent {
updated_price_feeds: Vec<PriceFeedId>,
}
pub struct ContractUpgradedEvent {
old_implementation: Identity,
new_implementation: Identity,
}
pub struct GovernanceDataSourceSetEvent {
old_data_source: DataSource,
new_data_source: DataSource,
initial_sequence: u64,
}
pub struct DataSourcesSetEvent {
old_data_sources: Vec<DataSource>,
new_data_sources: Vec<DataSource>,
}
pub struct FeeSetEvent {
old_fee: u64,
new_fee: u64,
}
pub struct ValidPeriodSetEvent {
old_valid_period: u64,
new_valid_period: u64,
}

View File

@ -15,12 +15,17 @@ use std::{
ZERO_B256,
},
context::msg_amount,
hash::Hash,
hash::{
Hash,
keccak256,
sha256,
},
storage::{
storage_map::StorageMap,
storage_vec::*,
},
u256::U256,
revert::revert,
};
use ::errors::{PythError, WormholeError};
@ -31,8 +36,9 @@ use ::data_structures::{
price::*,
update_type::UpdateType,
wormhole_light::*,
governance_instruction::*,
};
use ::events::{ConstructedEvent, NewGuardianSetEvent, UpdatedPriceFeedsEvent};
use ::events::{ConstructedEvent, NewGuardianSetEvent, UpdatedPriceFeedsEvent, ContractUpgradedEvent, GovernanceDataSourceSetEvent, DataSourcesSetEvent, FeeSetEvent, ValidPeriodSetEvent};
use pyth_interface::{
data_structures::{
@ -42,10 +48,11 @@ use pyth_interface::{
PriceFeed,
PriceFeedId,
},
governance_payload::{UpgradeContractPayload, AuthorizeGovernanceDataSourceTransferPayload, SetDataSourcesPayload, SetFeePayload, SetValidPeriodPayload},
wormhole_light::{
GuardianSet,
WormholeProvider,
},
governance_instruction::{GovernanceInstruction, GovernanceModule, GovernanceAction},
},
PythCore,
PythInfo,
@ -70,6 +77,7 @@ storage {
// Mapping of cached price information
// priceId => PriceInfo
latest_price_feed: StorageMap<PriceFeedId, PriceFeed> = StorageMap {},
// Fee required for each update
single_update_fee: u64 = 0,
// For tracking all active emitter/chain ID pairs
valid_data_sources: StorageVec<DataSource> = StorageVec {},
@ -77,20 +85,38 @@ storage {
/// This includes attestation delay, block time, and potential clock drift
/// between the source/target chains.
valid_time_period_seconds: u64 = 0,
// | |
// --+-- WORMHOLE STATE --+--
// | |
// Mapping of consumed governance actions
wormhole_consumed_governance_actions: StorageMap<b256, bool> = StorageMap {},
// Mapping of guardian_set_index => guardian set
wormhole_guardian_sets: StorageMap<u32, StorageGuardianSet> = StorageMap {},
// Current active guardian set index
wormhole_guardian_set_index: u32 = 0,
// Using Ethereum's Wormhole governance
wormhole_provider: WormholeProvider = WormholeProvider {
governance_chain_id: 0u16,
governance_contract: ZERO_B256,
/// Governance data source. VAA messages from this source can change this contract
/// state. e.g., upgrade the contract, change the valid data sources, and more.
governance_data_source: DataSource = DataSource {
chain_id: 0u16,
emitter_address: ZERO_B256,
},
/// Index of the governance data source, increased each time the governance data source changes.
governance_data_source_index: u32 = 0,
/// Sequence number of the last executed governance message. Any governance message
/// with a lower or equal sequence number will be discarded. This prevents double-execution,
/// and also makes sure that messages are executed in the right order.
last_executed_governance_sequence: u64 = 0,
/// Chain ID of the contract
chain_id: u16 = 0,
/// | |
/// --+-- WORMHOLE STATE --+--
/// | |
/// Mapping of consumed governance actions
wormhole_consumed_governance_actions: StorageMap<b256, bool> = StorageMap {},
/// Mapping of guardian_set_index => guardian set
wormhole_guardian_sets: StorageMap<u32, StorageGuardianSet> = StorageMap {},
/// Current active guardian set index
wormhole_guardian_set_index: u32 = 0,
/// Using Ethereum's Wormhole governance
wormhole_governance_data_source: DataSource = DataSource {
chain_id: 0u16,
emitter_address: ZERO_B256,
},
/// | |
/// --+-- GOVERNANCE STATE --+--
/// | |
current_implementation: Identity = Identity::Address(Address::from(ZERO_B256)),
}
impl SRC5 for Contract {
@ -409,15 +435,61 @@ fn valid_time_period() -> u64 {
storage.valid_time_period_seconds.read()
}
#[storage(read)]
fn governance_data_source() -> DataSource {
storage.governance_data_source.read()
}
#[storage(write)]
fn set_governance_data_source(data_source: DataSource) {
storage.governance_data_source.write(data_source);
}
#[storage(read)]
fn governance_data_source_index() -> u32 {
storage.governance_data_source_index.read()
}
#[storage(write)]
fn set_governance_data_source_index(index: u32) {
storage.governance_data_source_index.write(index);
}
#[storage(read)]
fn last_executed_governance_sequence() -> u64 {
storage.last_executed_governance_sequence.read()
}
#[storage(write)]
fn set_last_executed_governance_sequence(sequence: u64) {
storage.last_executed_governance_sequence.write(sequence);
}
#[storage(read)]
fn chain_id() -> u16 {
storage.chain_id.read()
}
#[storage(read)]
fn current_implementation() -> Identity {
storage.current_implementation.read()
}
impl PythInit for Contract {
#[storage(read, write)]
fn constructor(
data_sources: Vec<DataSource>,
governance_data_source: DataSource,
wormhole_governance_data_source: DataSource,
single_update_fee: u64,
valid_time_period_seconds: u64,
wormhole_guardian_set_upgrade: Bytes,
wormhole_guardian_set_addresses: Vec<b256>,
wormhole_guardian_set_index: u32,
chain_id: u16,
) {
// This function sets the passed identity as the initial owner. https://github.com/FuelLabs/sway-libs/blob/8045a19e3297599750abdf6300c11e9927a29d40/libs/src/ownership.sw#L127-L138
initialize_ownership(DEPLOYER);
// This function ensures that the sender is the owner. https://github.com/FuelLabs/sway-libs/blob/8045a19e3297599750abdf6300c11e9927a29d40/libs/src/ownership.sw#L59-L65
only_owner();
require(data_sources.len > 0, PythError::InvalidDataSourcesLength);
@ -436,26 +508,35 @@ impl PythInit for Contract {
.write(valid_time_period_seconds);
storage.single_update_fee.write(single_update_fee);
let vm = WormholeVM::parse_initial_wormhole_vm(wormhole_guardian_set_upgrade);
let upgrade = GuardianSetUpgrade::parse_encoded_upgrade(0, vm.payload);
let guardian_length: u8 = wormhole_guardian_set_addresses.len().try_as_u8().unwrap();
let mut new_guardian_set = StorageGuardianSet::new(
0,
StorageKey {
slot: sha256(("guardian_set_keys", wormhole_guardian_set_index)),
offset: 0,
field_id: ZERO_B256,
},
);
let mut i: u8 = 0;
while i < guardian_length {
let key: b256 = wormhole_guardian_set_addresses.get(i.as_u64()).unwrap();
new_guardian_set.keys.push(key);
i += 1;
}
storage
.wormhole_consumed_governance_actions
.insert(vm.governance_action_hash, true);
storage
.wormhole_guardian_sets
.insert(upgrade.new_guardian_set_index, upgrade.new_guardian_set);
storage
.wormhole_guardian_set_index
.write(upgrade.new_guardian_set_index);
storage
.wormhole_provider
.write(WormholeProvider::new(vm.emitter_chain_id, vm.emitter_address));
storage.wormhole_guardian_set_index.write(wormhole_guardian_set_index);
storage.wormhole_guardian_sets.insert(wormhole_guardian_set_index, new_guardian_set);
storage.governance_data_source.write(governance_data_source);
storage.wormhole_governance_data_source.write(wormhole_governance_data_source);
storage.chain_id.write(chain_id);
// This function revokes ownership of the current owner and disallows any new owners. https://github.com/FuelLabs/sway-libs/blob/8045a19e3297599750abdf6300c11e9927a29d40/libs/src/ownership.sw#L89-L99
renounce_ownership();
log(ConstructedEvent {
guardian_set_index: upgrade.new_guardian_set_index,
guardian_set_index: wormhole_guardian_set_index,
})
}
}
@ -492,8 +573,8 @@ impl PythInfo for Contract {
}
#[storage(read)]
fn valid_data_source(data_source: DataSource) -> bool {
data_source.is_valid(storage.is_valid_data_source)
fn is_valid_data_source(data_source: DataSource) -> bool {
data_source.is_valid_data_source(storage.is_valid_data_source)
}
}
@ -513,7 +594,7 @@ impl WormholeGuardians for Contract {
}
#[storage(read)]
fn current_wormhole_provider() -> WormholeProvider {
fn current_wormhole_provider() -> DataSource {
current_wormhole_provider()
}
@ -546,8 +627,8 @@ fn current_guardian_set_index() -> u32 {
}
#[storage(read)]
fn current_wormhole_provider() -> WormholeProvider {
storage.wormhole_provider.read()
fn current_wormhole_provider() -> DataSource {
storage.wormhole_governance_data_source.read()
}
#[storage(read)]
@ -573,12 +654,12 @@ fn submit_new_guardian_set(encoded_vm: Bytes) {
let current_wormhole_provider = current_wormhole_provider();
require(
vm.emitter_chain_id == current_wormhole_provider
.governance_chain_id,
.chain_id,
WormholeError::InvalidGovernanceChain,
);
require(
vm.emitter_address == current_wormhole_provider
.governance_contract,
.emitter_address,
WormholeError::InvalidGovernanceContract,
);
require(
@ -615,3 +696,194 @@ fn submit_new_guardian_set(encoded_vm: Bytes) {
new_guardian_set_index: upgrade.new_guardian_set_index,
})
}
/// Transfer the governance data source to a new value with sanity checks to ensure the new governance data source can manage the contract.
#[storage(read, write)]
fn authorize_governance_data_source_transfer(payload: AuthorizeGovernanceDataSourceTransferPayload) {
let old_governance_data_source = governance_data_source();
// Parse and verify the VAA contained in the payload to ensure it's valid and can manage the contract
let vm = WormholeVM::parse_and_verify_wormhole_vm(
current_guardian_set_index(),
payload.claim_vaa,
storage.wormhole_guardian_sets,
);
let gi = GovernanceInstruction::parse_governance_instruction(vm.payload);
require(gi.target_chain_id == chain_id() || gi.target_chain_id == 0, PythError::InvalidGovernanceTarget);
require(match gi.action {
GovernanceAction::RequestGovernanceDataSourceTransfer => true,
_ => false,
}, PythError::InvalidGovernanceMessage);
let claim_payload = GovernanceInstruction::parse_request_governance_data_source_transfer_payload(gi.payload);
require(governance_data_source_index() < claim_payload.governance_data_source_index, PythError::OldGovernanceMessage);
set_governance_data_source_index(claim_payload.governance_data_source_index);
let new_governance_data_source = DataSource {
chain_id: vm.emitter_chain_id,
emitter_address: vm.emitter_address,
};
set_governance_data_source(new_governance_data_source);
// Setting the last executed governance to the claimVaa sequence to avoid using older sequences.
set_last_executed_governance_sequence(vm.sequence);
log(GovernanceDataSourceSetEvent {
old_data_source: old_governance_data_source,
new_data_source: new_governance_data_source,
initial_sequence: vm.sequence,
});
}
#[storage(read, write)]
fn set_data_sources(payload: SetDataSourcesPayload) {
let old_data_sources = storage.valid_data_sources.load_vec();
let mut i = 0;
while i < old_data_sources.len {
let data_source = old_data_sources.get(i).unwrap();
storage.is_valid_data_source.insert(data_source, false);
i += 1;
}
// Clear the current list of valid data sources
storage.valid_data_sources.clear();
i = 0;
// Add new data sources from the payload and mark them as valid
while i < payload.data_sources.len {
let data_source = payload.data_sources.get(i).unwrap();
storage.valid_data_sources.push(data_source);
storage.is_valid_data_source.insert(data_source, true);
i += 1;
}
// Emit an event with the old and new data sources
log(DataSourcesSetEvent {
old_data_sources: old_data_sources,
new_data_sources: storage.valid_data_sources.load_vec(),
});
}
#[storage(read, write)]
fn set_fee(payload: SetFeePayload) {
let old_fee = storage.single_update_fee.read();
storage.single_update_fee.write(payload.new_fee);
log(FeeSetEvent {
old_fee,
new_fee: payload.new_fee,
});
}
#[storage(read, write)]
fn set_valid_period(payload: SetValidPeriodPayload) {
let old_valid_period = storage.valid_time_period_seconds.read();
storage.valid_time_period_seconds.write(payload.new_valid_period);
log(ValidPeriodSetEvent {
old_valid_period,
new_valid_period: payload.new_valid_period,
});
}
abi PythGovernance {
#[storage(read)]
fn governance_data_source() -> DataSource;
#[storage(read, write)]
fn execute_governance_instruction(encoded_vm: Bytes);
}
impl PythGovernance for Contract {
#[storage(read)]
fn governance_data_source() -> DataSource {
governance_data_source()
}
#[storage(read, write)]
fn execute_governance_instruction(encoded_vm: Bytes) {
let vm = verify_governance_vm(encoded_vm);
// Log so that the WormholeVM struct will show up in the ABI and can be used in the tests
log(vm);
let gi = GovernanceInstruction::parse_governance_instruction(vm.payload);
// Log so that the GovernanceInstruction struct will show up in the ABI and can be used in the tests
log(gi);
require(gi.target_chain_id == chain_id() || gi.target_chain_id == 0, PythError::InvalidGovernanceTarget);
match gi.action {
GovernanceAction::UpgradeContract => {
require(gi.target_chain_id != 0, PythError::InvalidGovernanceTarget);
// TODO: implement upgrade_upgradeable_contract(uc) when Fuel releases the upgrade standard library;
log("Upgrade functionality not implemented");
revert(0u64);
},
GovernanceAction::AuthorizeGovernanceDataSourceTransfer => {
let agdst = GovernanceInstruction::parse_authorize_governance_data_source_transfer_payload(gi.payload);
log(agdst);
authorize_governance_data_source_transfer(agdst);
},
GovernanceAction::SetDataSources => {
let sdsp = GovernanceInstruction::parse_set_data_sources_payload(gi.payload);
log(sdsp);
set_data_sources(sdsp);
},
GovernanceAction::SetFee => {
let sf = GovernanceInstruction::parse_set_fee_payload(gi.payload);
log(sf);
set_fee(sf);
},
GovernanceAction::SetValidPeriod => {
let svp = GovernanceInstruction::parse_set_valid_period_payload(gi.payload);
log(svp);
set_valid_period(svp);
},
GovernanceAction::RequestGovernanceDataSourceTransfer => {
// RequestGovernanceDataSourceTransfer can be only part of AuthorizeGovernanceDataSourceTransfer message
// The `revert` function only accepts u64, so as
// a workaround we use require.
require(false, PythError::InvalidGovernanceMessage);
},
_ => {
// The `revert` function only accepts u64, so as
// a workaround we use require.
require(false, PythError::InvalidGovernanceMessage);
}
}
}
}
#[storage(read, write)]
fn verify_governance_vm(encoded_vm: Bytes) -> WormholeVM {
let vm = WormholeVM::parse_and_verify_wormhole_vm(
current_guardian_set_index(),
encoded_vm,
storage
.wormhole_guardian_sets,
);
require(
storage
.governance_data_source
.read()
.is_valid_governance_data_source(vm.emitter_chain_id, vm.emitter_address),
PythError::InvalidGovernanceDataSource,
);
require(
vm.sequence > last_executed_governance_sequence(),
PythError::OldGovernanceMessage,
);
set_last_executed_governance_sequence(vm.sequence);
vm
}

View File

@ -2,4 +2,6 @@ library;
pub mod data_source;
pub mod price;
pub mod governance_payload;
pub mod governance_instruction;
pub mod wormhole_light;

View File

@ -0,0 +1,29 @@
library;
use std::bytes::Bytes;
pub struct GovernanceInstruction {
magic: u32,
module: GovernanceModule,
action: GovernanceAction,
target_chain_id: u16,
payload: Bytes,
}
pub enum GovernanceModule {
Executor: (), // 0
Target: (), // 1
EvmExecutor: (), // 2
StacksTarget: (), // 3
Invalid: (),
}
pub enum GovernanceAction {
UpgradeContract: (), // 0
AuthorizeGovernanceDataSourceTransfer: (), // 1
SetDataSources: (), // 2
SetFee: (), // 3
SetValidPeriod: (), // 4
RequestGovernanceDataSourceTransfer: (), // 5
Invalid: (),
}

View File

@ -0,0 +1,29 @@
library;
use std::bytes::Bytes;
use ::data_structures::data_source::DataSource;
pub struct UpgradeContractPayload {
new_implementation: Identity,
}
pub struct AuthorizeGovernanceDataSourceTransferPayload {
claim_vaa: Bytes,
}
pub struct RequestGovernanceDataSourceTransferPayload {
governance_data_source_index: u32,
}
pub struct SetDataSourcesPayload {
data_sources: Vec<DataSource>,
}
pub struct SetFeePayload {
new_fee: u64,
}
pub struct SetValidPeriodPayload {
new_valid_period: u64,
}

View File

@ -4,8 +4,3 @@ pub struct GuardianSet {
expiration_time: u64,
keys: Vec<b256>,
}
pub struct WormholeProvider {
governance_chain_id: u16,
governance_contract: b256,
}

View File

@ -9,9 +9,9 @@ use ::data_structures::{
PriceFeed,
PriceFeedId,
},
governance_payload::UpgradeContractPayload,
wormhole_light::{
GuardianSet,
WormholeProvider,
},
};
use std::{bytes::Bytes, storage::storage_vec::*};
@ -259,9 +259,13 @@ abi PythInit {
#[storage(read, write)]
fn constructor(
data_sources: Vec<DataSource>,
governance_data_source: DataSource,
wormhole_governance_data_source: DataSource,
single_update_fee: u64,
valid_time_period_seconds: u64,
wormhole_guardian_set_upgrade: Bytes,
wormhole_guardian_set_addresses: Vec<b256>,
wormhole_guardian_set_index: u32,
chain_id: u16,
);
}
@ -284,7 +288,7 @@ abi PythInfo {
fn single_update_fee() -> u64;
#[storage(read)]
fn valid_data_source(data_source: DataSource) -> bool;
fn is_valid_data_source(data_source: DataSource) -> bool;
#[storage(read)]
fn valid_data_sources() -> Vec<DataSource>;
@ -295,7 +299,7 @@ abi WormholeGuardians {
fn current_guardian_set_index() -> u32;
#[storage(read)]
fn current_wormhole_provider() -> WormholeProvider;
fn current_wormhole_provider() -> DataSource;
#[storage(read)]
fn governance_action_is_consumed(hash: b256) -> bool;

View File

@ -2,13 +2,13 @@ use fuels::{
prelude::{Address, Provider, WalletUnlocked},
types::Bits256,
};
use pyth_sdk::{constants::BETA_5_URL, pyth_utils::guardian_set_upgrade_4_vaa};
use pyth_sdk::{constants::BETA_5_URL, pyth_utils::guardian_set_upgrade_4_addresses};
use pyth_sdk::{
constants::{
BTC_USD_PRICE_FEED_ID, DEFAULT_VALID_TIME_PERIOD, ETH_USD_PRICE_FEED_ID,
BTC_USD_PRICE_FEED_ID, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID, ETH_USD_PRICE_FEED_ID,
USDC_USD_PRICE_FEED_ID,
},
pyth_utils::{update_data_bytes, Pyth},
pyth_utils::{update_data_bytes, DataSource, Pyth},
};
#[tokio::main]
@ -26,8 +26,31 @@ async fn main() {
let pyth = Pyth::deploy(admin).await.unwrap();
let governance_data_source: DataSource = DataSource {
chain_id: 1,
emitter_address: Bits256::from_hex_str(
"5635979a221c34931e32620b9293a463065555ea71fe97cd6237ade875b12e9e",
)
.unwrap(),
};
let wormhole_governance_data_source: DataSource = DataSource {
chain_id: 1,
emitter_address: Bits256([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4,
]),
};
let _ = pyth
.constructor(DEFAULT_VALID_TIME_PERIOD, guardian_set_upgrade_4_vaa())
.constructor(
governance_data_source,
wormhole_governance_data_source,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_4_addresses(),
4,
DUMMY_CHAIN_ID,
)
.await
.unwrap();

View File

@ -1,6 +1,27 @@
use crate::pyth_utils::{Price, PriceFeed};
use crate::pyth_utils::{DataSource, Price, PriceFeed};
use fuels::types::Bits256;
pub const MAGIC: u32 = 0x5054474d;
pub const GOVERNANCE_DATA_SOURCE: DataSource = DataSource {
chain_id: 1,
emitter_address: Bits256([
0x56, 0x35, 0x97, 0x9a, 0x22, 0x1c, 0x34, 0x93, 0x1e, 0x32, 0x62, 0x0b, 0x92, 0x93, 0xa4, 0x63,
0x06, 0x55, 0x55, 0xea, 0x71, 0xfe, 0x97, 0xcd, 0x62, 0x37, 0xad, 0xe8, 0x75, 0xb1, 0x2e, 0x9e
]),
};
// only used for updating guardian set
pub const WORMHOLE_GOVERNANCE_DATA_SOURCE: DataSource = DataSource {
chain_id: 1,
emitter_address: Bits256([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 4,
]),
};
pub const DUMMY_CHAIN_ID: u16 = 1;
pub const BETA_5_URL: &str = "beta-5.fuel.network";
pub const BETA_5_PYTH_CONTRACT_ID: &str =
"0xe69daeb9fcf4c536c0fe402403b4b9e9822cc8b1f296e5d754be12cc384554c5";

View File

@ -16,7 +16,9 @@ use fuels::{
use rand::Rng;
use reqwest;
use serde_json;
use serde_wormhole::RawMessage;
use std::path::PathBuf;
use wormhole_sdk::Vaa;
abigen!(Contract(
name = "PythOracleContract",
@ -87,6 +89,80 @@ pub fn test_accumulator_update_data_bytes() -> Vec<Bytes> {
)]
}
pub fn create_set_fee_payload(new_fee: u64, exponent: u64) -> Vec<u8> {
let base = new_fee / 10u64.pow(exponent.try_into().unwrap());
let base_bytes = base.to_be_bytes();
let exponent_bytes = exponent.to_be_bytes();
let mut payload = Vec::new();
payload.extend_from_slice(&base_bytes);
payload.extend_from_slice(&exponent_bytes);
payload
}
pub fn create_set_valid_period_payload(new_valid_period: u64) -> Vec<u8> {
let valid_period_bytes = new_valid_period.to_be_bytes();
let mut payload = Vec::new();
payload.extend_from_slice(&valid_period_bytes);
payload
}
pub fn create_set_data_sources_payload(data_sources: Vec<DataSource>) -> Vec<u8> {
let mut payload = Vec::new();
payload.push(data_sources.len() as u8);
for data_source in data_sources {
payload.extend_from_slice(&data_source.chain_id.to_be_bytes());
payload.extend_from_slice(&data_source.emitter_address.0);
}
payload
}
pub fn create_authorize_governance_data_source_transfer_payload(
claim_vaa: Vaa<Box<RawMessage>>,
) -> Vec<u8> {
serde_wormhole::to_vec(&claim_vaa).unwrap()
}
pub fn create_request_governance_data_source_transfer_payload(
governance_data_source_index: u32,
) -> Vec<u8> {
let index_bytes = governance_data_source_index.to_be_bytes();
let mut payload = Vec::new();
payload.extend_from_slice(&index_bytes);
payload
}
pub fn create_governance_instruction_payload(
magic: u32,
module: GovernanceModule,
action: GovernanceAction,
target_chain_id: u16,
payload: Vec<u8>,
) -> Vec<u8> {
let mut buffer = Vec::new();
buffer.extend_from_slice(&magic.to_be_bytes());
let module_number = match module {
GovernanceModule::Executor => 0,
GovernanceModule::Target => 1,
GovernanceModule::EvmExecutor => 2,
GovernanceModule::StacksTarget => 3,
GovernanceModule::Invalid => u8::MAX, // Typically 255 for invalid
};
buffer.push(module_number);
let action_number = match action {
GovernanceAction::UpgradeContract => 0,
GovernanceAction::AuthorizeGovernanceDataSourceTransfer => 1,
GovernanceAction::SetDataSources => 2,
GovernanceAction::SetFee => 3,
GovernanceAction::SetValidPeriod => 4,
GovernanceAction::RequestGovernanceDataSourceTransfer => 5,
GovernanceAction::Invalid => u8::MAX, // Typically 255 for invalid
};
buffer.push(action_number);
buffer.extend_from_slice(&target_chain_id.to_be_bytes());
buffer.extend_from_slice(&payload);
buffer
}
impl Pyth {
pub async fn price(&self, price_feed_id: Bits256) -> Result<FuelCallResponse<Price>, Error> {
self.instance
@ -119,16 +195,24 @@ impl Pyth {
pub async fn constructor(
&self,
governance_data_source: DataSource,
wormhole_governance_data_source: DataSource,
valid_time_period_seconds: u64,
wormhole_guardian_set_upgrade: Bytes,
wormhole_guardian_set_addresses: Vec<Bits256>,
wormhole_guardian_set_index: u32,
chain_id: u16,
) -> Result<FuelCallResponse<()>, Error> {
self.instance
.methods()
.constructor(
default_data_sources(),
governance_data_source,
wormhole_governance_data_source,
DEFAULT_SINGLE_UPDATE_FEE,
valid_time_period_seconds,
wormhole_guardian_set_upgrade,
wormhole_guardian_set_addresses,
wormhole_guardian_set_index,
chain_id,
)
.with_tx_policies(TxPolicies::default().with_gas_price(1))
.call()
@ -173,6 +257,68 @@ pub fn guardian_set_upgrade_4_vaa() -> Bytes {
Bytes(hex::decode(GUARDIAN_SET_UPGRADE_4_VAA).unwrap())
}
// Full list of guardian set upgrade 3 addresses can be found here: https://github.com/wormhole-foundation/wormhole-networks/blob/master/mainnetv2/guardianset/v3.prototxt
pub fn guardian_set_upgrade_3_addresses() -> Vec<Bits256> {
let addresses = vec![
"58CC3AE5C097b213cE3c81979e1B9f9570746AA5", // Certus One
"fF6CB952589BDE862c25Ef4392132fb9D4A42157", // Staked
"114De8460193bdf3A2fCf81f86a09765F4762fD1", // Figment
"107A0086b32d7A0977926A205131d8731D39cbEB", // ChainodeTech
"8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2", // Inotel
"11b39756C042441BE6D8650b69b54EbE715E2343", // HashQuark
"54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd", // Chainlayer
"15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20", // xLabs
"74a3bf913953D695260D88BC1aA25A4eeE363ef0", // Forbole
"000aC0076727b35FBea2dAc28fEE5cCB0fEA768e", // Staking Fund
"AF45Ced136b9D9e24903464AE889F5C8a723FC14", // Moonlet Wallet
"f93124b7c738843CBB89E864c862c38cddCccF95", // P2P
"D2CC37A4dc036a8D232b48f62cDD4731412f4890", // 01Node
"DA798F6896A3331F64b48c12D1D57Fd9cbe70811", // MCF
"71AA1BE1D36CaFE3867910F99C09e347899C19C3", // Everstake
"8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf", // Chorus One
"178e21ad2E77AE06711549CFBB1f9c7a9d8096e8", // Syncnode
"5E1487F35515d02A92753504a8D75471b9f49EdB", // Triton
"6FbEBc898F403E4773E95feB15E80C9A99c8348d", // Staking Facilities
];
// Convert the addresses to Bits256 by padding the leftmost 12 bytes with zeros. This is done because the original 20-byte key is shorter than the 32-byte b256 type.
addresses
.iter()
.map(|&addr| Bits256::from_hex_str(&format!("{:0>64}", addr)).unwrap())
.collect()
}
// Full list of guardian set upgrade 4 addresses can be found here: https://github.com/wormhole-foundation/wormhole-networks/blob/master/mainnetv2/guardianset/v4.prototxt
pub fn guardian_set_upgrade_4_addresses() -> Vec<Bits256> {
let addresses = vec![
"5893B5A76c3f739645648885bDCcC06cd70a3Cd3", // RockawayX
"fF6CB952589BDE862c25Ef4392132fb9D4A42157", // Staked
"114De8460193bdf3A2fCf81f86a09765F4762fD1", // Figment
"107A0086b32d7A0977926A205131d8731D39cbEB", // ChainodeTech
"8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2", // Inotel
"11b39756C042441BE6D8650b69b54EbE715E2343", // HashQuark
"54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd", // Chainlayer
"15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20", // xLabs
"74a3bf913953D695260D88BC1aA25A4eeE363ef0", // Forbole
"000aC0076727b35FBea2dAc28fEE5cCB0fEA768e", // Staking Fund
"AF45Ced136b9D9e24903464AE889F5C8a723FC14", // Moonlet Wallet
"f93124b7c738843CBB89E864c862c38cddCccF95", // P2P
"D2CC37A4dc036a8D232b48f62cDD4731412f4890", // 01Node
"DA798F6896A3331F64b48c12D1D57Fd9cbe70811", // MCF
"71AA1BE1D36CaFE3867910F99C09e347899C19C3", // Everstake
"8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf", // Chorus One
"178e21ad2E77AE06711549CFBB1f9c7a9d8096e8", // Syncnode
"5E1487F35515d02A92753504a8D75471b9f49EdB", // Triton
"6FbEBc898F403E4773E95feB15E80C9A99c8348d", // Staking Facilities
];
// Convert the addresses to Bits256 by padding the leftmost 12 bytes with zeros. This is done because the original 20-byte key is shorter than the 32-byte b256 type.
addresses
.iter()
.map(|&addr| Bits256::from_hex_str(&format!("{:0>64}", addr)).unwrap())
.collect()
}
pub fn default_price_feed_ids() -> Vec<Bits256> {
vec![
Bits256(

View File

@ -1,4 +1,5 @@
pub(crate) mod pyth_core;
pub(crate) mod pyth_info;
pub(crate) mod pyth_init;
pub(crate) mod pyth_governance;
pub(crate) mod wormhole_guardians;

View File

@ -5,12 +5,13 @@ use crate::utils::interface::{
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
TEST_ACCUMULATOR_USDC_USD_PRICE_FEED, TEST_BATCH_ETH_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED, TEST_EXTENDED_TIME_PERIOD,
DEFAULT_SINGLE_UPDATE_FEE, DUMMY_CHAIN_ID, GOVERNANCE_DATA_SOURCE,
TEST_ACCUMULATOR_ETH_USD_PRICE_FEED, TEST_ACCUMULATOR_USDC_USD_PRICE_FEED,
TEST_BATCH_ETH_USD_PRICE_FEED, TEST_BATCH_USDC_USD_PRICE_FEED, TEST_EXTENDED_TIME_PERIOD,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -26,9 +27,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
TEST_EXTENDED_TIME_PERIOD, //As the contract checks against the current timestamp, this allows unit testing with old but real price updates
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -64,9 +69,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
TEST_EXTENDED_TIME_PERIOD, //As the contract checks against the current timestamp, this allows unit testing with old but real price updates
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -4,12 +4,13 @@ use crate::utils::interface::{
};
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
TEST_ACCUMULATOR_USDC_USD_PRICE_FEED, TEST_BATCH_ETH_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED, TEST_EXTENDED_TIME_PERIOD,
TEST_BATCH_USDC_USD_PRICE_FEED, TEST_EXTENDED_TIME_PERIOD, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -26,9 +27,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -72,9 +77,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -5,12 +5,13 @@ use crate::utils::interface::{
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
TEST_ACCUMULATOR_USDC_USD_PRICE_FEED, TEST_BATCH_ETH_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED,
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, GOVERNANCE_DATA_SOURCE,
TEST_ACCUMULATOR_ETH_USD_PRICE_FEED, TEST_ACCUMULATOR_USDC_USD_PRICE_FEED,
TEST_BATCH_ETH_USD_PRICE_FEED, TEST_BATCH_USDC_USD_PRICE_FEED,
WORMHOLE_GOVERNANCE_DATA_SOURCE, DUMMY_CHAIN_ID
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -26,9 +27,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -64,9 +69,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -5,12 +5,13 @@ use crate::utils::interface::{
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
TEST_ACCUMULATOR_USDC_USD_PRICE_FEED, TEST_BATCH_ETH_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -26,9 +27,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -59,9 +64,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -2,16 +2,16 @@ use crate::utils::interface::{
pyth_core::{price, update_fee, update_price_feeds},
pyth_init::constructor,
};
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
TEST_ACCUMULATOR_USDC_USD_PRICE_FEED, TEST_BATCH_ETH_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED, TEST_EXTENDED_TIME_PERIOD,
DEFAULT_SINGLE_UPDATE_FEE, DUMMY_CHAIN_ID, GOVERNANCE_DATA_SOURCE,
TEST_ACCUMULATOR_ETH_USD_PRICE_FEED, TEST_ACCUMULATOR_USDC_USD_PRICE_FEED,
TEST_BATCH_ETH_USD_PRICE_FEED, TEST_BATCH_USDC_USD_PRICE_FEED, TEST_EXTENDED_TIME_PERIOD,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -26,9 +26,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
TEST_EXTENDED_TIME_PERIOD, //As the contract checks against the current timestamp, this allows unit testing with old but real price updates
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -64,9 +68,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
TEST_EXTENDED_TIME_PERIOD, //As the contract checks against the current timestamp, this allows unit testing with old but real price updates
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -5,12 +5,13 @@ use crate::utils::interface::{
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
TEST_ACCUMULATOR_USDC_USD_PRICE_FEED, TEST_BATCH_ETH_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED, TEST_EXTENDED_TIME_PERIOD,
TEST_BATCH_USDC_USD_PRICE_FEED, TEST_EXTENDED_TIME_PERIOD, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -27,9 +28,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -73,9 +78,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -5,12 +5,13 @@ use crate::utils::interface::{
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
TEST_ACCUMULATOR_USDC_USD_PRICE_FEED, TEST_BATCH_ETH_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -25,9 +26,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -63,9 +68,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -1,9 +1,12 @@
use crate::utils::interface::{pyth_core::update_fee, pyth_init::constructor};
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD},
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, guardian_set_upgrade_3_vaa, test_accumulator_update_data_bytes,
default_data_sources, guardian_set_upgrade_3_addresses, test_accumulator_update_data_bytes,
test_batch_update_data_bytes,
},
};
@ -18,9 +21,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -38,9 +45,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -3,12 +3,14 @@ use crate::utils::interface::{
pyth_info::price_feed_exists,
pyth_init::constructor,
};
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD},
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -23,9 +25,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -69,9 +75,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -5,9 +5,12 @@ use crate::utils::interface::{
};
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD},
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -22,9 +25,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -75,9 +82,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -0,0 +1,229 @@
use {
crate::utils::{interface::pyth_init::constructor, setup::setup_environment},
fuels::types::{Bits256, Bytes},
pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::default_data_sources,
},
pythnet_sdk::test_utils::{create_vaa_from_payload, dummy_guardians_addresses},
};
mod success {
use {
super::*,
crate::utils::interface::{
pyth_core::valid_time_period,
pyth_governance::{execute_governance_instruction, governance_data_source},
pyth_info::{single_update_fee, valid_data_sources},
},
pyth_sdk::{
constants::MAGIC,
pyth_utils::{
create_authorize_governance_data_source_transfer_payload,
create_governance_instruction_payload,
create_request_governance_data_source_transfer_payload,
create_set_data_sources_payload, create_set_fee_payload,
create_set_valid_period_payload, DataSource, GovernanceAction, GovernanceModule,
Pyth,
},
},
};
async fn setup_governance_test_environment() -> (Pyth, Vec<Bits256>) {
let (_oracle_contract_id, deployer) = setup_environment().await.unwrap();
let dummy_guardians_addresses: Vec<[u8; 20]> = dummy_guardians_addresses();
let bits256_guardians: Vec<Bits256> = dummy_guardians_addresses
.iter()
.map(|address| {
let mut full_address = [0u8; 32]; // Create a 32-byte array filled with zeros
full_address[12..].copy_from_slice(address); // Copy the 20-byte address into the rightmost part
Bits256(full_address) // Create Bits256 from the 32-byte array
})
.collect();
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
bits256_guardians.clone(),
0,
DUMMY_CHAIN_ID,
)
.await;
(deployer, bits256_guardians)
}
#[tokio::test]
async fn test_set_fee() {
let (deployer, _bits256_guardians) = setup_governance_test_environment().await;
// Test SetFee logic here
let set_fee_payload = create_set_fee_payload(100, 1);
let governance_instruction_payload = create_governance_instruction_payload(
MAGIC,
GovernanceModule::Target,
GovernanceAction::SetFee,
1,
set_fee_payload,
);
let vaa = create_vaa_from_payload(
&governance_instruction_payload,
wormhole_sdk::Address(GOVERNANCE_DATA_SOURCE.emitter_address.0),
wormhole_sdk::Chain::from(GOVERNANCE_DATA_SOURCE.chain_id),
1,
);
execute_governance_instruction(
&deployer.instance,
Bytes(serde_wormhole::to_vec(&vaa).unwrap()),
)
.await;
let fee = single_update_fee(&deployer.instance).await.value;
assert_eq!(fee, 100);
}
#[tokio::test]
async fn test_set_valid_period() {
let (deployer, _bits256_guardians) = setup_governance_test_environment().await;
// Test SetValidPeriod logic here
let set_valid_period_payload = create_set_valid_period_payload(100);
let governance_instruction_payload = create_governance_instruction_payload(
MAGIC,
GovernanceModule::Target,
GovernanceAction::SetValidPeriod,
1,
set_valid_period_payload,
);
let vaa = create_vaa_from_payload(
&governance_instruction_payload,
wormhole_sdk::Address(GOVERNANCE_DATA_SOURCE.emitter_address.0),
wormhole_sdk::Chain::from(GOVERNANCE_DATA_SOURCE.chain_id),
2,
);
execute_governance_instruction(
&deployer.instance,
Bytes(serde_wormhole::to_vec(&vaa).unwrap()),
)
.await;
let valid_period = valid_time_period(&deployer.instance).await.value;
assert_eq!(valid_period, 100);
}
#[tokio::test]
async fn test_set_data_sources() {
let (deployer, _bits256_guardians) = setup_governance_test_environment().await;
// Test SetDataSources
let test_data_sources = vec![
DataSource {
chain_id: 2,
emitter_address: Bits256([1u8; 32]),
},
DataSource {
chain_id: 27,
emitter_address: Bits256([2u8; 32]),
},
];
let set_data_sources_payload = create_set_data_sources_payload(test_data_sources.clone());
let governance_instruction_payload = create_governance_instruction_payload(
MAGIC,
GovernanceModule::Target,
GovernanceAction::SetDataSources,
1,
set_data_sources_payload,
);
let vaa = create_vaa_from_payload(
&governance_instruction_payload,
wormhole_sdk::Address(GOVERNANCE_DATA_SOURCE.emitter_address.0),
wormhole_sdk::Chain::from(GOVERNANCE_DATA_SOURCE.chain_id),
3,
);
execute_governance_instruction(
&deployer.instance,
Bytes(serde_wormhole::to_vec(&vaa).unwrap()),
)
.await;
let new_data_sources = valid_data_sources(&deployer.instance).await.value;
assert_eq!(new_data_sources, test_data_sources);
}
#[tokio::test]
async fn test_authorize_governance_data_source_transfer() {
let (deployer, _bits256_guardians) = setup_governance_test_environment().await;
// Test AuthorizeGovernanceDataSourceTransfer
let new_emitter_address = Bits256([3u8; 32]);
let new_emitter_chain = 2;
// Simulate creating a RequestGovernanceDataSourceTransfer VAA
let request_governance_data_source_transfer_payload =
create_request_governance_data_source_transfer_payload(1);
let mut governance_instruction_payload = create_governance_instruction_payload(
MAGIC,
GovernanceModule::Target,
GovernanceAction::RequestGovernanceDataSourceTransfer,
1,
request_governance_data_source_transfer_payload,
);
let mut vaa = create_vaa_from_payload(
&governance_instruction_payload,
wormhole_sdk::Address(new_emitter_address.0),
wormhole_sdk::Chain::from(new_emitter_chain),
4,
);
// Authorize the transfer
let authorize_governance_data_source_transfer_payload =
create_authorize_governance_data_source_transfer_payload(vaa);
governance_instruction_payload = create_governance_instruction_payload(
MAGIC,
GovernanceModule::Target,
GovernanceAction::AuthorizeGovernanceDataSourceTransfer,
1,
authorize_governance_data_source_transfer_payload,
);
vaa = create_vaa_from_payload(
&governance_instruction_payload,
wormhole_sdk::Address(GOVERNANCE_DATA_SOURCE.emitter_address.0),
wormhole_sdk::Chain::from(GOVERNANCE_DATA_SOURCE.chain_id),
5,
);
let old_governance_data_source = governance_data_source(&deployer.instance).await;
execute_governance_instruction(
&deployer.instance,
Bytes(serde_wormhole::to_vec(&vaa).unwrap()),
)
.await;
let new_governance_data_source = governance_data_source(&deployer.instance).await;
assert_ne!(
old_governance_data_source.value.emitter_address,
new_governance_data_source.value.emitter_address
);
assert_ne!(
old_governance_data_source.value.chain_id,
new_governance_data_source.value.chain_id
);
assert_eq!(
new_governance_data_source.value.emitter_address,
new_emitter_address
);
assert_eq!(new_governance_data_source.value.chain_id, new_emitter_chain);
}
}

View File

@ -0,0 +1 @@
pub(crate) mod execute_governance_instruction;

View File

@ -6,12 +6,13 @@ use crate::utils::interface::{
use crate::utils::setup::setup_environment;
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, DUMMY_CHAIN_ID,
GOVERNANCE_DATA_SOURCE, TEST_ACCUMULATOR_ETH_USD_PRICE_FEED,
TEST_ACCUMULATOR_USDC_USD_PRICE_FEED, TEST_BATCH_ETH_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED,
TEST_BATCH_USDC_USD_PRICE_FEED, WORMHOLE_GOVERNANCE_DATA_SOURCE,
},
pyth_utils::{
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_vaa,
default_data_sources, default_price_feed_ids, guardian_set_upgrade_3_addresses,
test_accumulator_update_data_bytes, test_batch_update_data_bytes,
},
};
@ -26,9 +27,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;
@ -57,9 +62,13 @@ mod success {
constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID,
)
.await;

View File

@ -1,6 +1,6 @@
use crate::utils::interface::{
pyth_core::valid_time_period,
pyth_info::{owner, single_update_fee, valid_data_source, valid_data_sources},
pyth_info::{is_valid_data_source, owner, single_update_fee, valid_data_sources},
pyth_init::constructor,
wormhole_guardians::{
current_guardian_set_index, current_wormhole_provider, governance_action_is_consumed,
@ -8,10 +8,10 @@ use crate::utils::interface::{
};
use pyth_sdk::{
constants::{
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, UPGRADE_3_VAA_GOVERNANCE_ACTION_HASH,
DEFAULT_SINGLE_UPDATE_FEE, DEFAULT_VALID_TIME_PERIOD, UPGRADE_3_VAA_GOVERNANCE_ACTION_HASH, GOVERNANCE_DATA_SOURCE, WORMHOLE_GOVERNANCE_DATA_SOURCE, DUMMY_CHAIN_ID
},
pyth_utils::{
default_data_sources, guardian_set_upgrade_3_vaa, ConstructedEvent, State, WormholeProvider,
default_data_sources, guardian_set_upgrade_3_addresses, ConstructedEvent, DataSource, State,
},
};
@ -29,7 +29,7 @@ mod success {
// Initial values
assert!(
!valid_data_source(&deployer.instance, &default_data_sources()[0])
!is_valid_data_source(&deployer.instance, &default_data_sources()[0])
.await
.value
);
@ -50,9 +50,9 @@ mod success {
);
assert_eq!(
current_wormhole_provider(&deployer.instance,).await.value,
WormholeProvider {
governance_chain_id: 0,
governance_contract: Bits256::zeroed(),
DataSource {
chain_id: 0,
emitter_address: Bits256::zeroed(),
}
);
assert_eq!(owner(&deployer.instance,).await.value, State::Uninitialized);
@ -60,9 +60,13 @@ mod success {
let response = constructor(
&deployer.instance,
default_data_sources(),
GOVERNANCE_DATA_SOURCE,
WORMHOLE_GOVERNANCE_DATA_SOURCE,
DEFAULT_SINGLE_UPDATE_FEE,
DEFAULT_VALID_TIME_PERIOD,
guardian_set_upgrade_3_vaa(),
guardian_set_upgrade_3_addresses(),
3,
DUMMY_CHAIN_ID
)
.await;
@ -79,7 +83,7 @@ mod success {
// Final values
assert!(
valid_data_source(&deployer.instance, &default_data_sources()[0])
is_valid_data_source(&deployer.instance, &default_data_sources()[0])
.await
.value
);
@ -95,20 +99,15 @@ mod success {
single_update_fee(&deployer.instance).await.value,
DEFAULT_SINGLE_UPDATE_FEE
);
assert!(
governance_action_is_consumed(&deployer.instance, UPGRADE_3_VAA_GOVERNANCE_ACTION_HASH)
.await
.value
);
assert_eq!(
current_guardian_set_index(&deployer.instance,).await.value,
3
);
assert_eq!(
current_wormhole_provider(&deployer.instance,).await.value,
WormholeProvider {
governance_chain_id: 1,
governance_contract: Bits256([
DataSource {
chain_id: 1,
emitter_address: Bits256([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 4
])

View File

@ -1 +1 @@
pub(crate) mod constuctor;
pub(crate) mod constructor;

View File

@ -1,4 +1,5 @@
pub(crate) mod pyth_core;
pub(crate) mod pyth_info;
pub(crate) mod pyth_init;
pub(crate) mod pyth_governance;
pub(crate) mod wormhole_guardians;

View File

@ -0,0 +1,27 @@
use fuels::{
accounts::wallet::WalletUnlocked, programs::call_response::FuelCallResponse, types::Bytes,
};
use pyth_sdk::pyth_utils::{DataSource, PythOracleContract};
pub(crate) async fn execute_governance_instruction(
contract: &PythOracleContract<WalletUnlocked>,
encoded_vm: Bytes,
) -> FuelCallResponse<()> {
contract
.methods()
.execute_governance_instruction(encoded_vm)
.call()
.await
.unwrap()
}
pub(crate) async fn governance_data_source(
contract: &PythOracleContract<WalletUnlocked>,
) -> FuelCallResponse<DataSource> {
contract
.methods()
.governance_data_source()
.call()
.await
.unwrap()
}

View File

@ -40,13 +40,13 @@ pub(crate) async fn single_update_fee(
contract.methods().single_update_fee().call().await.unwrap()
}
pub(crate) async fn valid_data_source(
pub(crate) async fn is_valid_data_source(
contract: &PythOracleContract<WalletUnlocked>,
data_source: &DataSource,
) -> FuelCallResponse<bool> {
contract
.methods()
.valid_data_source(data_source.clone())
.is_valid_data_source(data_source.clone())
.call()
.await
.unwrap()

View File

@ -1,5 +1,6 @@
use fuels::{
accounts::wallet::WalletUnlocked, prelude::Bytes, programs::call_response::FuelCallResponse,
accounts::wallet::WalletUnlocked, programs::call_response::FuelCallResponse,
types::Bits256,
};
use pyth_sdk::pyth_utils::{DataSource, PythOracleContract};
@ -7,17 +8,25 @@ use pyth_sdk::pyth_utils::{DataSource, PythOracleContract};
pub(crate) async fn constructor(
contract: &PythOracleContract<WalletUnlocked>,
data_sources: Vec<DataSource>,
governance_data_source: DataSource,
wormhole_governance_data_source: DataSource,
single_update_fee: u64,
valid_time_period_seconds: u64,
wormhole_guardian_set_upgrade: Bytes,
wormhole_guardian_set_addresses: Vec<Bits256>,
wormhole_guardian_set_index: u32,
chain_id: u16,
) -> FuelCallResponse<()> {
contract
.methods()
.constructor(
data_sources,
governance_data_source,
wormhole_governance_data_source,
single_update_fee,
valid_time_period_seconds,
wormhole_guardian_set_upgrade,
wormhole_guardian_set_addresses,
wormhole_guardian_set_index,
chain_id,
)
.call()
.await

View File

@ -1,7 +1,7 @@
use fuels::{
accounts::wallet::WalletUnlocked, programs::call_response::FuelCallResponse, types::Bits256,
};
use pyth_sdk::pyth_utils::{GuardianSet, PythOracleContract, WormholeProvider};
use pyth_sdk::pyth_utils::{GuardianSet, PythOracleContract, DataSource};
pub(crate) async fn current_guardian_set_index(
contract: &PythOracleContract<WalletUnlocked>,
@ -16,7 +16,7 @@ pub(crate) async fn current_guardian_set_index(
pub(crate) async fn current_wormhole_provider(
contract: &PythOracleContract<WalletUnlocked>,
) -> FuelCallResponse<WormholeProvider> {
) -> FuelCallResponse<DataSource> {
contract
.methods()
.current_wormhole_provider()

View File

@ -30,85 +30,32 @@ fee_contract_address=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e
${sleep}
# guardian set #0
wormhole_address=$(starkli deploy "${wormhole_hash}" \
"${owner}" \
1 `# num_guardians` \
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5 \
1 `# chain_id` \
1 `# governance_chain_id` \
4 0 `# governance_contract` \
)
${sleep}
# guardian set #1: https://raw.githubusercontent.com/wormhole-foundation/wormhole-networks/master/mainnetv2/guardianset/v1.prototxt
starkli invoke "${wormhole_address}" submit_new_guardian_set \
1 `# set index` \
19 `# num_guardians` \
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5 \
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157 \
0x114De8460193bdf3A2fCf81f86a09765F4762fD1 \
0x107A0086b32d7A0977926A205131d8731D39cbEB \
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2 \
0x11b39756C042441BE6D8650b69b54EbE715E2343 \
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd \
0xeB5F7389Fa26941519f0863349C223b73a6DDEE7 \
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0 \
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e \
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14 \
0xf93124b7c738843CBB89E864c862c38cddCccF95 \
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890 \
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811 \
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3 \
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf \
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8 \
0x5E1487F35515d02A92753504a8D75471b9f49EdB \
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d \
16 18 1766847064779994277746302277072294871108550301449637470263976489521154979 374953657095152923031303770743522269007103499920836805761143506434463979495 373725794026553362537846905304981854320892126869150736450761801254169477120 4835703278458516786446336 1131377253 3533694129556775410652111826415980944262631656421498398215501759245151417 145493015216602589471695207668173527044214450021182755196032581352392984224 267497573836069714380350521200881787609530659298168186016481773490244091266 443348533394886521835330696538264729103669807313401311199245411889706258110 200303433165499832354845293203843028338419687800279845786613090211434473108 37282816539161742972709083946551920068062204748477644719930149699874385462 111200938271608595261384934914291476246753101189480743698823215749338358345 5785682963869019134199015821749288033381872318410562688180948003975909269 372447340016996751453958019806457886428852701283870538393820846119845147788 33251468085387571623103303511315696691298281336333243761063342581452341650 323161992096034641767541451811925056802673576212351940217752194462561980347 55852237138651071644815135002358067220635692701051811455610533875912981641 190413173566657072516608762222993749133
${sleep}
# guardian set #2: https://raw.githubusercontent.com/wormhole-foundation/wormhole-networks/master/mainnetv2/guardianset/v2.prototxt
starkli invoke "${wormhole_address}" submit_new_guardian_set \
2 `# set index` \
19 `# num_guardians` \
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5 \
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157 \
0x114De8460193bdf3A2fCf81f86a09765F4762fD1 \
0x107A0086b32d7A0977926A205131d8731D39cbEB \
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2 \
0x11b39756C042441BE6D8650b69b54EbE715E2343 \
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd \
0x66B9590e1c41e0B226937bf9217D1d67Fd4E91F5 \
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0 \
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e \
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14 \
0xf93124b7c738843CBB89E864c862c38cddCccF95 \
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890 \
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811 \
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3 \
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf \
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8 \
0x5E1487F35515d02A92753504a8D75471b9f49EdB \
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d \
2 44 1766847065210651126944505525521222069599854288126726949998063840465138797 39054013088470866893599813322035661048501117089555726788687392536164861911 186918267413056900890218521593203545230034250983266377769400670103688217224 54214750922097681971590495378823998039261772575502204791108963815396679538 248994008232667872698758486001506749115615219491023036208140426934463230397 224235483823871042187452194579994340291351644933258737665365374081962645784 129444929990547403397151941456764812818993218972657847255833740800106200260 318548597134783137700627869311159664823693239605331630210202210299165477657 308852933010951129895011963941866000261904600807292935694851016610643657184 57272874228621364335170723193404742446392708606247574725663969156507973216 268057363923565984687253533797066429881117576606682526627284795527707196557 421894189151847402000239734668088381785344768284464330085711322870200424121 387584417395337067059819722404321580961380603778956902593594676080614899975 443523131755342451570503958659975367050637907361274652611595274647186073286 375107813087591446268414166006799680982485924290770541964399283524664437852 269085314426821465871247165234113878276592898426373369094610290261431112145 394348694527460459816454348661249546781513091938003106009521096332701847735 125670844183465056159554034633959680574572737212268749779705635378223489518 35053869475614771227400345921974210525173525784259894123687028214330135561 57544237843860512274491447149877290860624174166427313971286819807773907946 110681468147560408039747352809294082929396639199393789980441736520638055418 45709869872872997180086402576677640909777820941436708911954532772405320395 339910511168418517917975736269171135752028257685502872671902330279073260362 76482764517489607955778008190826845581092838692650194719207882266659669490 443663869577220861680293443959666949893574779475752540587040489501289361777 231057874919577223790659840464529054850239319545221055959374917590157019925 309066533217885002074480704480667619809952056265738927105682076502747220549 212379788814604791028007106821871908074818251907335322546331543385945316762 165408661499085325620077702639227003047567884011538988728381864749733773312 29852013947978990147012099107546124222651092940097518043136 5874446556610227706402640854088357165514903 314635470832203526600706472223155382046271943513513368538979543914002758100 289993023590817330918274026889451152915026890048318491140264484864242055689 211265316833000774821515110003986084297271807500310630074520699505436206838 314620948986744608212517578488307826224331071350776523303159889004405167502 242768143829057016675085776170635413106817756232919004913342240722183648628 289318220340670045883106021427202666948428587921558828582664470923483208386 254304247593881109676724582609273741670949040469906895867342151706444640548 324707984060675446628128892371664948354047882542253609514703956739624414429 125786084546320950738753348592393927755418642173185609412108154831520915923 192033422676298173731756291271054199566981168481817292625435767748408605264 70237018464728620254434305961956673950089621204502627373468857093940647376 75218391584551901010047495874303520775865073092730040058902770251005073864 13453
${sleep}
# guardian set #3: https://raw.githubusercontent.com/wormhole-foundation/wormhole-networks/master/mainnetv2/guardianset/v3.prototxt
starkli invoke "${wormhole_address}" submit_new_guardian_set \
3 `# set index` \
19 `# num_guardians` \
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5 \
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157 \
0x114De8460193bdf3A2fCf81f86a09765F4762fD1 \
0x107A0086b32d7A0977926A205131d8731D39cbEB \
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2 \
0x11b39756C042441BE6D8650b69b54EbE715E2343 \
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd \
0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20 \
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0 \
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e \
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14 \
0xf93124b7c738843CBB89E864c862c38cddCccF95 \
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890 \
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811 \
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3 \
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf \
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8 \
0x5E1487F35515d02A92753504a8D75471b9f49EdB \
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d \
2 44 1766847065622031860560134801367788401015571316785630090767859240961980367 408239335069601434456324970231880063141100099721451058487412176729277688481 239499689753305520381601038928513796227317320911002802551444547638809838552 377515301744513788082523380503265415136454305699441419871183462725292421897 293792427782284265052215516935690252661713253004854348809536189867737815900 307592266914902727916633292174670243188255704510048236277225897328900269063 127373290139474278928992577974696343462455769904046193125018730285162391253 391788800785481654990215164673817619378887263909639120513493454202816019863 410413307118599096084169722886408584518140871169074821252461819158667354254 18837648490111595970198935552266546643395427923804444528866768515056419823 29964034682984173558839379357821569529808274426015494950430380078317881569 86017868501532670528023530422115758730056738654625156800662778409971102579 316587967137295297243489759859408971025377360462781809717927347025414193161 412080542369789462767669836400697110505430973769191785499739175360336337147 342817079347905714229318925597762381802367663565411998187223317628701911440 323381353160339090772037140072061985169258958022395380273676898316834639836 199931590715070935127318740956564588449721873695471932311700469202637695100 53310522180389647586576928116330850824055549848985438538201222342553700451 387322343922164253479438966163491855981414317104760621828688810466847848718 81609701542274539489711635515209037026645626576756528749469616228397567798 182108205861564989333892774796475580877981373947799860454217397980367659628 21549663410658134468902761710868642321546772465973442277960059676129502668 189434039785735939400321781125039794740638779195156759980704929066694157130 52255833533187953003213955242027099690232530588872309460610106220279805641 197105018621162723372171195601447549272902142615124680111298974553437412361 243585516151555343004264928593678764289083751554802049062044286334698216184 98577806073901898829375415748245478967425496216912736575886605480181121443 92916551389967933235240931764170084503123511470557201449603712010638670912 279247190794260779926452059689914005511524938154821508635388069101252378624 27765181507524306000048567556593270127801507143251178553344 5874446556610227706402640926145951203442839 314635470832203526600706472223155382046271943513513368538979543914002758100 289993023590817330918274026889451152915026890048318491140264484864242055689 211265316833000774821515110003986084297271807500310630074520699505436206838 314620948986744608212517578488307826224331071350776523303159889004405167502 242768143829057016675085658054156069029173843566452718977789980910319968372 289318220340670045883106021427202666948428587921558828582664470923483208386 254304247593881109676724582609273741670949040469906895867342151706444640548 324707984060675446628128892371664948354047882542253609514703956739624414429 125786084546320950738753348592393927755418642173185609412108154831520915923 192033422676298173731756291271054199566981168481817292625435767748408605264 70237018464728620254434305961956673950089621204502627373468857093940647376 75218391584551901010047495874303520775865073092730040058902770251005073864 13453
${sleep}
# guardian set #4
starkli invoke "${wormhole_address}" submit_new_guardian_set \
2 44 1766847066033426987337757245669159273063358729535478806850006662056807068 191023158244075433218055661747029015323596061316379687901032561397223546211 30156550775609732785124128084945604136341937408029517653427049258063209215 301841618969457377999846355946508544313297803407604349411042057045510372286 399879387152070823070522891203531321261797829310211644637928969034931151834 1184971666775858810527395126763859219514013163556756790208661779020321698 427827873217506136303198988655697899138087317492051993053159867826892618987 55439109913191967501571602277685262841453050617358377329061538069328212552 34944602254693785869427132065664922748183924456022812505745784482260734500 50091615215549712387991200985741575718080363004681463525186508796585379155 265247833149227842278059484961926330281584344437952973839486092319885300192 421631446041795295328070636491346018953171276542115189082171457479754499396 59057903625576869235407103565877017330396402246452653660114888284545941770 315797852826246435174946736461540321579373154656484006452063031513301027405 9521420622979958910372839981791309896262328383324674284772682980734269170 272964069264268937653695089515793248726920319914036642027008415285406913245 194708434228888226032102758315234166672190899487218971410889219134701358728 117864954129109327302856065706421701676973859697066630532570005860486924893 323457021720552374478769194145226061243431674370101604382965685057422991463 327482733702858147057975319784026874245182397914737119038454598086198587150 159726033816658034104416471293601013976445904149240898589368461412472508473 165970343982649234398221341351816767302457220504375238905210573566962780340 66269488760319836583658182431744051236825244016843316092957806563966254500 360882001282595740056823749884962228392082962172369522212117195988772429063 202692667772209236945884489592750537635169234501360011152939202347962132650 407257364829649465305126488148712878739144584682351279109461295389594525334 270499607712829989691415988895838806019492861138165540862008308077962735002 388443296961168536186587069708212659389994895697827691755155284015603161464 45068266527940236008536134081672474027695203549460934893262212861351952384 31319268777966350508118557206583844424308993254125039779840 5874446556610227706402640998203302487747647 204224545225244051821590480758420624947979343122083461045877549162059250132 289993023590817330918274026889451152915026890048318491140264484864242055689 211265316833000774821515110003986084297271807500310630074520699505436206838 314620948986744608212517578488307826224331071350776523303159889004405167502 242768143829057016675085658054156069029173843566452718977789980910319968372 289318220340670045883106021427202666948428587921558828582664470923483208386 254304247593881109676724582609273741670949040469906895867342151706444640548 324707984060675446628128892371664948354047882542253609514703956739624414429 125786084546320950738753348592393927755418642173185609412108154831520915923 192033422676298173731756291271054199566981168481817292625435767748408605264 70237018464728620254434305961956673950089621204502627373468857093940647376 75218391584551901010047495874303520775865073092730040058902770251005073864 13453
${sleep}
starkli call "${wormhole_address}" parse_and_verify_vm \

View File

@ -73,7 +73,7 @@ pub impl ByteArrayImpl of ByteArrayTrait {
#[cfg(test)]
mod tests {
use super::{ByteArray, ByteArrayImpl};
use pyth::util::array_felt252_to_bytes31;
use pyth::util::array_try_into;
#[test]
fn empty_byte_array() {
@ -84,7 +84,7 @@ mod tests {
#[test]
fn byte_array_3_zeros() {
let mut array = ByteArrayImpl::new(array_felt252_to_bytes31(array![0]), 3);
let mut array = ByteArrayImpl::new(array_try_into(array![0]), 3);
assert!(array.len() == 3);
assert!(array.pop_front() == Option::Some((0.try_into().unwrap(), 3)));
assert!(array.len() == 0);
@ -93,7 +93,7 @@ mod tests {
#[test]
fn byte_array_3_bytes() {
let mut array = ByteArrayImpl::new(array_felt252_to_bytes31(array![0x010203]), 3);
let mut array = ByteArrayImpl::new(array_try_into(array![0x010203]), 3);
assert!(array.len() == 3);
assert!(array.pop_front() == Option::Some((0x010203.try_into().unwrap(), 3)));
assert!(array.len() == 0);
@ -103,7 +103,7 @@ mod tests {
#[test]
fn byte_array_single_full() {
let value_31_bytes = 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f;
let mut array = ByteArrayImpl::new(array_felt252_to_bytes31(array![value_31_bytes]), 31);
let mut array = ByteArrayImpl::new(array_try_into(array![value_31_bytes]), 31);
assert!(array.len() == 31);
assert!(array.pop_front() == Option::Some((value_31_bytes.try_into().unwrap(), 31)));
assert!(array.len() == 0);
@ -115,7 +115,7 @@ mod tests {
let value_31_bytes = 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f;
let value2_31_bytes = 0x2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f;
let mut array = ByteArrayImpl::new(
array_felt252_to_bytes31(array![value_31_bytes, value2_31_bytes]), 31
array_try_into(array![value_31_bytes, value2_31_bytes]), 31
);
assert!(array.len() == 62);
assert!(array.pop_front() == Option::Some((value_31_bytes.try_into().unwrap(), 31)));
@ -131,7 +131,7 @@ mod tests {
let value2_31_bytes = 0x2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f;
let value3_5_bytes = 0x4142434445;
let mut array = ByteArrayImpl::new(
array_felt252_to_bytes31(array![value_31_bytes, value2_31_bytes, value3_5_bytes]), 5
array_try_into(array![value_31_bytes, value2_31_bytes, value3_5_bytes]), 5
);
assert!(array.len() == 67);
assert!(array.pop_front() == Option::Some((value_31_bytes.try_into().unwrap(), 31)));
@ -151,18 +151,18 @@ mod tests {
#[test]
#[should_panic]
fn byte_array_last_too_large() {
ByteArrayImpl::new(array_felt252_to_bytes31(array![1, 2, 3]), 35);
ByteArrayImpl::new(array_try_into(array![1, 2, 3]), 35);
}
#[test]
#[should_panic]
fn byte_array_last_zero_invalid() {
ByteArrayImpl::new(array_felt252_to_bytes31(array![1, 2, 0]), 0);
ByteArrayImpl::new(array_try_into(array![1, 2, 0]), 0);
}
#[test]
#[should_panic]
fn byte_array_last_too_many_bytes() {
ByteArrayImpl::new(array_felt252_to_bytes31(array![1, 2, 0x010203]), 2);
ByteArrayImpl::new(array_try_into(array![1, 2, 0x010203]), 2);
}
}

View File

@ -55,8 +55,7 @@ pub impl HasherImpl of HasherTrait {
/// Reads all remaining data from the reader and pushes it to
/// the hashing buffer.
fn push_reader(ref self: Hasher, ref reader: Reader) -> Result<(), pyth::reader::Error> {
let mut result = Result::Ok(());
fn push_reader(ref self: Hasher, ref reader: Reader) {
while reader.len() > 0 {
let mut chunk_len = 8 - self.num_last_bytes;
if reader.len() < chunk_len.into() {
@ -66,8 +65,7 @@ pub impl HasherImpl of HasherTrait {
let value = reader.read_num_bytes(chunk_len);
// chunk_len <= 8 so value must fit in u64.
self.push_to_last(value.try_into().expect(UNEXPECTED_OVERFLOW), chunk_len);
};
result
}
}
/// Returns the keccak256 hash of the buffer. The output hash is interpreted

View File

@ -3,6 +3,7 @@ use super::reader::{Reader, ReaderImpl};
use super::byte_array::ByteArray;
use super::util::ONE_SHIFT_96;
use core::cmp::{min, max};
use core::panic_with_felt252;
const MERKLE_LEAF_PREFIX: u8 = 0;
const MERKLE_NODE_PREFIX: u8 = 1;
@ -14,6 +15,15 @@ pub enum MerkleVerificationError {
DigestMismatch,
}
impl MerkleVerificationErrorIntoFelt252 of Into<MerkleVerificationError, felt252> {
fn into(self: MerkleVerificationError) -> felt252 {
match self {
MerkleVerificationError::Reader(err) => err.into(),
MerkleVerificationError::DigestMismatch => 'digest mismatch',
}
}
}
#[generate_trait]
impl ResultReaderToMerkleVerification<T> of ResultReaderToMerkleVerificationTrait<T> {
fn map_err(self: Result<T, pyth::reader::Error>) -> Result<T, MerkleVerificationError> {
@ -24,12 +34,11 @@ impl ResultReaderToMerkleVerification<T> of ResultReaderToMerkleVerificationTrai
}
}
fn leaf_hash(mut reader: Reader) -> Result<u256, super::reader::Error> {
fn leaf_hash(mut reader: Reader) -> u256 {
let mut hasher = HasherImpl::new();
hasher.push_u8(MERKLE_LEAF_PREFIX);
hasher.push_reader(ref reader)?;
let hash = hasher.finalize() / ONE_SHIFT_96;
Result::Ok(hash)
hasher.push_reader(ref reader);
hasher.finalize() / ONE_SHIFT_96
}
fn node_hash(a: u256, b: u256) -> u256 {
@ -40,25 +49,20 @@ fn node_hash(a: u256, b: u256) -> u256 {
hasher.finalize() / ONE_SHIFT_96
}
pub fn read_and_verify_proof(
root_digest: u256, message: @ByteArray, ref reader: Reader
) -> Result<(), MerkleVerificationError> {
pub fn read_and_verify_proof(root_digest: u256, message: @ByteArray, ref reader: Reader) {
let mut message_reader = ReaderImpl::new(message.clone());
let mut current_hash = leaf_hash(message_reader.clone()).map_err()?;
let mut current_hash = leaf_hash(message_reader.clone());
let proof_size = reader.read_u8();
let mut i = 0;
let mut result = Result::Ok(());
while i < proof_size {
let sibling_digest = reader.read_u160();
current_hash = node_hash(current_hash, sibling_digest);
i += 1;
};
result?;
if root_digest != current_hash {
return Result::Err(MerkleVerificationError::DigestMismatch);
panic_with_felt252(MerkleVerificationError::DigestMismatch.into());
}
Result::Ok(())
}

View File

@ -1,144 +1,27 @@
use core::array::ArrayTrait;
use core::fmt::{Debug, Formatter};
use super::byte_array::ByteArray;
use super::util::UnwrapWithFelt252;
mod errors;
mod interface;
mod price_update;
pub use pyth::{Event, PriceFeedUpdateEvent};
#[starknet::interface]
pub trait IPyth<T> {
fn get_price_unsafe(self: @T, price_id: u256) -> Result<Price, GetPriceUnsafeError>;
fn get_ema_price_unsafe(self: @T, price_id: u256) -> Result<Price, GetPriceUnsafeError>;
fn set_data_sources(
ref self: T, sources: Array<DataSource>
) -> Result<(), GovernanceActionError>;
fn set_fee(ref self: T, single_update_fee: u256) -> Result<(), GovernanceActionError>;
fn update_price_feeds(ref self: T, data: ByteArray) -> Result<(), UpdatePriceFeedsError>;
}
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
pub enum GetPriceUnsafeError {
PriceFeedNotFound,
}
pub impl GetPriceUnsafeErrorUnwrapWithFelt252<T> of UnwrapWithFelt252<T, GetPriceUnsafeError> {
fn unwrap_with_felt252(self: Result<T, GetPriceUnsafeError>) -> T {
match self {
Result::Ok(v) => v,
Result::Err(err) => core::panic_with_felt252(err.into()),
}
}
}
impl GetPriceUnsafeErrorIntoFelt252 of Into<GetPriceUnsafeError, felt252> {
fn into(self: GetPriceUnsafeError) -> felt252 {
match self {
GetPriceUnsafeError::PriceFeedNotFound => 'price feed not found',
}
}
}
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
pub enum GovernanceActionError {
AccessDenied,
}
pub impl GovernanceActionErrorUnwrapWithFelt252<T> of UnwrapWithFelt252<T, GovernanceActionError> {
fn unwrap_with_felt252(self: Result<T, GovernanceActionError>) -> T {
match self {
Result::Ok(v) => v,
Result::Err(err) => core::panic_with_felt252(err.into()),
}
}
}
impl GovernanceActionErrorIntoFelt252 of Into<GovernanceActionError, felt252> {
fn into(self: GovernanceActionError) -> felt252 {
match self {
GovernanceActionError::AccessDenied => 'access denied',
}
}
}
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
pub enum UpdatePriceFeedsError {
Reader: super::reader::Error,
Wormhole: super::wormhole::ParseAndVerifyVmError,
InvalidUpdateData,
InvalidUpdateDataSource,
InsufficientFeeAllowance,
}
pub impl UpdatePriceFeedsErrorUnwrapWithFelt252<T> of UnwrapWithFelt252<T, UpdatePriceFeedsError> {
fn unwrap_with_felt252(self: Result<T, UpdatePriceFeedsError>) -> T {
match self {
Result::Ok(v) => v,
Result::Err(err) => core::panic_with_felt252(err.into()),
}
}
}
impl UpdatePriceFeedsErrorIntoFelt252 of Into<UpdatePriceFeedsError, felt252> {
fn into(self: UpdatePriceFeedsError) -> felt252 {
match self {
UpdatePriceFeedsError::Reader(err) => err.into(),
UpdatePriceFeedsError::Wormhole(err) => err.into(),
UpdatePriceFeedsError::InvalidUpdateData => 'invalid update data',
UpdatePriceFeedsError::InvalidUpdateDataSource => 'invalid update data source',
UpdatePriceFeedsError::InsufficientFeeAllowance => 'insufficient fee allowance',
}
}
}
#[derive(Drop, Debug, Clone, Copy, Hash, Default, Serde, starknet::Store)]
pub struct DataSource {
pub emitter_chain_id: u16,
pub emitter_address: u256,
}
#[derive(Drop, Clone, Serde, starknet::Store)]
struct PriceInfo {
pub price: i64,
pub conf: u64,
pub expo: i32,
pub publish_time: u64,
pub ema_price: i64,
pub ema_conf: u64,
}
#[derive(Drop, Clone, Serde)]
struct Price {
pub price: i64,
pub conf: u64,
pub expo: i32,
pub publish_time: u64,
}
pub use errors::{GetPriceUnsafeError, GovernanceActionError, UpdatePriceFeedsError};
pub use interface::{IPyth, IPythDispatcher, IPythDispatcherTrait, DataSource, Price};
#[starknet::contract]
mod pyth {
use pyth::reader::ReaderTrait;
use super::price_update::{
PriceInfo, PriceFeedMessage, read_and_verify_message, read_header_and_wormhole_proof,
parse_wormhole_proof
};
use pyth::reader::{Reader, ReaderImpl};
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 super::{
DataSource, UpdatePriceFeedsError, PriceInfo, GovernanceActionError, Price,
GetPriceUnsafeError
DataSource, UpdatePriceFeedsError, GovernanceActionError, Price, GetPriceUnsafeError
};
use pyth::merkle_tree::{read_and_verify_proof, MerkleVerificationError};
use pyth::hash::{Hasher, HasherImpl};
use core::fmt::{Debug, Formatter};
use pyth::util::{u64_as_i64, u32_as_i32};
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcherTrait, IERC20CamelDispatcher};
// Stands for PNAU (Pyth Network Accumulator Update)
const ACCUMULATOR_MAGIC: u32 = 0x504e4155;
// Stands for AUWV (Accumulator Update Wormhole Verficiation)
const ACCUMULATOR_WORMHOLE_MAGIC: u32 = 0x41555756;
const MAJOR_VERSION: u8 = 1;
const MINIMUM_ALLOWED_MINOR_VERSION: u8 = 0;
#[event]
#[derive(Drop, PartialEq, starknet::Event)]
pub enum Event {
@ -154,74 +37,6 @@ mod pyth {
pub conf: u64,
}
#[generate_trait]
impl ResultReaderToUpdatePriceFeeds<T> of ResultReaderToUpdatePriceFeedsTrait<T> {
fn map_err(self: Result<T, pyth::reader::Error>) -> Result<T, UpdatePriceFeedsError> {
match self {
Result::Ok(v) => Result::Ok(v),
Result::Err(err) => Result::Err(UpdatePriceFeedsError::Reader(err)),
}
}
}
#[generate_trait]
impl ResultWormholeToUpdatePriceFeeds<T> of ResultWormholeToUpdatePriceFeedsTrait<T> {
fn map_err(
self: Result<T, pyth::wormhole::ParseAndVerifyVmError>
) -> Result<T, UpdatePriceFeedsError> {
match self {
Result::Ok(v) => Result::Ok(v),
Result::Err(err) => Result::Err(UpdatePriceFeedsError::Wormhole(err)),
}
}
}
#[generate_trait]
impl ResultMerkleToUpdatePriceFeeds<T> of ResultMerkleToUpdatePriceFeedsTrait<T> {
fn map_err(self: Result<T, MerkleVerificationError>) -> Result<T, UpdatePriceFeedsError> {
match self {
Result::Ok(v) => Result::Ok(v),
Result::Err(err) => {
let err = match err {
MerkleVerificationError::Reader(err) => UpdatePriceFeedsError::Reader(err),
MerkleVerificationError::DigestMismatch => UpdatePriceFeedsError::InvalidUpdateData,
};
Result::Err(err)
},
}
}
}
#[derive(Drop)]
enum UpdateType {
WormholeMerkle
}
impl U8TryIntoUpdateType of TryInto<u8, UpdateType> {
fn try_into(self: u8) -> Option<UpdateType> {
if self == 0 {
Option::Some(UpdateType::WormholeMerkle)
} else {
Option::None
}
}
}
#[derive(Drop)]
enum MessageType {
PriceFeed
}
impl U8TryIntoMessageType of TryInto<u8, MessageType> {
fn try_into(self: u8) -> Option<MessageType> {
if self == 0 {
Option::Some(MessageType::PriceFeed)
} else {
Option::None
}
}
}
#[storage]
struct Storage {
wormhole_address: ContractAddress,
@ -264,39 +79,7 @@ mod pyth {
self.wormhole_address.write(wormhole_address);
self.fee_contract_address.write(fee_contract_address);
self.single_update_fee.write(single_update_fee);
write_data_sources(ref self, data_sources);
}
fn write_data_sources(ref self: ContractState, data_sources: Array<DataSource>) {
let num_old = self.num_data_sources.read();
let mut i = 0;
while i < num_old {
let old_source = self.data_sources.read(i);
self.is_valid_data_source.write(old_source, false);
self.data_sources.write(i, Default::default());
i += 1;
};
self.num_data_sources.write(data_sources.len());
i = 0;
while i < data_sources.len() {
let source = data_sources.at(i);
self.is_valid_data_source.write(*source, true);
self.data_sources.write(i, *source);
i += 1;
};
}
#[derive(Drop)]
struct PriceFeedMessage {
price_id: u256,
price: i64,
conf: u64,
expo: i32,
publish_time: u64,
prev_publish_time: u64,
ema_price: i64,
ema_conf: u64,
self.write_data_sources(data_sources);
}
#[abi(embed_v0)]
@ -333,85 +116,37 @@ mod pyth {
Result::Ok(price)
}
fn set_data_sources(
ref self: ContractState, sources: Array<DataSource>
) -> Result<(), GovernanceActionError> {
fn set_data_sources(ref self: ContractState, sources: Array<DataSource>) {
if self.owner.read() != get_caller_address() {
return Result::Err(GovernanceActionError::AccessDenied);
panic_with_felt252(GovernanceActionError::AccessDenied.into());
}
write_data_sources(ref self, sources);
Result::Ok(())
self.write_data_sources(sources);
}
fn set_fee(
ref self: ContractState, single_update_fee: u256
) -> Result<(), GovernanceActionError> {
fn set_fee(ref self: ContractState, single_update_fee: u256) {
if self.owner.read() != get_caller_address() {
return Result::Err(GovernanceActionError::AccessDenied);
panic_with_felt252(GovernanceActionError::AccessDenied.into());
}
self.single_update_fee.write(single_update_fee);
Result::Ok(())
}
fn update_price_feeds(
ref self: ContractState, data: ByteArray
) -> Result<(), UpdatePriceFeedsError> {
fn update_price_feeds(ref self: ContractState, data: ByteArray) {
let mut reader = ReaderImpl::new(data);
let x = reader.read_u32();
if x != ACCUMULATOR_MAGIC {
return Result::Err(UpdatePriceFeedsError::InvalidUpdateData);
}
if reader.read_u8() != MAJOR_VERSION {
return Result::Err(UpdatePriceFeedsError::InvalidUpdateData);
}
if reader.read_u8() < MINIMUM_ALLOWED_MINOR_VERSION {
return Result::Err(UpdatePriceFeedsError::InvalidUpdateData);
}
let trailing_header_size = reader.read_u8();
reader.skip(trailing_header_size);
let update_type: Option<UpdateType> = reader.read_u8().try_into();
match update_type {
Option::Some(v) => match v {
UpdateType::WormholeMerkle => {}
},
Option::None => { return Result::Err(UpdatePriceFeedsError::InvalidUpdateData); }
};
let wh_proof_size = reader.read_u16();
let wh_proof = reader.read_byte_array(wh_proof_size.into());
let wormhole_proof = read_header_and_wormhole_proof(ref reader);
let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() };
let vm = wormhole.parse_and_verify_vm(wh_proof).map_err()?;
let vm = wormhole.parse_and_verify_vm(wormhole_proof);
let source = DataSource {
emitter_chain_id: vm.emitter_chain_id, emitter_address: vm.emitter_address
};
if !self.is_valid_data_source.read(source) {
return Result::Err(UpdatePriceFeedsError::InvalidUpdateDataSource);
panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateDataSource.into());
}
let mut payload_reader = ReaderImpl::new(vm.payload);
let x = payload_reader.read_u32();
if x != ACCUMULATOR_WORMHOLE_MAGIC {
return Result::Err(UpdatePriceFeedsError::InvalidUpdateData);
}
let update_type: Option<UpdateType> = payload_reader.read_u8().try_into();
match update_type {
Option::Some(v) => match v {
UpdateType::WormholeMerkle => {}
},
Option::None => { return Result::Err(UpdatePriceFeedsError::InvalidUpdateData); }
};
let _slot = payload_reader.read_u64();
let _ring_size = payload_reader.read_u32();
let root_digest = payload_reader.read_u160();
let root_digest = parse_wormhole_proof(vm.payload);
let num_updates = reader.read_u8();
let total_fee = get_total_fee(ref self, num_updates);
let total_fee = self.get_total_fee(num_updates);
let fee_contract = IERC20CamelDispatcher {
contract_address: self.fee_contract_address.read()
};
@ -419,90 +154,72 @@ mod pyth {
let caller = execution_info.caller_address;
let contract = execution_info.contract_address;
if fee_contract.allowance(caller, contract) < total_fee {
return Result::Err(UpdatePriceFeedsError::InsufficientFeeAllowance);
panic_with_felt252(UpdatePriceFeedsError::InsufficientFeeAllowance.into());
}
if !fee_contract.transferFrom(caller, contract, total_fee) {
return Result::Err(UpdatePriceFeedsError::InsufficientFeeAllowance);
panic_with_felt252(UpdatePriceFeedsError::InsufficientFeeAllowance.into());
}
let mut i = 0;
let mut result = Result::Ok(());
while i < num_updates {
let r = read_and_verify_message(ref reader, root_digest);
match r {
Result::Ok(message) => { update_latest_price_if_necessary(ref self, message); },
Result::Err(err) => {
result = Result::Err(err);
break;
}
}
let message = read_and_verify_message(ref reader, root_digest);
self.update_latest_price_if_necessary(message);
i += 1;
};
result?;
if reader.len() != 0 {
return Result::Err(UpdatePriceFeedsError::InvalidUpdateData);
panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into());
}
Result::Ok(())
}
}
fn read_and_verify_message(
ref reader: Reader, root_digest: u256
) -> Result<PriceFeedMessage, UpdatePriceFeedsError> {
let message_size = reader.read_u16();
let message = reader.read_byte_array(message_size.into());
read_and_verify_proof(root_digest, @message, ref reader).map_err()?;
let mut message_reader = ReaderImpl::new(message);
let message_type: Option<MessageType> = message_reader.read_u8().try_into();
match message_type {
Option::Some(v) => match v {
MessageType::PriceFeed => {}
},
Option::None => { return Result::Err(UpdatePriceFeedsError::InvalidUpdateData); }
};
let price_id = message_reader.read_u256();
let price = u64_as_i64(message_reader.read_u64());
let conf = message_reader.read_u64();
let expo = u32_as_i32(message_reader.read_u32());
let publish_time = message_reader.read_u64();
let prev_publish_time = message_reader.read_u64();
let ema_price = u64_as_i64(message_reader.read_u64());
let ema_conf = message_reader.read_u64();
let message = PriceFeedMessage {
price_id, price, conf, expo, publish_time, prev_publish_time, ema_price, ema_conf,
};
Result::Ok(message)
}
fn update_latest_price_if_necessary(ref self: ContractState, message: PriceFeedMessage) {
let latest_publish_time = self.latest_price_info.read(message.price_id).publish_time;
if message.publish_time > latest_publish_time {
let info = PriceInfo {
price: message.price,
conf: message.conf,
expo: message.expo,
publish_time: message.publish_time,
ema_price: message.ema_price,
ema_conf: message.ema_conf,
#[generate_trait]
impl PrivateImpl of PrivateTrait {
fn write_data_sources(ref self: ContractState, data_sources: Array<DataSource>) {
let num_old = self.num_data_sources.read();
let mut i = 0;
while i < num_old {
let old_source = self.data_sources.read(i);
self.is_valid_data_source.write(old_source, false);
self.data_sources.write(i, Default::default());
i += 1;
};
self.latest_price_info.write(message.price_id, info);
let event = PriceFeedUpdateEvent {
price_id: message.price_id,
publish_time: message.publish_time,
price: message.price,
conf: message.conf,
self.num_data_sources.write(data_sources.len());
i = 0;
while i < data_sources.len() {
let source = data_sources.at(i);
self.is_valid_data_source.write(*source, true);
self.data_sources.write(i, *source);
i += 1;
};
self.emit(event);
}
}
fn get_total_fee(ref self: ContractState, num_updates: u8) -> u256 {
self.single_update_fee.read() * num_updates.into()
fn update_latest_price_if_necessary(ref self: ContractState, message: PriceFeedMessage) {
let latest_publish_time = self.latest_price_info.read(message.price_id).publish_time;
if message.publish_time > latest_publish_time {
let info = PriceInfo {
price: message.price,
conf: message.conf,
expo: message.expo,
publish_time: message.publish_time,
ema_price: message.ema_price,
ema_conf: message.ema_conf,
};
self.latest_price_info.write(message.price_id, info);
let event = PriceFeedUpdateEvent {
price_id: message.price_id,
publish_time: message.publish_time,
price: message.price,
conf: message.conf,
};
self.emit(event);
}
}
fn get_total_fee(ref self: ContractState, num_updates: u8) -> u256 {
self.single_update_fee.read() * num_updates.into()
}
}
}

View File

@ -0,0 +1,46 @@
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
pub enum GetPriceUnsafeError {
PriceFeedNotFound,
}
impl GetPriceUnsafeErrorIntoFelt252 of Into<GetPriceUnsafeError, felt252> {
fn into(self: GetPriceUnsafeError) -> felt252 {
match self {
GetPriceUnsafeError::PriceFeedNotFound => 'price feed not found',
}
}
}
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
pub enum GovernanceActionError {
AccessDenied,
}
impl GovernanceActionErrorIntoFelt252 of Into<GovernanceActionError, felt252> {
fn into(self: GovernanceActionError) -> felt252 {
match self {
GovernanceActionError::AccessDenied => 'access denied',
}
}
}
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
pub enum UpdatePriceFeedsError {
Reader: pyth::reader::Error,
Wormhole: pyth::wormhole::ParseAndVerifyVmError,
InvalidUpdateData,
InvalidUpdateDataSource,
InsufficientFeeAllowance,
}
impl UpdatePriceFeedsErrorIntoFelt252 of Into<UpdatePriceFeedsError, felt252> {
fn into(self: UpdatePriceFeedsError) -> felt252 {
match self {
UpdatePriceFeedsError::Reader(err) => err.into(),
UpdatePriceFeedsError::Wormhole(err) => err.into(),
UpdatePriceFeedsError::InvalidUpdateData => 'invalid update data',
UpdatePriceFeedsError::InvalidUpdateDataSource => 'invalid update data source',
UpdatePriceFeedsError::InsufficientFeeAllowance => 'insufficient fee allowance',
}
}
}

View File

@ -0,0 +1,25 @@
use super::GetPriceUnsafeError;
use pyth::byte_array::ByteArray;
#[starknet::interface]
pub trait IPyth<T> {
fn get_price_unsafe(self: @T, price_id: u256) -> Result<Price, GetPriceUnsafeError>;
fn get_ema_price_unsafe(self: @T, price_id: u256) -> Result<Price, GetPriceUnsafeError>;
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);
}
#[derive(Drop, Debug, Clone, Copy, Hash, Default, Serde, starknet::Store)]
pub struct DataSource {
pub emitter_chain_id: u16,
pub emitter_address: u256,
}
#[derive(Drop, Clone, Serde)]
pub struct Price {
pub price: i64,
pub conf: u64,
pub expo: i32,
pub publish_time: u64,
}

View File

@ -0,0 +1,141 @@
use pyth::reader::{Reader, ReaderImpl};
use pyth::pyth::UpdatePriceFeedsError;
use core::panic_with_felt252;
use pyth::byte_array::ByteArray;
use pyth::merkle_tree::read_and_verify_proof;
use pyth::util::{u32_as_i32, u64_as_i64};
// Stands for PNAU (Pyth Network Accumulator Update)
const ACCUMULATOR_MAGIC: u32 = 0x504e4155;
// Stands for AUWV (Accumulator Update Wormhole Verficiation)
const ACCUMULATOR_WORMHOLE_MAGIC: u32 = 0x41555756;
const MAJOR_VERSION: u8 = 1;
const MINIMUM_ALLOWED_MINOR_VERSION: u8 = 0;
#[derive(Drop, Clone, Serde, starknet::Store)]
pub struct PriceInfo {
pub price: i64,
pub conf: u64,
pub expo: i32,
pub publish_time: u64,
pub ema_price: i64,
pub ema_conf: u64,
}
#[derive(Drop)]
enum UpdateType {
WormholeMerkle
}
impl U8TryIntoUpdateType of TryInto<u8, UpdateType> {
fn try_into(self: u8) -> Option<UpdateType> {
if self == 0 {
Option::Some(UpdateType::WormholeMerkle)
} else {
Option::None
}
}
}
#[derive(Drop)]
enum MessageType {
PriceFeed
}
impl U8TryIntoMessageType of TryInto<u8, MessageType> {
fn try_into(self: u8) -> Option<MessageType> {
if self == 0 {
Option::Some(MessageType::PriceFeed)
} else {
Option::None
}
}
}
#[derive(Drop)]
pub struct PriceFeedMessage {
pub price_id: u256,
pub price: i64,
pub conf: u64,
pub expo: i32,
pub publish_time: u64,
pub prev_publish_time: u64,
pub ema_price: i64,
pub ema_conf: u64,
}
pub fn read_header_and_wormhole_proof(ref reader: Reader) -> ByteArray {
if reader.read_u32() != ACCUMULATOR_MAGIC {
panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into());
}
if reader.read_u8() != MAJOR_VERSION {
panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into());
}
if reader.read_u8() < MINIMUM_ALLOWED_MINOR_VERSION {
panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into());
}
let trailing_header_size = reader.read_u8();
reader.skip(trailing_header_size);
let update_type: UpdateType = reader
.read_u8()
.try_into()
.expect(UpdatePriceFeedsError::InvalidUpdateData.into());
match update_type {
UpdateType::WormholeMerkle => {}
}
let wormhole_proof_size = reader.read_u16();
reader.read_byte_array(wormhole_proof_size.into())
}
pub fn parse_wormhole_proof(payload: ByteArray) -> u256 {
let mut reader = ReaderImpl::new(payload);
if reader.read_u32() != ACCUMULATOR_WORMHOLE_MAGIC {
panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into());
}
let update_type: UpdateType = reader
.read_u8()
.try_into()
.expect(UpdatePriceFeedsError::InvalidUpdateData.into());
match update_type {
UpdateType::WormholeMerkle => {}
}
let _slot = reader.read_u64();
let _ring_size = reader.read_u32();
reader.read_u160()
}
pub fn read_and_verify_message(ref reader: Reader, root_digest: u256) -> PriceFeedMessage {
let message_size = reader.read_u16();
let message = reader.read_byte_array(message_size.into());
read_and_verify_proof(root_digest, @message, ref reader);
let mut message_reader = ReaderImpl::new(message);
let message_type: MessageType = message_reader
.read_u8()
.try_into()
.expect(UpdatePriceFeedsError::InvalidUpdateData.into());
match message_type {
MessageType::PriceFeed => {}
}
let price_id = message_reader.read_u256();
let price = u64_as_i64(message_reader.read_u64());
let conf = message_reader.read_u64();
let expo = u32_as_i32(message_reader.read_u32());
let publish_time = message_reader.read_u64();
let prev_publish_time = message_reader.read_u64();
let ema_price = u64_as_i64(message_reader.read_u64());
let ema_conf = message_reader.read_u64();
PriceFeedMessage {
price_id, price, conf, expo, publish_time, prev_publish_time, ema_price, ema_conf,
}
}

View File

@ -105,6 +105,15 @@ pub trait UnwrapWithFelt252<T, E> {
fn unwrap_with_felt252(self: Result<T, E>) -> T;
}
pub impl UnwrapWithFelt252Impl<T, E, +Into<E, felt252>> of UnwrapWithFelt252<T, E> {
fn unwrap_with_felt252(self: Result<T, E>) -> T {
match self {
Result::Ok(v) => v,
Result::Err(err) => core::panic_with_felt252(err.into()),
}
}
}
/// Reinterpret `u64` as `i64` as if it was a two's complement binary representation.
pub fn u64_as_i64(value: u64) -> i64 {
if value < 0x8000000000000000 {
@ -125,7 +134,7 @@ pub fn u32_as_i32(value: u32) -> i32 {
}
}
pub fn array_felt252_to_bytes31(mut input: Array<felt252>) -> Array<bytes31> {
pub fn array_try_into<T, U, +TryInto<T, U>, +Drop<T>, +Drop<U>>(mut input: Array<T>) -> Array<U> {
let mut output = array![];
loop {
match input.pop_front() {

View File

@ -1,13 +1,18 @@
use super::byte_array::ByteArray;
use core::starknet::secp256_trait::Signature;
use core::starknet::EthAddress;
use pyth::util::UnwrapWithFelt252;
mod governance;
#[starknet::interface]
pub trait IWormhole<T> {
fn submit_new_guardian_set(
ref self: T, set_index: u32, guardians: Array<felt252>
) -> Result<(), SubmitNewGuardianSetError>;
fn parse_and_verify_vm(self: @T, encoded_vm: ByteArray) -> Result<VM, ParseAndVerifyVmError>;
fn parse_and_verify_vm(self: @T, encoded_vm: ByteArray) -> VerifiedVM;
// We don't need to implement other governance actions for now.
// Instead of upgrading the Wormhole contract, we can switch to another Wormhole address
// in the Pyth contract.
fn submit_new_guardian_set(ref self: T, encoded_vm: ByteArray);
}
#[derive(Drop, Debug, Clone, Serde)]
@ -17,7 +22,7 @@ pub struct GuardianSignature {
}
#[derive(Drop, Debug, Clone, Serde)]
pub struct VM {
pub struct VerifiedVM {
pub version: u8,
pub guardian_set_index: u32,
pub signatures: Array<GuardianSignature>,
@ -28,6 +33,34 @@ pub struct VM {
pub sequence: u64,
pub consistency_level: u8,
pub payload: ByteArray,
pub hash: u256,
}
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
pub enum GovernanceError {
InvalidModule,
InvalidAction,
InvalidChainId,
TrailingData,
NotCurrentGuardianSet,
WrongChain,
WrongContract,
ActionAlreadyConsumed,
}
impl GovernanceErrorIntoFelt252 of Into<GovernanceError, felt252> {
fn into(self: GovernanceError) -> felt252 {
match self {
GovernanceError::InvalidModule => 'invalid module',
GovernanceError::InvalidAction => 'invalid action',
GovernanceError::InvalidChainId => 'invalid chain ID',
GovernanceError::TrailingData => 'trailing data',
GovernanceError::NotCurrentGuardianSet => 'not signed by current guard.set',
GovernanceError::WrongChain => 'wrong governance chain',
GovernanceError::WrongContract => 'wrong governance contract',
GovernanceError::ActionAlreadyConsumed => 'gov. action already consumed',
}
}
}
#[derive(Copy, Drop, Debug, Serde, PartialEq)]
@ -40,17 +73,6 @@ pub enum SubmitNewGuardianSetError {
AccessDenied,
}
pub impl SubmitNewGuardianSetErrorUnwrapWithFelt252<
T
> of UnwrapWithFelt252<T, SubmitNewGuardianSetError> {
fn unwrap_with_felt252(self: Result<T, SubmitNewGuardianSetError>) -> T {
match self {
Result::Ok(v) => v,
Result::Err(err) => core::panic_with_felt252(err.into()),
}
}
}
impl SubmitNewGuardianSetErrorIntoFelt252 of Into<SubmitNewGuardianSetError, felt252> {
fn into(self: SubmitNewGuardianSetError) -> felt252 {
match self {
@ -76,16 +98,6 @@ pub enum ParseAndVerifyVmError {
InvalidGuardianIndex,
}
pub impl ParseAndVerifyVmErrorUnwrapWithFelt252<T> of UnwrapWithFelt252<T, ParseAndVerifyVmError> {
fn unwrap_with_felt252(self: Result<T, ParseAndVerifyVmError>) -> T {
match self {
Result::Ok(v) => v,
Result::Err(err) => core::panic_with_felt252(err.into()),
}
}
}
impl ErrorIntoFelt252 of Into<ParseAndVerifyVmError, felt252> {
fn into(self: ParseAndVerifyVmError) -> felt252 {
match self {
@ -112,31 +124,24 @@ mod wormhole {
use core::box::BoxTrait;
use core::array::ArrayTrait;
use super::{
VM, IWormhole, GuardianSignature, quorum, ParseAndVerifyVmError, SubmitNewGuardianSetError
VerifiedVM, IWormhole, GuardianSignature, quorum, ParseAndVerifyVmError,
SubmitNewGuardianSetError, GovernanceError
};
use super::governance;
use pyth::reader::{Reader, ReaderImpl};
use pyth::byte_array::ByteArray;
use core::starknet::secp256_trait::{Signature, recover_public_key, Secp256PointTrait};
use core::starknet::secp256k1::Secp256k1Point;
use core::starknet::{
ContractAddress, get_execution_info, get_caller_address, get_block_timestamp
ContractAddress, get_execution_info, get_caller_address, get_block_timestamp, EthAddress,
};
use core::keccak::cairo_keccak;
use core::starknet::eth_signature::is_eth_signature_valid;
use core::integer::u128_byte_reverse;
use core::panic_with_felt252;
use pyth::hash::{Hasher, HasherImpl};
use pyth::util::{ONE_SHIFT_160, UNEXPECTED_OVERFLOW};
#[generate_trait]
impl ResultReaderToWormhole<T> of ResultReaderToWormholeTrait<T> {
fn map_err(self: Result<T, pyth::reader::Error>) -> Result<T, ParseAndVerifyVmError> {
match self {
Result::Ok(v) => Result::Ok(v),
Result::Err(err) => Result::Err(ParseAndVerifyVmError::Reader(err)),
}
}
}
#[derive(Drop, Debug, Clone, Serde, starknet::Store)]
struct GuardianSet {
num_guardians: usize,
@ -146,42 +151,46 @@ mod wormhole {
#[storage]
struct Storage {
owner: ContractAddress,
chain_id: u16,
governance_chain_id: u16,
governance_contract: u256,
current_guardian_set_index: u32,
consumed_governance_actions: LegacyMap<u256, bool>,
guardian_sets: LegacyMap<u32, GuardianSet>,
// (guardian_set_index, guardian_index) => guardian_address
guardian_keys: LegacyMap<(u32, u8), u256>,
guardian_keys: LegacyMap<(u32, u8), EthAddress>,
}
#[constructor]
fn constructor(
ref self: ContractState, owner: ContractAddress, initial_guardians: Array<felt252>
ref self: ContractState,
initial_guardians: Array<EthAddress>,
chain_id: u16,
governance_chain_id: u16,
governance_contract: u256,
) {
self.owner.write(owner);
self.chain_id.write(chain_id);
self.governance_chain_id.write(governance_chain_id);
self.governance_contract.write(governance_contract);
let set_index = 0;
store_guardian_set(ref self, set_index, @initial_guardians).unwrap_with_felt252();
store_guardian_set(ref self, set_index, @initial_guardians);
}
fn store_guardian_set(
ref self: ContractState, set_index: u32, guardians: @Array<felt252>
) -> Result<(), SubmitNewGuardianSetError> {
fn store_guardian_set(ref self: ContractState, set_index: u32, guardians: @Array<EthAddress>) {
if guardians.len() == 0 {
return Result::Err(SubmitNewGuardianSetError::NoGuardiansSpecified.into());
panic_with_felt252(SubmitNewGuardianSetError::NoGuardiansSpecified.into());
}
if guardians.len() >= 256 {
return Result::Err(SubmitNewGuardianSetError::TooManyGuardians.into());
panic_with_felt252(SubmitNewGuardianSetError::TooManyGuardians.into());
}
let mut i = 0;
let mut result = Result::Ok(());
while i < guardians.len() {
if *guardians.at(i) == 0 {
result = Result::Err(SubmitNewGuardianSetError::InvalidGuardianKey.into());
break;
if (*guardians.at(i)).into() == 0 {
panic_with_felt252(SubmitNewGuardianSetError::InvalidGuardianKey.into());
}
i += 1;
};
result?;
let set = GuardianSet { num_guardians: guardians.len(), expiration_time: 0 };
self.guardian_sets.write(set_index, set);
@ -195,7 +204,6 @@ mod wormhole {
i += 1;
};
self.current_guardian_set_index.write(set_index);
Result::Ok(())
}
fn expire_guardian_set(ref self: ContractState, set_index: u32, now: u64) {
@ -206,43 +214,22 @@ mod wormhole {
#[abi(embed_v0)]
impl WormholeImpl of IWormhole<ContractState> {
fn submit_new_guardian_set(
ref self: ContractState, set_index: u32, guardians: Array<felt252>
) -> Result<(), SubmitNewGuardianSetError> {
let execution_info = get_execution_info().unbox();
if self.owner.read() != execution_info.caller_address {
return Result::Err(SubmitNewGuardianSetError::AccessDenied);
}
let current_set_index = self.current_guardian_set_index.read();
if set_index != current_set_index + 1 {
return Result::Err(SubmitNewGuardianSetError::InvalidGuardianSetSequence.into());
}
store_guardian_set(ref self, set_index, @guardians)?;
expire_guardian_set(
ref self, current_set_index, execution_info.block_info.unbox().block_timestamp
);
Result::Ok(())
}
fn parse_and_verify_vm(
self: @ContractState, encoded_vm: ByteArray
) -> Result<VM, ParseAndVerifyVmError> {
let (vm, body_hash) = parse_vm(encoded_vm)?;
fn parse_and_verify_vm(self: @ContractState, encoded_vm: ByteArray) -> VerifiedVM {
let vm = parse_vm(encoded_vm);
let guardian_set = self.guardian_sets.read(vm.guardian_set_index);
if guardian_set.num_guardians == 0 {
return Result::Err(ParseAndVerifyVmError::InvalidGuardianSetIndex);
panic_with_felt252(ParseAndVerifyVmError::InvalidGuardianSetIndex.into());
}
if vm.guardian_set_index != self.current_guardian_set_index.read()
&& guardian_set.expiration_time < get_block_timestamp() {
return Result::Err(ParseAndVerifyVmError::GuardianSetExpired);
panic_with_felt252(ParseAndVerifyVmError::GuardianSetExpired.into());
}
if vm.signatures.len() < quorum(guardian_set.num_guardians) {
return Result::Err(ParseAndVerifyVmError::NoQuorum);
panic_with_felt252(ParseAndVerifyVmError::NoQuorum.into());
}
let mut signatures_clone = vm.signatures.clone();
let mut last_index = Option::None;
let mut result = Result::Ok(());
loop {
let signature = match signatures_clone.pop_front() {
Option::Some(v) => { v },
@ -252,8 +239,7 @@ mod wormhole {
match last_index {
Option::Some(last_index) => {
if *(@signature).guardian_index <= last_index {
result = Result::Err(ParseAndVerifyVmError::InvalidSignatureOrder);
break;
panic_with_felt252(ParseAndVerifyVmError::InvalidSignatureOrder.into());
}
},
Option::None => {},
@ -261,43 +247,58 @@ mod wormhole {
last_index = Option::Some(*(@signature).guardian_index);
if signature.guardian_index.into() >= guardian_set.num_guardians {
result = Result::Err(ParseAndVerifyVmError::InvalidGuardianIndex);
break;
panic_with_felt252(ParseAndVerifyVmError::InvalidGuardianIndex.into());
}
let guardian_key = self
.guardian_keys
.read((vm.guardian_set_index, signature.guardian_index));
.read((vm.guardian_set_index, signature.guardian_index))
.try_into()
.expect(UNEXPECTED_OVERFLOW);
let r = verify_signature(body_hash, signature.signature, guardian_key);
if r.is_err() {
result = r;
break;
}
is_eth_signature_valid(vm.hash, signature.signature, guardian_key)
.unwrap_with_felt252();
};
result?;
vm
}
Result::Ok(vm)
fn submit_new_guardian_set(ref self: ContractState, encoded_vm: ByteArray) {
let vm = self.parse_and_verify_vm(encoded_vm);
self.verify_governance_vm(@vm);
let mut reader = ReaderImpl::new(vm.payload);
let header = governance::parse_header(ref reader);
if header.action != governance::Action::GuardianSetUpgrade {
panic_with_felt252(GovernanceError::InvalidAction.into());
}
if header.chain_id != 0 && header.chain_id != self.chain_id.read() {
panic_with_felt252(GovernanceError::InvalidChainId.into());
}
let new_set = governance::parse_new_guardian_set(ref reader);
let current_set_index = self.current_guardian_set_index.read();
if new_set.set_index != current_set_index + 1 {
panic_with_felt252(SubmitNewGuardianSetError::InvalidGuardianSetSequence.into());
}
store_guardian_set(ref self, new_set.set_index, @new_set.keys);
expire_guardian_set(ref self, current_set_index, get_block_timestamp());
self.consumed_governance_actions.write(vm.hash, true);
}
}
fn parse_signature(ref reader: Reader) -> Result<GuardianSignature, ParseAndVerifyVmError> {
fn parse_signature(ref reader: Reader) -> GuardianSignature {
let guardian_index = reader.read_u8();
let r = reader.read_u256();
let s = reader.read_u256();
let recovery_id = reader.read_u8();
let y_parity = (recovery_id % 2) > 0;
let signature = GuardianSignature {
guardian_index, signature: Signature { r, s, y_parity }
};
Result::Ok(signature)
GuardianSignature { guardian_index, signature: Signature { r, s, y_parity } }
}
fn parse_vm(encoded_vm: ByteArray) -> Result<(VM, u256), ParseAndVerifyVmError> {
fn parse_vm(encoded_vm: ByteArray) -> VerifiedVM {
let mut reader = ReaderImpl::new(encoded_vm);
let version = reader.read_u8();
if version != 1 {
return Result::Err(ParseAndVerifyVmError::VmVersionIncompatible);
panic_with_felt252(ParseAndVerifyVmError::VmVersionIncompatible.into());
}
let guardian_set_index = reader.read_u32();
@ -305,22 +306,14 @@ mod wormhole {
let mut i = 0;
let mut signatures = array![];
let mut result = Result::Ok(());
while i < sig_count {
match parse_signature(ref reader) {
Result::Ok(signature) => { signatures.append(signature); },
Result::Err(err) => {
result = Result::Err(err);
break;
},
}
signatures.append(parse_signature(ref reader));
i += 1;
};
result?;
let mut reader_for_hash = reader.clone();
let mut hasher = HasherImpl::new();
hasher.push_reader(ref reader_for_hash).map_err()?;
hasher.push_reader(ref reader_for_hash);
let body_hash1 = hasher.finalize();
let mut hasher2 = HasherImpl::new();
hasher2.push_u256(body_hash1);
@ -335,7 +328,7 @@ mod wormhole {
let payload_len = reader.len();
let payload = reader.read_byte_array(payload_len);
let vm = VM {
VerifiedVM {
version,
guardian_set_index,
signatures,
@ -346,33 +339,25 @@ mod wormhole {
sequence,
consistency_level,
payload,
};
Result::Ok((vm, body_hash2))
}
fn verify_signature(
body_hash: u256, signature: Signature, guardian_key: u256,
) -> Result<(), ParseAndVerifyVmError> {
let point: Secp256k1Point = recover_public_key(body_hash, signature)
.ok_or(ParseAndVerifyVmError::InvalidSignature)?;
let address = eth_address(point)?;
assert(guardian_key != 0, SubmitNewGuardianSetError::InvalidGuardianKey.into());
if address != guardian_key {
return Result::Err(ParseAndVerifyVmError::InvalidSignature);
hash: body_hash2,
}
Result::Ok(())
}
fn eth_address(point: Secp256k1Point) -> Result<u256, ParseAndVerifyVmError> {
let (x, y) = match point.get_coordinates() {
Result::Ok(v) => { v },
Result::Err(_) => { return Result::Err(ParseAndVerifyVmError::InvalidSignature); },
};
let mut hasher = HasherImpl::new();
hasher.push_u256(x);
hasher.push_u256(y);
let address = hasher.finalize() % ONE_SHIFT_160;
Result::Ok(address)
#[generate_trait]
impl PrivateImpl of PrivateImplTrait {
fn verify_governance_vm(self: @ContractState, vm: @VerifiedVM) {
if self.current_guardian_set_index.read() != *vm.guardian_set_index {
panic_with_felt252(GovernanceError::NotCurrentGuardianSet.into());
}
if self.governance_chain_id.read() != *vm.emitter_chain_id {
panic_with_felt252(GovernanceError::WrongChain.into());
}
if self.governance_contract.read() != *vm.emitter_address {
panic_with_felt252(GovernanceError::WrongContract.into());
}
if self.consumed_governance_actions.read(*vm.hash) {
panic_with_felt252(GovernanceError::ActionAlreadyConsumed.into());
}
}
}
}

View File

@ -0,0 +1,70 @@
use pyth::reader::{Reader, ReaderImpl};
use pyth::util::UNEXPECTED_OVERFLOW;
use super::GovernanceError;
use core::panic_with_felt252;
use core::starknet::EthAddress;
// "Core" (left padded)
const MODULE: u256 = 0x00000000000000000000000000000000000000000000000000000000436f7265;
#[derive(Drop, Debug, Copy, PartialEq)]
pub enum Action {
ContractUpgrade,
GuardianSetUpgrade,
SetMessageFee,
TransferFees,
RecoverChainId,
}
impl U8TryIntoAction of TryInto<u8, Action> {
fn try_into(self: u8) -> Option<Action> {
let value = match self {
0 => { return Option::None; },
1 => Action::ContractUpgrade,
2 => Action::GuardianSetUpgrade,
3 => Action::SetMessageFee,
4 => Action::TransferFees,
5 => Action::RecoverChainId,
_ => { return Option::None; },
};
Option::Some(value)
}
}
#[derive(Drop, Debug, Clone)]
pub struct Header {
pub module: u256,
pub action: Action,
pub chain_id: u16,
}
#[derive(Drop, Debug, Clone)]
pub struct NewGuardianSet {
pub set_index: u32,
pub keys: Array<EthAddress>,
}
pub fn parse_header(ref reader: Reader) -> Header {
let module = reader.read_u256();
if module != MODULE {
panic_with_felt252(GovernanceError::InvalidModule.into());
}
let action = reader.read_u8().try_into().expect(GovernanceError::InvalidAction.into());
let chain_id = reader.read_u16();
Header { module, action, chain_id }
}
pub fn parse_new_guardian_set(ref reader: Reader) -> NewGuardianSet {
let set_index = reader.read_u32();
let num_guardians = reader.read_u8();
let mut i = 0;
let mut keys = array![];
while i < num_guardians {
let key = reader.read_u160();
keys.append(key.try_into().expect(UNEXPECTED_OVERFLOW));
i += 1;
};
assert(reader.len() == 0, GovernanceError::TrailingData.into());
NewGuardianSet { set_index, keys }
}

View File

@ -6,7 +6,7 @@ use pyth::pyth::{
IPythDispatcher, IPythDispatcherTrait, DataSource, Event as PythEvent, PriceFeedUpdateEvent
};
use pyth::byte_array::{ByteArray, ByteArrayImpl};
use pyth::util::{array_felt252_to_bytes31, UnwrapWithFelt252};
use pyth::util::{array_try_into, UnwrapWithFelt252};
use core::starknet::ContractAddress;
use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
@ -33,7 +33,7 @@ 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(owner);
let wormhole = super::wormhole::deploy_and_init();
let fee_contract = deploy_fee_contract(user);
let pyth = deploy(
owner,
@ -55,7 +55,7 @@ fn update_price_feeds_works() {
let mut spy = spy_events(SpyOn::One(pyth.contract_address));
start_prank(CheatTarget::One(pyth.contract_address), user.try_into().unwrap());
pyth.update_price_feeds(good_update1()).unwrap_with_felt252();
pyth.update_price_feeds(good_update1());
stop_prank(CheatTarget::One(pyth.contract_address));
spy.fetch_events();
@ -170,5 +170,5 @@ fn good_update1() -> ByteArray {
226866843267230707879834616967256711063296411939069440476882347301771901839,
95752383404870925303422787,
];
ByteArrayImpl::new(array_felt252_to_bytes31(bytes), 11)
ByteArrayImpl::new(array_try_into(bytes), 11)
}

View File

@ -2,16 +2,15 @@ use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTar
use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait, ParseAndVerifyVmError};
use pyth::reader::ReaderImpl;
use pyth::byte_array::{ByteArray, ByteArrayImpl};
use pyth::util::{UnwrapWithFelt252, array_felt252_to_bytes31};
use core::starknet::ContractAddress;
use pyth::util::{UnwrapWithFelt252, array_try_into};
use core::starknet::{ContractAddress, EthAddress};
use core::panic_with_felt252;
#[test]
fn test_parse_and_verify_vm_works() {
let owner = 'owner'.try_into().unwrap();
let dispatcher = deploy_and_init(owner);
let dispatcher = deploy_and_init();
let vm = dispatcher.parse_and_verify_vm(good_vm1()).unwrap();
let vm = dispatcher.parse_and_verify_vm(good_vm1());
assert!(vm.version == 1);
assert!(vm.guardian_set_index == 3);
assert!(vm.signatures.len() == 13);
@ -36,68 +35,144 @@ fn test_parse_and_verify_vm_works() {
#[test]
#[fuzzer(runs: 100, seed: 0)]
#[should_panic(expected: ('any_expected',))]
#[should_panic]
fn test_parse_and_verify_vm_rejects_corrupted_vm(pos: usize, random1: usize, random2: usize) {
let owner = 'owner'.try_into().unwrap();
let dispatcher = deploy_and_init(owner);
let dispatcher = deploy_and_init();
let r = dispatcher.parse_and_verify_vm(corrupted_vm(pos, random1, random2));
match r {
Result::Ok(v) => { println!("no error, output: {:?}", v); },
Result::Err(err) => {
if err == ParseAndVerifyVmError::InvalidSignature
|| err == ParseAndVerifyVmError::InvalidGuardianIndex
|| err == ParseAndVerifyVmError::InvalidGuardianSetIndex
|| err == ParseAndVerifyVmError::VmVersionIncompatible
|| err == ParseAndVerifyVmError::Reader(pyth::reader::Error::UnexpectedEndOfInput) {
panic_with_felt252('any_expected');
} else {
panic_with_felt252(err.into());
}
},
}
let input = corrupted_vm(good_vm1(), pos, random1, random2);
let vm = dispatcher.parse_and_verify_vm(input);
println!("no error, output: {:?}", vm);
}
#[test]
#[should_panic(expected: ('access denied',))]
fn test_submit_guardian_set_rejects_wrong_owner() {
let owner = 'owner'.try_into().unwrap();
let dispatcher = deploy(owner, guardian_set1());
start_prank(CheatTarget::One(dispatcher.contract_address), 'baddy'.try_into().unwrap());
dispatcher.submit_new_guardian_set(1, guardian_set1()).unwrap_with_felt252();
#[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
);
dispatcher
.submit_new_guardian_set(
ByteArrayImpl::new(
array_try_into(
array![
1766847064779993780836504669527478016540696065743262550388106844126893447,
69151420679589009790918040295649622139220704596068812939049771907133879935,
104495383778385781169925874245014135708035627262536525919103632893328490496,
6044629098073145873860096,
1131377253,
210626190275159008588167432483938910866205453014421218013934467097,
]
),
28
)
);
}
#[test]
#[should_panic(expected: ('invalid guardian set index',))]
fn test_submit_guardian_set_rejects_wrong_index_in_signer() {
let dispatcher = deploy(guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT);
dispatcher.submit_new_guardian_set(governance_upgrade_vm1());
dispatcher.submit_new_guardian_set(governance_upgrade_vm3());
}
#[test]
#[should_panic(expected: ('invalid guardian set sequence',))]
fn test_submit_guardian_set_rejects_wrong_index() {
let owner = 'owner'.try_into().unwrap();
let dispatcher = deploy(owner, guardian_set1());
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
);
start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
dispatcher.submit_new_guardian_set(1, guardian_set1()).unwrap_with_felt252();
dispatcher.submit_new_guardian_set(3, guardian_set3()).unwrap_with_felt252();
dispatcher
.submit_new_guardian_set(
ByteArrayImpl::new(
array_try_into(
array![
1766847064779992086486657352557640859156186269544082638392748527776826036,
194277059768327503957595402526511955581212349039595919400671200491321209820,
434496814532575177274556800095069791478767471500230428914335519265903345664,
4835703278458516699153920,
1131377253,
210626190275159756877005745906233031152839803751327281851396470809,
]
),
28
)
);
}
#[test]
#[should_panic(expected: ('no guardians specified',))]
fn test_deploy_rejects_empty() {
let owner = 'owner'.try_into().unwrap();
deploy(owner, array![]);
deploy(array![], CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT);
}
#[test]
#[should_panic(expected: ('no guardians specified',))]
fn test_submit_guardian_set_rejects_empty() {
let owner = 'owner'.try_into().unwrap();
let dispatcher = deploy(owner, guardian_set1());
let dispatcher = deploy(
array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]),
CHAIN_ID,
GOVERNANCE_CHAIN_ID,
GOVERNANCE_CONTRACT
);
start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
dispatcher.submit_new_guardian_set(1, array![]).unwrap_with_felt252();
dispatcher
.submit_new_guardian_set(
ByteArrayImpl::new(
array_try_into(
array![
1766847064779991325832211120621568385196129637393266719212748612567366167,
101913621687013458268034923649142123047718029322567283658602535951581008800,
334673952516423004763530105323893104621683276518933872430791864882642812928,
4835703278458516699153920,
1131377253,
144116287587483904,
]
),
8
)
);
}
fn deploy(owner: ContractAddress, guardians: Array<felt252>) -> IWormholeDispatcher {
#[test]
#[fuzzer(runs: 100, seed: 0)]
#[should_panic]
fn test_submit_guardian_set_rejects_corrupted(pos: usize, random1: usize, random2: usize) {
let dispatcher = deploy(guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT);
let vm = corrupted_vm(governance_upgrade_vm1(), pos, random1, random2);
dispatcher.submit_new_guardian_set(vm);
}
#[test]
#[should_panic(expected: ('wrong governance chain',))]
fn test_submit_guardian_set_rejects_non_governance(pos: usize, random1: usize, random2: usize) {
let dispatcher = deploy(guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT);
dispatcher.submit_new_guardian_set(governance_upgrade_vm1());
dispatcher.submit_new_guardian_set(governance_upgrade_vm2());
dispatcher.submit_new_guardian_set(governance_upgrade_vm3());
dispatcher.submit_new_guardian_set(good_vm1());
}
fn deploy(
guardians: Array<EthAddress>,
chain_id: u16,
governance_chain_id: u16,
governance_contract: u256,
) -> IWormholeDispatcher {
let mut args = array![];
(owner, guardians).serialize(ref args);
(guardians, chain_id, governance_chain_id, governance_contract).serialize(ref args);
let contract = declare("wormhole");
let contract_address = match contract.deploy(@args) {
Result::Ok(v) => { v },
@ -109,22 +184,20 @@ fn deploy(owner: ContractAddress, guardians: Array<felt252>) -> IWormholeDispatc
IWormholeDispatcher { contract_address }
}
pub fn deploy_and_init(owner: ContractAddress) -> IWormholeDispatcher {
let dispatcher = deploy(owner, guardian_set1());
pub fn deploy_and_init() -> IWormholeDispatcher {
let dispatcher = deploy(guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT);
start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
dispatcher.submit_new_guardian_set(1, guardian_set1()).unwrap_with_felt252();
dispatcher.submit_new_guardian_set(2, guardian_set2()).unwrap_with_felt252();
dispatcher.submit_new_guardian_set(3, guardian_set3()).unwrap_with_felt252();
stop_prank(CheatTarget::One(dispatcher.contract_address));
dispatcher.submit_new_guardian_set(governance_upgrade_vm1());
dispatcher.submit_new_guardian_set(governance_upgrade_vm2());
dispatcher.submit_new_guardian_set(governance_upgrade_vm3());
dispatcher.submit_new_guardian_set(governance_upgrade_vm4());
dispatcher
}
fn corrupted_vm(pos: usize, random1: usize, random2: usize) -> ByteArray {
fn corrupted_vm(mut real_data: ByteArray, pos: usize, random1: usize, random2: usize) -> ByteArray {
let mut new_data = array![];
let mut real_data = good_vm1();
// Make sure we select a position not on the last item because
// we didn't implement corrupting an incomplete bytes31.
let pos = pos % (real_data.len() - 31);
@ -183,76 +256,8 @@ fn corrupted_byte(value: u8, random: usize) -> u8 {
(v % 256).try_into().unwrap()
}
// Below are actual guardian keys from
// https://github.com/wormhole-foundation/wormhole-networks/tree/master/mainnetv2/guardianset
fn guardian_set1() -> Array<felt252> {
array![
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157,
0x114De8460193bdf3A2fCf81f86a09765F4762fD1,
0x107A0086b32d7A0977926A205131d8731D39cbEB,
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2,
0x11b39756C042441BE6D8650b69b54EbE715E2343,
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd,
0xeB5F7389Fa26941519f0863349C223b73a6DDEE7,
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0,
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e,
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14,
0xf93124b7c738843CBB89E864c862c38cddCccF95,
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890,
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811,
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3,
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf,
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8,
0x5E1487F35515d02A92753504a8D75471b9f49EdB,
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d,
]
}
fn guardian_set2() -> Array<felt252> {
array![
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157,
0x114De8460193bdf3A2fCf81f86a09765F4762fD1,
0x107A0086b32d7A0977926A205131d8731D39cbEB,
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2,
0x11b39756C042441BE6D8650b69b54EbE715E2343,
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd,
0x66B9590e1c41e0B226937bf9217D1d67Fd4E91F5,
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0,
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e,
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14,
0xf93124b7c738843CBB89E864c862c38cddCccF95,
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890,
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811,
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3,
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf,
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8,
0x5E1487F35515d02A92753504a8D75471b9f49EdB,
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d,
]
}
fn guardian_set3() -> Array<felt252> {
array![
0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,
0xfF6CB952589BDE862c25Ef4392132fb9D4A42157,
0x114De8460193bdf3A2fCf81f86a09765F4762fD1,
0x107A0086b32d7A0977926A205131d8731D39cbEB,
0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2,
0x11b39756C042441BE6D8650b69b54EbE715E2343,
0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd,
0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20,
0x74a3bf913953D695260D88BC1aA25A4eeE363ef0,
0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e,
0xAF45Ced136b9D9e24903464AE889F5C8a723FC14,
0xf93124b7c738843CBB89E864c862c38cddCccF95,
0xD2CC37A4dc036a8D232b48f62cDD4731412f4890,
0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811,
0x71AA1BE1D36CaFE3867910F99C09e347899C19C3,
0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf,
0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8,
0x5E1487F35515d02A92753504a8D75471b9f49EdB,
0x6FbEBc898F403E4773E95feB15E80C9A99c8348d,
]
fn guardian_set0() -> Array<EthAddress> {
array_try_into(array![0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5])
}
// A random VAA pulled from Hermes.
@ -290,5 +295,185 @@ fn good_vm1() -> ByteArray {
52685537088250779930155363779405986390839624071318818148325576008719597568,
14615204155786886573933667335033405822686404253588533,
];
ByteArrayImpl::new(array_felt252_to_bytes31(bytes), 22)
ByteArrayImpl::new(array_try_into(bytes), 22)
}
const CHAIN_ID: u16 = 1;
const GOVERNANCE_CHAIN_ID: u16 = 1;
const GOVERNANCE_CONTRACT: u256 = 4;
// Below are actual guardian set upgrade VAAS from
// https://github.com/pyth-network/pyth-crosschain/blob/main/contract_manager/src/contracts/wormhole.ts#L32-L37
fn governance_upgrade_vm1() -> ByteArray {
let bytes = array![
1766847064779994277746302277072294871108550301449637470263976489521154979,
374953657095152923031303770743522269007103499920836805761143506434463979495,
373725794026553362537846905304981854320892126869150736450761801254169477120,
4835703278458516786446336,
1131377253,
3533694129556775410652111826415980944262631656421498398215501759245151417,
145493015216602589471695207668173527044214450021182755196032581352392984224,
267497573836069714380350521200881787609530659298168186016481773490244091266,
443348533394886521835330696538264729103669807313401311199245411889706258110,
200303433165499832354845293203843028338419687800279845786613090211434473108,
37282816539161742972709083946551920068062204748477644719930149699874385462,
111200938271608595261384934914291476246753101189480743698823215749338358345,
5785682963869019134199015821749288033381872318410562688180948003975909269,
372447340016996751453958019806457886428852701283870538393820846119845147788,
33251468085387571623103303511315696691298281336333243761063342581452341650,
323161992096034641767541451811925056802673576212351940217752194462561980347,
55852237138651071644815135002358067220635692701051811455610533875912981641,
190413173566657072516608762222993749133,
];
ByteArrayImpl::new(array_try_into(bytes), 16)
}
fn governance_upgrade_vm2() -> ByteArray {
let bytes = array![
1766847065210651126944505525521222069599854288126726949998063840465138797,
39054013088470866893599813322035661048501117089555726788687392536164861911,
186918267413056900890218521593203545230034250983266377769400670103688217224,
54214750922097681971590495378823998039261772575502204791108963815396679538,
248994008232667872698758486001506749115615219491023036208140426934463230397,
224235483823871042187452194579994340291351644933258737665365374081962645784,
129444929990547403397151941456764812818993218972657847255833740800106200260,
318548597134783137700627869311159664823693239605331630210202210299165477657,
308852933010951129895011963941866000261904600807292935694851016610643657184,
57272874228621364335170723193404742446392708606247574725663969156507973216,
268057363923565984687253533797066429881117576606682526627284795527707196557,
421894189151847402000239734668088381785344768284464330085711322870200424121,
387584417395337067059819722404321580961380603778956902593594676080614899975,
443523131755342451570503958659975367050637907361274652611595274647186073286,
375107813087591446268414166006799680982485924290770541964399283524664437852,
269085314426821465871247165234113878276592898426373369094610290261431112145,
394348694527460459816454348661249546781513091938003106009521096332701847735,
125670844183465056159554034633959680574572737212268749779705635378223489518,
35053869475614771227400345921974210525173525784259894123687028214330135561,
57544237843860512274491447149877290860624174166427313971286819807773907946,
110681468147560408039747352809294082929396639199393789980441736520638055418,
45709869872872997180086402576677640909777820941436708911954532772405320395,
339910511168418517917975736269171135752028257685502872671902330279073260362,
76482764517489607955778008190826845581092838692650194719207882266659669490,
443663869577220861680293443959666949893574779475752540587040489501289361777,
231057874919577223790659840464529054850239319545221055959374917590157019925,
309066533217885002074480704480667619809952056265738927105682076502747220549,
212379788814604791028007106821871908074818251907335322546331543385945316762,
165408661499085325620077702639227003047567884011538988728381864749733773312,
29852013947978990147012099107546124222651092940097518043136,
5874446556610227706402640854088357165514903,
314635470832203526600706472223155382046271943513513368538979543914002758100,
289993023590817330918274026889451152915026890048318491140264484864242055689,
211265316833000774821515110003986084297271807500310630074520699505436206838,
314620948986744608212517578488307826224331071350776523303159889004405167502,
242768143829057016675085776170635413106817756232919004913342240722183648628,
289318220340670045883106021427202666948428587921558828582664470923483208386,
254304247593881109676724582609273741670949040469906895867342151706444640548,
324707984060675446628128892371664948354047882542253609514703956739624414429,
125786084546320950738753348592393927755418642173185609412108154831520915923,
192033422676298173731756291271054199566981168481817292625435767748408605264,
70237018464728620254434305961956673950089621204502627373468857093940647376,
75218391584551901010047495874303520775865073092730040058902770251005073864,
13453,
];
ByteArrayImpl::new(array_try_into(bytes), 2)
}
fn governance_upgrade_vm3() -> ByteArray {
let bytes = array![
1766847065622031860560134801367788401015571316785630090767859240961980367,
408239335069601434456324970231880063141100099721451058487412176729277688481,
239499689753305520381601038928513796227317320911002802551444547638809838552,
377515301744513788082523380503265415136454305699441419871183462725292421897,
293792427782284265052215516935690252661713253004854348809536189867737815900,
307592266914902727916633292174670243188255704510048236277225897328900269063,
127373290139474278928992577974696343462455769904046193125018730285162391253,
391788800785481654990215164673817619378887263909639120513493454202816019863,
410413307118599096084169722886408584518140871169074821252461819158667354254,
18837648490111595970198935552266546643395427923804444528866768515056419823,
29964034682984173558839379357821569529808274426015494950430380078317881569,
86017868501532670528023530422115758730056738654625156800662778409971102579,
316587967137295297243489759859408971025377360462781809717927347025414193161,
412080542369789462767669836400697110505430973769191785499739175360336337147,
342817079347905714229318925597762381802367663565411998187223317628701911440,
323381353160339090772037140072061985169258958022395380273676898316834639836,
199931590715070935127318740956564588449721873695471932311700469202637695100,
53310522180389647586576928116330850824055549848985438538201222342553700451,
387322343922164253479438966163491855981414317104760621828688810466847848718,
81609701542274539489711635515209037026645626576756528749469616228397567798,
182108205861564989333892774796475580877981373947799860454217397980367659628,
21549663410658134468902761710868642321546772465973442277960059676129502668,
189434039785735939400321781125039794740638779195156759980704929066694157130,
52255833533187953003213955242027099690232530588872309460610106220279805641,
197105018621162723372171195601447549272902142615124680111298974553437412361,
243585516151555343004264928593678764289083751554802049062044286334698216184,
98577806073901898829375415748245478967425496216912736575886605480181121443,
92916551389967933235240931764170084503123511470557201449603712010638670912,
279247190794260779926452059689914005511524938154821508635388069101252378624,
27765181507524306000048567556593270127801507143251178553344,
5874446556610227706402640926145951203442839,
314635470832203526600706472223155382046271943513513368538979543914002758100,
289993023590817330918274026889451152915026890048318491140264484864242055689,
211265316833000774821515110003986084297271807500310630074520699505436206838,
314620948986744608212517578488307826224331071350776523303159889004405167502,
242768143829057016675085658054156069029173843566452718977789980910319968372,
289318220340670045883106021427202666948428587921558828582664470923483208386,
254304247593881109676724582609273741670949040469906895867342151706444640548,
324707984060675446628128892371664948354047882542253609514703956739624414429,
125786084546320950738753348592393927755418642173185609412108154831520915923,
192033422676298173731756291271054199566981168481817292625435767748408605264,
70237018464728620254434305961956673950089621204502627373468857093940647376,
75218391584551901010047495874303520775865073092730040058902770251005073864,
13453,
];
ByteArrayImpl::new(array_try_into(bytes), 2)
}
fn governance_upgrade_vm4() -> ByteArray {
let bytes = array![
1766847066033426987337757245669159273063358729535478806850006662056807068,
191023158244075433218055661747029015323596061316379687901032561397223546211,
30156550775609732785124128084945604136341937408029517653427049258063209215,
301841618969457377999846355946508544313297803407604349411042057045510372286,
399879387152070823070522891203531321261797829310211644637928969034931151834,
1184971666775858810527395126763859219514013163556756790208661779020321698,
427827873217506136303198988655697899138087317492051993053159867826892618987,
55439109913191967501571602277685262841453050617358377329061538069328212552,
34944602254693785869427132065664922748183924456022812505745784482260734500,
50091615215549712387991200985741575718080363004681463525186508796585379155,
265247833149227842278059484961926330281584344437952973839486092319885300192,
421631446041795295328070636491346018953171276542115189082171457479754499396,
59057903625576869235407103565877017330396402246452653660114888284545941770,
315797852826246435174946736461540321579373154656484006452063031513301027405,
9521420622979958910372839981791309896262328383324674284772682980734269170,
272964069264268937653695089515793248726920319914036642027008415285406913245,
194708434228888226032102758315234166672190899487218971410889219134701358728,
117864954129109327302856065706421701676973859697066630532570005860486924893,
323457021720552374478769194145226061243431674370101604382965685057422991463,
327482733702858147057975319784026874245182397914737119038454598086198587150,
159726033816658034104416471293601013976445904149240898589368461412472508473,
165970343982649234398221341351816767302457220504375238905210573566962780340,
66269488760319836583658182431744051236825244016843316092957806563966254500,
360882001282595740056823749884962228392082962172369522212117195988772429063,
202692667772209236945884489592750537635169234501360011152939202347962132650,
407257364829649465305126488148712878739144584682351279109461295389594525334,
270499607712829989691415988895838806019492861138165540862008308077962735002,
388443296961168536186587069708212659389994895697827691755155284015603161464,
45068266527940236008536134081672474027695203549460934893262212861351952384,
31319268777966350508118557206583844424308993254125039779840,
5874446556610227706402640998203302487747647,
204224545225244051821590480758420624947979343122083461045877549162059250132,
289993023590817330918274026889451152915026890048318491140264484864242055689,
211265316833000774821515110003986084297271807500310630074520699505436206838,
314620948986744608212517578488307826224331071350776523303159889004405167502,
242768143829057016675085658054156069029173843566452718977789980910319968372,
289318220340670045883106021427202666948428587921558828582664470923483208386,
254304247593881109676724582609273741670949040469906895867342151706444640548,
324707984060675446628128892371664948354047882542253609514703956739624414429,
125786084546320950738753348592393927755418642173185609412108154831520915923,
192033422676298173731756291271054199566981168481817292625435767748408605264,
70237018464728620254434305961956673950089621204502627373468857093940647376,
75218391584551901010047495874303520775865073092730040058902770251005073864,
13453,
];
ByteArrayImpl::new(array_try_into(bytes), 2)
}