Skip to content

Commit 16c10e5

Browse files
authored
solana: udpate governance wire format to conform to spec (#419)
* solana: udpate governance wire format to conform to spec * evm: add SolanaCall to GovernanceAction
1 parent 1ce9935 commit 16c10e5

File tree

3 files changed

+256
-5
lines changed

3 files changed

+256
-5
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,17 @@ NTT supports rate-limiting both on the sending and destination chains. If a tran
4040

4141
If users bridge frequently between a given source chain and destination chain, the capacity could be exhausted quickly. This can leave other users rate-limited, potentially delaying their transfers. To mitigate this issue, the outbound transfer cancels the inbound rate-limit on the source chain (refills the inbound rate-limit by an amount equal to that of the outbound transfer amount) and vice-versa, the inbound transfer cancels the outbound rate-limit on the destination chain (refills the outbound rate-limit by an amount equal to the inbound transfer amount).
4242

43+
## Wormhole Governance
44+
45+
There are general purpose governance contracts implemented for both EVM and Solana, which allow Wormhole Guardians to govern arbitrary contracts if they choose to do so (and the governed contract chooses to as well).
46+
The concrete interpretation of the governance packets are runtime specific, but they both follow the same spec (as defined in https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0002_governance_messaging.md).
47+
Namely, the governance messages start with a 32 byte module identifier, which is the string `"GeneralPurposeGovernance"` left padded, followed by a 1 byte action identifier, finally followed by a chain id.
48+
49+
The action identifier specifies the runtime. Currently, these are as follows:
50+
51+
- 0: undefined
52+
- 1: evm
53+
- 2: solana
54+
4355
___
4456
⚠️ **WARNING:** Ensure that if the `NttManager` on the source chain is configured to be in `LOCKING` mode, the corresponding `NttManager`s on the target chains are configured to be in `BURNING` mode. If not, transfers will NOT go through and user funds may be lost! Proceed with caution!

evm/src/wormhole/Governance.sol

+13-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,21 @@ contract Governance {
1111
bytes32 public constant MODULE =
1212
0x000000000000000047656E6572616C507572706F7365476F7665726E616E6365;
1313

14+
/// @notice The known set of governance actions.
15+
/// @dev As the governance logic is expanded to more runtimes, it's
16+
/// important to keep them in sync, at least the newer ones should ensure
17+
/// they don't overlap with the existing ones.
18+
///
19+
/// Existing implementations are not strongly required to be updated
20+
/// to be aware of new actions (as they will never need to know the
21+
/// action indices higher than the one corresponding to the current
22+
/// runtime), but it's good practice.
23+
///
24+
/// When adding a new runtime, make sure to at least update in the README.md
1425
enum GovernanceAction {
1526
UNDEFINED,
16-
EVM_CALL
27+
EVM_CALL,
28+
SOLANA_CALL
1729
}
1830

1931
IWormhole immutable wormhole;

solana/programs/wormhole-governance/src/instructions/governance.rs

+231-4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
//! accounts. These accounts may be in any order, with two placeholder accounts:
1313
//! - [`OWNER`]: the program will replace this account with the governance PDA
1414
//! - [`PAYER`]: the program will replace this account with the payer account
15+
use std::io;
16+
1517
use anchor_lang::prelude::*;
1618
use solana_program::instruction::Instruction;
1719
use wormhole_anchor_sdk::wormhole::PostedVaa;
20+
use wormhole_io::{Readable, Writeable};
1821
use wormhole_sdk::{Chain, GOVERNANCE_EMITTER};
1922

2023
use crate::error::GovernanceError;
@@ -42,7 +45,8 @@ pub struct Governance<'info> {
4245
seeds = [b"governance"],
4346
bump,
4447
)]
45-
/// CHECK: TODO
48+
/// CHECK: governance PDA. This PDA has to be the owner assigned to the
49+
/// governed program.
4650
pub governance: AccountInfo<'info>,
4751

4852
#[account(
@@ -71,14 +75,237 @@ pub struct Governance<'info> {
7175
pub system_program: Program<'info, System>,
7276
}
7377

74-
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
75-
// TODO: adjust wire format to match the wh governance spec
78+
/// General purpose governance message to call arbitrary instructions on a governed program.
79+
///
80+
/// This message adheres to the Wormhole governance packet standard:
81+
/// https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0002_governance_messaging.md
82+
///
83+
/// The wire format for this message is:
84+
/// | field | size (bytes) | description |
85+
/// |-----------------+----------------------------------+-----------------------------------------|
86+
/// | MODULE | 32 | Governance module identifier |
87+
/// | ACTION | 1 | Governance action identifier |
88+
/// | CHAIN | 2 | Chain identifier |
89+
/// |-----------------+----------------------------------+-----------------------------------------|
90+
/// | program_id | 32 | Program ID of the program to be invoked |
91+
/// | accounts_length | 2 | Number of accounts |
92+
/// | accounts | `accounts_length` * (32 + 1 + 1) | Accounts to be passed to the program |
93+
/// | data_length | 2 | Length of the data |
94+
/// | data | `data_length` | Data to be passed to the program |
95+
///
96+
#[derive(Clone, Debug, PartialEq, Eq)]
7697
pub struct GovernanceMessage {
7798
pub program_id: Pubkey,
7899
pub accounts: Vec<Acc>,
79100
pub data: Vec<u8>,
80101
}
81102

103+
impl GovernanceMessage {
104+
// "GeneralPurposeGovernance" (left padded)
105+
const MODULE: [u8; 32] = [
106+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x6C,
107+
0x50, 0x75, 0x72, 0x70, 0x6F, 0x73, 0x65, 0x47, 0x6F, 0x76, 0x65, 0x72, 0x6E, 0x61, 0x6E,
108+
0x63, 0x65,
109+
];
110+
}
111+
112+
#[test]
113+
fn test_governance_module() {
114+
let s = "GeneralPurposeGovernance";
115+
let mut module = [0; 32];
116+
module[32 - s.len()..].copy_from_slice(s.as_bytes());
117+
assert_eq!(module, GovernanceMessage::MODULE);
118+
}
119+
120+
impl AnchorDeserialize for GovernanceMessage {
121+
fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
122+
Readable::read(reader)
123+
}
124+
}
125+
126+
impl AnchorSerialize for GovernanceMessage {
127+
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
128+
Writeable::write(self, writer)
129+
}
130+
}
131+
132+
impl Readable for GovernanceMessage {
133+
const SIZE: Option<usize> = None;
134+
135+
fn read<R>(reader: &mut R) -> io::Result<Self>
136+
where
137+
Self: Sized,
138+
R: io::Read,
139+
{
140+
let module: [u8; 32] = Readable::read(reader)?;
141+
if module != Self::MODULE {
142+
return Err(io::Error::new(
143+
io::ErrorKind::InvalidData,
144+
"Invalid GovernanceMessage module",
145+
));
146+
}
147+
let action: GovernanceAction = Readable::read(reader)?;
148+
if action != GovernanceAction::SolanaCall {
149+
return Err(io::Error::new(
150+
io::ErrorKind::InvalidData,
151+
"Invalid GovernanceAction",
152+
));
153+
}
154+
let chain: u16 = Readable::read(reader)?;
155+
if Chain::from(chain) != Chain::Solana {
156+
return Err(io::Error::new(
157+
io::ErrorKind::InvalidData,
158+
"Invalid GovernanceMessage chain",
159+
));
160+
}
161+
162+
let program_id: Pubkey = Pubkey::new_from_array(Readable::read(reader)?);
163+
let accounts_len: u16 = Readable::read(reader)?;
164+
let mut accounts = Vec::with_capacity(accounts_len as usize);
165+
for _ in 0..accounts_len {
166+
let pubkey: [u8; 32] = Readable::read(reader)?;
167+
let is_signer: bool = Readable::read(reader)?;
168+
let is_writable: bool = Readable::read(reader)?;
169+
accounts.push(Acc {
170+
pubkey: Pubkey::new_from_array(pubkey),
171+
is_signer,
172+
is_writable,
173+
});
174+
}
175+
let data_len: u16 = Readable::read(reader)?;
176+
let mut data = vec![0; data_len as usize];
177+
reader.read_exact(&mut data)?;
178+
179+
Ok(GovernanceMessage {
180+
program_id,
181+
accounts,
182+
data,
183+
})
184+
}
185+
}
186+
187+
impl Writeable for GovernanceMessage {
188+
fn written_size(&self) -> usize {
189+
Self::MODULE.len()
190+
+ GovernanceAction::SIZE.unwrap() // action
191+
+ u16::SIZE.unwrap() // chain
192+
+ <[u8; 32]>::SIZE.unwrap() // program_id
193+
+ u16::SIZE.unwrap() // accounts_len
194+
+ self.accounts.iter() // accounts
195+
.map(|_| 32 + 1 + 1).sum::<usize>()
196+
+ u16::SIZE.unwrap() // data_len
197+
+ self.data.len() // data
198+
}
199+
200+
fn write<W>(&self, writer: &mut W) -> io::Result<()>
201+
where
202+
W: io::Write,
203+
{
204+
let GovernanceMessage {
205+
program_id,
206+
accounts,
207+
data,
208+
} = self;
209+
210+
Self::MODULE.write(writer)?;
211+
GovernanceAction::SolanaCall.write(writer)?;
212+
u16::from(Chain::Solana).write(writer)?;
213+
program_id.to_bytes().write(writer)?;
214+
(accounts.len() as u16).write(writer)?;
215+
for acc in accounts {
216+
acc.pubkey.to_bytes().write(writer)?;
217+
acc.is_signer.write(writer)?;
218+
acc.is_writable.write(writer)?;
219+
}
220+
(data.len() as u16).write(writer)?;
221+
writer.write_all(data)
222+
}
223+
}
224+
225+
#[test]
226+
fn test_governance_message_serde() {
227+
let program_id = Pubkey::new_unique();
228+
let accounts = vec![
229+
Acc {
230+
pubkey: Pubkey::new_unique(),
231+
is_signer: true,
232+
is_writable: true,
233+
},
234+
Acc {
235+
pubkey: Pubkey::new_unique(),
236+
is_signer: false,
237+
is_writable: true,
238+
},
239+
];
240+
let data = vec![1, 2, 3, 4, 5];
241+
let msg = GovernanceMessage {
242+
program_id,
243+
accounts,
244+
data,
245+
};
246+
247+
let mut buf = Vec::new();
248+
msg.serialize(&mut buf).unwrap();
249+
250+
let msg2 = GovernanceMessage::deserialize(&mut buf.as_slice()).unwrap();
251+
assert_eq!(msg, msg2);
252+
}
253+
254+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
255+
/// The known set of governance actions.
256+
///
257+
/// As the governance logic is expanded to more runtimes, it's important to keep
258+
/// them in sync, at least the newer ones should ensure they don't overlap with
259+
/// the existing ones.
260+
///
261+
/// Existing implementations are not strongly required to be updated to be aware
262+
/// of new actions (as they will never need to know the action indices higher
263+
/// than the one corresponding to the current runtime), but it's good practice.
264+
///
265+
/// When adding a new runtime, make sure to at least update in the README.md
266+
pub enum GovernanceAction {
267+
Undefined,
268+
EvmCall,
269+
SolanaCall,
270+
}
271+
272+
impl Readable for GovernanceAction {
273+
const SIZE: Option<usize> = Some(1);
274+
275+
fn read<R>(reader: &mut R) -> io::Result<Self>
276+
where
277+
Self: Sized,
278+
R: io::Read,
279+
{
280+
match Readable::read(reader)? {
281+
0 => Ok(GovernanceAction::Undefined),
282+
1 => Ok(GovernanceAction::EvmCall),
283+
2 => Ok(GovernanceAction::SolanaCall),
284+
_ => Err(io::Error::new(
285+
io::ErrorKind::InvalidData,
286+
"Invalid GovernanceAction",
287+
)),
288+
}
289+
}
290+
}
291+
292+
impl Writeable for GovernanceAction {
293+
fn written_size(&self) -> usize {
294+
Self::SIZE.unwrap()
295+
}
296+
297+
fn write<W>(&self, writer: &mut W) -> io::Result<()>
298+
where
299+
W: io::Write,
300+
{
301+
match self {
302+
GovernanceAction::Undefined => Ok(()),
303+
GovernanceAction::EvmCall => 1.write(writer),
304+
GovernanceAction::SolanaCall => 2.write(writer),
305+
}
306+
}
307+
}
308+
82309
impl From<GovernanceMessage> for Instruction {
83310
fn from(val: GovernanceMessage) -> Self {
84311
let GovernanceMessage {
@@ -114,7 +341,7 @@ impl From<Instruction> for GovernanceMessage {
114341
/// A copy of [`solana_program::instruction::AccountMeta`] with
115342
/// `AccountSerialize`/`AccountDeserialize` impl.
116343
/// Would be nice to just use the original, but it lacks these traits.
117-
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
344+
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq, Eq)]
118345
pub struct Acc {
119346
pub pubkey: Pubkey,
120347
pub is_signer: bool,

0 commit comments

Comments
 (0)