Skip to content

Commit c367812

Browse files
authored
Format and lint executor (#287)
* Start anchor program * pythnet folder * Renames * Implement processor * Comments * More comments * Bump anchor * Use new version of the wormhole package * Get Solana chain id from wormhole core * Pythnet bridge address * Remove comment * Fix borsh headers * Format * Fix precommit * Fix precommit * Precommit * Add to CI * Fix CI
1 parent d20099a commit c367812

File tree

9 files changed

+176
-68
lines changed

9 files changed

+176
-68
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Check Remote Executor
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
jobs:
9+
pre-commit:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v2
13+
- uses: actions/setup-python@v2
14+
- uses: actions-rs/toolchain@v1
15+
with:
16+
profile: minimal
17+
toolchain: nightly
18+
components: rustfmt, clippy
19+
- uses: pre-commit/[email protected]

.pre-commit-config.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v3.2.0
4+
hooks:
5+
- id: trailing-whitespace
6+
files: pythnet/
7+
- id: end-of-file-fixer
8+
files: pythnet/
9+
- id: check-added-large-files
10+
- repo: local
11+
hooks:
12+
- id: cargo-fmt-executor
13+
name: Cargo format executor
14+
language: "rust"
15+
entry: cargo +nightly fmt --manifest-path ./pythnet/remote-executor/Cargo.toml
16+
pass_filenames: false
17+
- id: cargo-clippy-executor
18+
name: Cargo clippy executor
19+
language: "rust"
20+
entry: cargo +nightly clippy --manifest-path ./pythnet/remote-executor/Cargo.toml -- -D warnings
21+
pass_filenames: false
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
use anchor_lang::prelude::*;
32

43
#[error_code]
@@ -8,5 +7,5 @@ pub enum ExecutorError {
87
GovernanceHeaderInvalidMagicNumber,
98
GovernanceHeaderInvalidModule,
109
GovernanceHeaderInvalidAction,
11-
GovernanceHeaderInvalidReceiverChain
12-
}
10+
GovernanceHeaderInvalidReceiverChain,
11+
}
Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,90 @@
11
#![deny(warnings)]
2+
#![allow(clippy::result_large_err)]
23

3-
use anchor_lang::{prelude::*, solana_program::borsh::get_packed_len};
4-
use state::{claim_record::ClaimRecord, posted_vaa::AnchorVaa};
4+
use anchor_lang::{
5+
prelude::*,
6+
solana_program::borsh::get_packed_len,
7+
};
8+
use state::{
9+
claim_record::ClaimRecord,
10+
posted_vaa::AnchorVaa,
11+
};
512

6-
mod state;
713
mod error;
14+
mod state;
815

916
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
1017

1118
#[program]
1219
pub mod remote_executor {
13-
use anchor_lang::solana_program::{program::invoke_signed, instruction::Instruction};
14-
use wormhole::Chain::{Solana, self};
20+
use anchor_lang::solana_program::{
21+
instruction::Instruction,
22+
program::invoke_signed,
23+
};
24+
use wormhole::Chain::{
25+
self,
26+
Solana,
27+
};
1528

16-
use crate::{state::{governance_payload::ExecutorPayload}, error::ExecutorError};
29+
use crate::{
30+
error::ExecutorError,
31+
state::governance_payload::ExecutorPayload,
32+
};
1733

1834
use super::*;
1935

2036
pub fn execute_posted_vaa(ctx: Context<ExecutePostedVaa>) -> Result<()> {
2137
let posted_vaa = &ctx.accounts.posted_vaa;
2238
let claim_record = &mut ctx.accounts.claim_record;
23-
assert_or_err(Chain::from(posted_vaa.emitter_chain) == Solana, err!(ExecutorError::EmitterChainNotSolana))?;
24-
assert_or_err(posted_vaa.sequence > claim_record.sequence, err!(ExecutorError::NonIncreasingSequence))?;
39+
assert_or_err(
40+
Chain::from(posted_vaa.emitter_chain) == Solana,
41+
err!(ExecutorError::EmitterChainNotSolana),
42+
)?;
43+
assert_or_err(
44+
posted_vaa.sequence > claim_record.sequence,
45+
err!(ExecutorError::NonIncreasingSequence),
46+
)?;
2547
claim_record.sequence = posted_vaa.sequence;
2648

2749
let payload = ExecutorPayload::try_from_slice(&posted_vaa.payload)?;
2850
payload.check_header()?;
29-
51+
3052
for instruction in payload.instructions.iter().map(Instruction::from) {
3153
// TO DO: We currently pass `remaining_accounts` down to the CPIs, is there a more efficient way to do it?
32-
invoke_signed(&instruction, ctx.remaining_accounts, &[&[EXECUTOR_KEY_SEED.as_bytes(), &posted_vaa.emitter_address, &[*ctx.bumps.get("executor_key").unwrap()]]])?;
54+
invoke_signed(
55+
&instruction,
56+
ctx.remaining_accounts,
57+
&[&[
58+
EXECUTOR_KEY_SEED.as_bytes(),
59+
&posted_vaa.emitter_address,
60+
&[*ctx.bumps.get("executor_key").unwrap()],
61+
]],
62+
)?;
3363
}
3464
Ok(())
3565
}
3666
}
3767

38-
const EXECUTOR_KEY_SEED : &str = "EXECUTOR_KEY";
39-
const CLAIM_RECORD_SEED : &str = "CLAIM_RECORD";
40-
68+
const EXECUTOR_KEY_SEED: &str = "EXECUTOR_KEY";
69+
const CLAIM_RECORD_SEED: &str = "CLAIM_RECORD";
4170

4271
#[derive(Accounts)]
4372
pub struct ExecutePostedVaa<'info> {
4473
#[account(mut)]
45-
pub payer : Signer<'info>,
46-
pub posted_vaa : Account<'info, AnchorVaa>,
74+
pub payer: Signer<'info>,
75+
pub posted_vaa: Account<'info, AnchorVaa>,
4776
#[account(seeds = [EXECUTOR_KEY_SEED.as_bytes(), &posted_vaa.emitter_address], bump)]
48-
pub executor_key : UncheckedAccount<'info>,
77+
pub executor_key: UncheckedAccount<'info>,
4978
/// The reason claim record is separated from executor_key is that executor key might need to pay in the CPI, so we want it to be a wallet
5079
#[account(init_if_needed, space = 8 + get_packed_len::<ClaimRecord>(), payer=payer, seeds = [CLAIM_RECORD_SEED.as_bytes(), &posted_vaa.emitter_address], bump)]
51-
pub claim_record : Account<'info, ClaimRecord>,
52-
pub system_program: Program<'info, System>
80+
pub claim_record: Account<'info, ClaimRecord>,
81+
pub system_program: Program<'info, System>,
5382
}
5483

55-
pub fn assert_or_err(condition : bool, error : Result<()>) -> Result<()>{
84+
pub fn assert_or_err(condition: bool, error: Result<()>) -> Result<()> {
5685
if !condition {
5786
error
5887
} else {
5988
Result::Ok(())
6089
}
61-
}
90+
}
Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
use anchor_lang::prelude::*;
2-
use anchor_lang::account;
3-
use anchor_lang::prelude::borsh::BorshSchema;
1+
use anchor_lang::{
2+
account,
3+
prelude::{
4+
borsh::BorshSchema,
5+
*,
6+
},
7+
};
48

59
#[account]
610
#[derive(Default, BorshSchema)]
7-
/// This struct records
8-
pub struct ClaimRecord{
9-
pub sequence : u64
10-
}
11+
/// This struct records
12+
pub struct ClaimRecord {
13+
pub sequence: u64,
14+
}

pythnet/remote-executor/programs/remote-executor/src/state/governance_payload.rs

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,56 @@
1-
use std::{ops::Deref, mem::size_of, io::ErrorKind};
2-
3-
use anchor_lang::{prelude::*, solana_program::instruction::Instruction};
1+
use std::{
2+
io::ErrorKind,
3+
mem::size_of,
4+
ops::Deref,
5+
};
6+
7+
use anchor_lang::{
8+
prelude::*,
9+
solana_program::instruction::Instruction,
10+
};
411
use wormhole::Chain;
512

6-
use crate::{assert_or_err, error::ExecutorError};
13+
use crate::{
14+
assert_or_err,
15+
error::ExecutorError,
16+
};
717

8-
pub const MAGIC_NUMBER : u32 = 0x4d475450; // Reverse order of the solidity contract because borsh uses little endian numbers
18+
pub const MAGIC_NUMBER: u32 = 0x4d475450; // Reverse order of the solidity contract because borsh uses little endian numbers
919

1020
#[derive(AnchorDeserialize, AnchorSerialize)]
11-
pub struct ExecutorPayload{
12-
pub header : GovernanceHeader,
13-
pub instructions: Vec<InstructionData>,
21+
pub struct ExecutorPayload {
22+
pub header: GovernanceHeader,
1423

24+
pub instructions: Vec<InstructionData>,
1525
}
1626

17-
#[derive(AnchorDeserialize, AnchorSerialize, PartialEq)]
27+
#[derive(AnchorDeserialize, AnchorSerialize, PartialEq, Eq)]
1828
pub enum Module {
1929
Executor = 0,
20-
Target
30+
Target,
2131
}
2232

23-
#[derive(AnchorDeserialize, AnchorSerialize, PartialEq)]
33+
#[derive(AnchorDeserialize, AnchorSerialize, PartialEq, Eq)]
2434
pub enum Action {
2535
ExecutePostedVaa = 0,
2636
}
2737

28-
2938
/// The Governance Header format for pyth governance messages is the following:
3039
/// - A 4 byte magic number `['P','T','G','M']`
3140
/// - A one byte module variant (0 for Executor and 1 for Target contracts)
3241
/// - A one byte action variant (for Executor only 0 is currently valid)
3342
/// - A bigendian 2 bytes u16 chain id
3443
#[derive(AnchorDeserialize, AnchorSerialize)]
3544
pub struct GovernanceHeader {
36-
pub magic_number : u32,
37-
pub module : Module,
38-
pub action : Action,
39-
pub chain : BigEndianU16
45+
pub magic_number: u32,
46+
pub module: Module,
47+
pub action: Action,
48+
pub chain: BigEndianU16,
4049
}
4150

4251
/// Hack to get Borsh to deserialize, serialize this number with big endian order
43-
pub struct BigEndianU16{
44-
pub value : u16
52+
pub struct BigEndianU16 {
53+
pub value: u16,
4554
}
4655

4756
impl AnchorDeserialize for BigEndianU16 {
@@ -54,7 +63,7 @@ impl AnchorDeserialize for BigEndianU16 {
5463
}
5564
let res = u16::from_be_bytes(buf[..size_of::<u16>()].try_into().unwrap());
5665
*buf = &buf[size_of::<u16>()..];
57-
Ok(BigEndianU16{ value:res})
66+
Ok(BigEndianU16 { value: res })
5867
}
5968
}
6069

@@ -113,13 +122,25 @@ impl From<&InstructionData> for Instruction {
113122
}
114123

115124
impl ExecutorPayload {
116-
const MODULE : Module = Module::Executor;
117-
const ACTION : Action = Action::ExecutePostedVaa;
118-
119-
pub fn check_header(&self) -> Result<()>{
120-
assert_or_err(self.header.magic_number == MAGIC_NUMBER, err!(ExecutorError::GovernanceHeaderInvalidMagicNumber))?;
121-
assert_or_err(self.header.module == ExecutorPayload::MODULE, err!(ExecutorError::GovernanceHeaderInvalidMagicNumber))?;
122-
assert_or_err(self.header.action == ExecutorPayload::ACTION, err!(ExecutorError::GovernanceHeaderInvalidMagicNumber))?;
123-
assert_or_err(Chain::from(self.header.chain.value) == Chain::Pythnet, err!(ExecutorError::GovernanceHeaderInvalidMagicNumber))
125+
const MODULE: Module = Module::Executor;
126+
const ACTION: Action = Action::ExecutePostedVaa;
127+
128+
pub fn check_header(&self) -> Result<()> {
129+
assert_or_err(
130+
self.header.magic_number == MAGIC_NUMBER,
131+
err!(ExecutorError::GovernanceHeaderInvalidMagicNumber),
132+
)?;
133+
assert_or_err(
134+
self.header.module == ExecutorPayload::MODULE,
135+
err!(ExecutorError::GovernanceHeaderInvalidMagicNumber),
136+
)?;
137+
assert_or_err(
138+
self.header.action == ExecutorPayload::ACTION,
139+
err!(ExecutorError::GovernanceHeaderInvalidMagicNumber),
140+
)?;
141+
assert_or_err(
142+
Chain::from(self.header.chain.value) == Chain::Pythnet,
143+
err!(ExecutorError::GovernanceHeaderInvalidMagicNumber),
144+
)
124145
}
125-
}
146+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pub mod claim_record;
2+
pub mod governance_payload;
23
pub mod posted_vaa;
3-
pub mod governance_payload;
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
use std::{io::Write, str::FromStr, ops::Deref};
21
use anchor_lang::prelude::*;
2+
use std::{
3+
io::Write,
4+
ops::Deref,
5+
str::FromStr,
6+
};
37
use wormhole_solana::VAA;
48

59
impl Owner for AnchorVaa {
6-
fn owner() -> Pubkey{
7-
Pubkey::from_str("H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU").unwrap() // Pythnet bridge address
10+
fn owner() -> Pubkey {
11+
Pubkey::from_str("H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU").unwrap()
12+
// Pythnet bridge address
813
}
914
}
1015

@@ -15,16 +20,16 @@ impl AccountDeserialize for AnchorVaa {
1520
}
1621

1722
// Manual implementation because this account does not have an anchor discriminator
18-
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
23+
fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
1924
AnchorDeserialize::deserialize(buf)
20-
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
25+
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
2126
}
2227
}
2328

2429
impl AccountSerialize for AnchorVaa {
2530
// Make this fail, this is readonly VAA it should never be serialized by this program
2631
fn try_serialize<W: Write>(&self, _writer: &mut W) -> Result<()> {
27-
return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
32+
Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into())
2833
}
2934
}
3035

@@ -36,8 +41,7 @@ impl Deref for AnchorVaa {
3641
}
3742
}
3843

39-
4044
#[derive(Clone, AnchorDeserialize, AnchorSerialize)]
41-
pub struct AnchorVaa{
42-
pub vaa : VAA
45+
pub struct AnchorVaa {
46+
pub vaa: VAA,
4347
}

pythnet/remote-executor/rustfmt.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Merge similar crates together to avoid multiple use statements.
2+
imports_granularity = "Crate"
3+
4+
# Consistency in formatting makes tool based searching/editing better.
5+
empty_item_single_line = false
6+
7+
# Easier editing when arbitrary mixed use statements do not collapse.
8+
imports_layout = "Vertical"
9+
10+
# Default rustfmt formatting of match arms with branches is awful.
11+
match_arm_leading_pipes = "Preserve"

0 commit comments

Comments
 (0)