accountant: use token bridge governance vaa to make modifications

This commit is contained in:
Conor Patrick 2023-02-14 00:39:45 +00:00
parent 3e05a027af
commit a3a8e3ea87
10 changed files with 405 additions and 155 deletions

View File

@ -0,0 +1,3 @@
[profile.release]
lto = true
strip = true

View File

@ -2,7 +2,7 @@ use std::marker::PhantomData;
use accountant::{
query_balance, query_modification,
state::{account, transfer, Modification, TokenAddress, Transfer},
state::{account, transfer, Kind, Modification, Reason, TokenAddress, Transfer},
validate_transfer,
};
use anyhow::{ensure, Context};
@ -17,7 +17,7 @@ use cw_storage_plus::Bound;
use serde_wormhole::RawMessage;
use tinyvec::{Array, TinyVec};
use wormhole::{
token::{Action, GovernancePacket, Message},
token::{Action, GovernancePacket, Message, ModificationKind},
vaa::{self, Body, Header, Signature},
Chain,
};
@ -74,17 +74,17 @@ pub fn execute(
guardian_set_index,
signature,
} => submit_observations(deps, info, observations, guardian_set_index, signature),
ExecuteMsg::ModifyBalance {
modification,
guardian_set_index,
signatures,
} => modify_balance(deps, info, modification, guardian_set_index, signatures),
// ExecuteMsg::ModifyBalance {
// modification,
// guardian_set_index,
// signatures,
// } => modify_balance(deps, info, modification, guardian_set_index, signatures),
ExecuteMsg::UpgradeContract {
upgrade,
guardian_set_index,
signatures,
} => upgrade_contract(deps, env, info, upgrade, guardian_set_index, signatures),
ExecuteMsg::SubmitVAAs { vaas } => submit_vaas(deps, info, vaas),
ExecuteMsg::SubmitVaas { vaas } => submit_vaas(deps, info, vaas),
}
}
@ -276,31 +276,17 @@ fn handle_observation(
fn modify_balance(
deps: DepsMut<WormholeQuery>,
info: MessageInfo,
modification: Binary,
guardian_set_index: u32,
signatures: Vec<Signature>,
) -> Result<Response, AnyError> {
deps.querier
.query::<Empty>(
&WormholeQuery::VerifyQuorum {
data: modification.clone(),
guardian_set_index,
signatures: signatures.into_iter().map(From::from).collect(),
}
.into(),
)
.context(ContractError::VerifyQuorum)?;
info: &MessageInfo,
modification: Modification,
) -> Result<Event, AnyError> {
let mut event = accountant::modify_balance(deps, modification)
.context("failed to modify account balance")?;
let msg: Modification = from_binary(&modification).context("failed to parse `Modification`")?;
let event =
accountant::modify_balance(deps, msg).context("failed to modify account balance")?;
Ok(Response::new()
event = event
.add_attribute("action", "modify_balance")
.add_attribute("owner", info.sender)
.add_event(event))
.add_attribute("owner", info.sender.clone());
Ok(event)
}
fn upgrade_contract(
@ -345,7 +331,7 @@ fn submit_vaas(
) -> Result<Response, AnyError> {
let evts = vaas
.into_iter()
.map(|v| handle_vaa(deps.branch(), v))
.map(|v| handle_vaa(deps.branch(), &info, v))
.collect::<anyhow::Result<Vec<_>>>()?;
Ok(Response::new()
.add_attribute("action", "submit_vaas")
@ -353,7 +339,11 @@ fn submit_vaas(
.add_events(evts))
}
fn handle_vaa(mut deps: DepsMut<WormholeQuery>, vaa: Binary) -> anyhow::Result<Event> {
fn handle_vaa(
mut deps: DepsMut<WormholeQuery>,
info: &MessageInfo,
vaa: Binary,
) -> anyhow::Result<Event> {
let (header, data) = serde_wormhole::from_slice::<(Header, &RawMessage)>(&vaa)
.context("failed to parse VAA header")?;
@ -394,12 +384,12 @@ fn handle_vaa(mut deps: DepsMut<WormholeQuery>, vaa: Binary) -> anyhow::Result<E
bail!(ContractError::DuplicateMessage);
}
let evt = if body.emitter_chain == Chain::Solana
let evt = if (body.emitter_chain == Chain::Solana || body.emitter_chain == Chain::Wormchain)
&& body.emitter_address == wormhole::GOVERNANCE_EMITTER
{
let govpacket = serde_wormhole::from_slice(body.payload)
.context("failed to parse governance packet")?;
handle_governance_vaa(deps.branch(), body.with_payload(govpacket))?
handle_governance_vaa(deps.branch(), info, body.with_payload(govpacket))?
} else {
let msg = serde_wormhole::from_slice(body.payload)
.context("failed to parse tokenbridge message")?;
@ -415,6 +405,7 @@ fn handle_vaa(mut deps: DepsMut<WormholeQuery>, vaa: Binary) -> anyhow::Result<E
fn handle_governance_vaa(
deps: DepsMut<WormholeQuery>,
info: &MessageInfo,
body: Body<GovernancePacket>,
) -> anyhow::Result<Event> {
ensure!(
@ -438,6 +429,34 @@ fn handle_governance_vaa(
.add_attribute("chain", chain.to_string())
.add_attribute("emitter_address", emitter_address.to_string()))
}
Action::ModifyBalance {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
} => {
let token_address = TokenAddress::new(token_address.0);
let kind = match kind {
ModificationKind::Add => Kind::Add,
ModificationKind::Subtract => Kind::Sub,
ModificationKind::Unknown => bail!("unsupported governance action"),
};
let amount = Uint256::from_be_bytes(amount.0);
let reason = Reason::new(reason);
let modification = Modification {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
};
modify_balance(deps, info, modification).map_err(|e| e.into())
}
_ => bail!("unsupported governance action"),
}
}

View File

@ -106,18 +106,17 @@ pub enum ExecuteMsg {
signature: Signature,
},
/// Modifies the balance of a single account. Used to manually override the balance.
ModifyBalance {
// A serialized `Modification` message.
modification: Binary,
// /// Modifies the balance of a single account. Used to manually override the balance.
// ModifyBalance {
// // A serialized governance vaa containing a serialized `Modification` message.
// // modification_vaa: Binary,
// The index of the guardian set used to sign this modification.
guardian_set_index: u32,
// A quorum of signatures for `modification`.
signatures: Vec<Signature>,
},
// // The index of the guardian set used to sign this modification.
// // guardian_set_index: u32,
// // A quorum of signatures for `modification`.
// // signatures: Vec<Signature>,
// },
UpgradeContract {
// A serialized `Upgrade` message.
upgrade: Binary,
@ -131,7 +130,7 @@ pub enum ExecuteMsg {
/// Submit one or more signed VAAs to update the on-chain state. If processing any of the VAAs
/// returns an error, the entire transaction is aborted and none of the VAAs are committed.
SubmitVAAs {
SubmitVaas {
/// One or more VAAs to be submitted. Each VAA should be encoded in the standard wormhole
/// wire format.
vaas: Vec<Binary>,

View File

@ -1,6 +1,6 @@
#![allow(dead_code)]
use accountant::state::{account, transfer, Modification};
use accountant::state::{account, transfer, Kind, Modification};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
testing::{MockApi, MockStorage},
@ -19,9 +19,9 @@ use global_accountant::{
};
use serde::Serialize;
use wormhole::{
token::{Action, GovernancePacket},
token::{Action, GovernancePacket, ModificationKind},
vaa::{Body, Header, Signature},
Address, Chain, Vaa,
Address, Amount, Chain, Vaa,
};
use wormhole_bindings::{fake, WormholeQuery};
@ -34,6 +34,7 @@ pub struct TransferResponse {
pub struct Contract {
addr: Addr,
app: FakeApp,
pub sequence: u64,
}
impl Contract {
@ -67,23 +68,71 @@ impl Contract {
)
}
pub fn modify_balance(
pub fn modify_balance_with(
&mut self,
modification: Binary,
guardian_set_index: u32,
signatures: Vec<Signature>,
modification: Modification,
wh: &fake::WormholeKeeper,
tamperer: impl Fn(Vaa<GovernancePacket>) -> Binary,
) -> anyhow::Result<AppResponse> {
let Modification {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
} = modification;
let token_address = Address(*token_address);
let kind = match kind {
Kind::Add => ModificationKind::Add,
Kind::Sub => ModificationKind::Subtract,
};
let amount = Amount(amount.to_be_bytes());
let reason = reason.0;
let body = Body {
timestamp: self.sequence as u32,
nonce: self.sequence as u32,
emitter_chain: Chain::Solana,
emitter_address: wormhole::GOVERNANCE_EMITTER,
sequence: self.sequence,
consistency_level: 0,
payload: GovernancePacket {
chain: Chain::Any,
action: Action::ModifyBalance {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
},
},
};
self.sequence += 1;
let (vaa, _) = sign_vaa_body(wh, body);
let data = tamperer(vaa);
let vaas: Vec<Binary> = vec![data];
self.app.execute_contract(
Addr::unchecked(USER),
self.addr(),
&ExecuteMsg::ModifyBalance {
modification,
guardian_set_index,
signatures,
},
&ExecuteMsg::SubmitVaas { vaas },
&[],
)
}
pub fn modify_balance(
&mut self,
modification: Modification,
wh: &fake::WormholeKeeper,
) -> anyhow::Result<AppResponse> {
self.modify_balance_with(modification, wh, |vaa| {
serde_wormhole::to_vec(&vaa).map(From::from).unwrap()
})
}
pub fn upgrade_contract(
&mut self,
@ -107,7 +156,7 @@ impl Contract {
self.app.execute_contract(
Addr::unchecked(ADMIN),
self.addr(),
&ExecuteMsg::SubmitVAAs { vaas },
&ExecuteMsg::SubmitVaas { vaas },
&[],
)
}
@ -287,7 +336,16 @@ pub fn proper_instantiate() -> (fake::WormholeKeeper, Contract) {
)
.unwrap();
(wh, Contract { addr, app })
let sequence = 0;
(
wh,
Contract {
addr,
app,
sequence,
},
)
}
pub fn sign_vaa_body<P: Serialize>(wh: &fake::WormholeKeeper, body: Body<P>) -> (Vaa<P>, Binary) {

View File

@ -1,14 +1,13 @@
mod helpers;
use accountant::state::{account, Kind, Modification};
use cosmwasm_std::{to_binary, Event, Uint256};
use cosmwasm_std::{Event, Uint256};
use helpers::*;
#[test]
fn simple_modify() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let m = Modification {
sequence: 0,
chain_id: 1,
@ -16,14 +15,10 @@ fn simple_modify() {
token_address: [0x7c; 32].into(),
kind: Kind::Add,
amount: Uint256::from(300u128),
reason: "test".into(),
reason: "test".try_into().unwrap(),
};
let modification = to_binary(&m).unwrap();
let signatures = wh.sign(&modification);
let resp = contract
.modify_balance(modification, index, signatures)
.unwrap();
let resp = contract.modify_balance(m.clone(), &wh).unwrap();
let evt = Event::new("wasm-Modification")
.add_attribute("sequence", serde_json_wasm::to_string(&m.sequence).unwrap())
@ -59,7 +54,6 @@ fn simple_modify() {
fn duplicate_modify() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let m = Modification {
sequence: 0,
chain_id: 1,
@ -67,17 +61,13 @@ fn duplicate_modify() {
token_address: [0x7c; 32].into(),
kind: Kind::Add,
amount: Uint256::from(300u128),
reason: "test".into(),
reason: "test".try_into().unwrap(),
};
let modification = to_binary(&m).unwrap();
let signatures = wh.sign(&modification);
contract
.modify_balance(modification.clone(), index, signatures.clone())
.unwrap();
contract.modify_balance(m.clone(), &wh).unwrap();
contract
.modify_balance(modification, index, signatures)
.modify_balance(m, &wh)
.expect_err("successfully submitted duplicate modification");
}
@ -85,7 +75,6 @@ fn duplicate_modify() {
fn round_trip() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let mut m = Modification {
sequence: 0,
chain_id: 1,
@ -93,14 +82,10 @@ fn round_trip() {
token_address: [0x7c; 32].into(),
kind: Kind::Add,
amount: Uint256::from(300u128),
reason: "test".into(),
reason: "test".try_into().unwrap(),
};
let modification = to_binary(&m).unwrap();
let signatures = wh.sign(&modification);
contract
.modify_balance(modification, index, signatures)
.unwrap();
contract.modify_balance(m.clone(), &wh).unwrap();
let actual = contract.query_modification(m.sequence).unwrap();
assert_eq!(m, actual);
@ -108,14 +93,9 @@ fn round_trip() {
// Now reverse the modification.
m.sequence += 1;
m.kind = Kind::Sub;
m.reason = "reverse".into();
m.reason = "reverse".try_into().unwrap();
let modification = to_binary(&m).unwrap();
let signatures = wh.sign(&modification);
contract
.modify_balance(modification, index, signatures)
.unwrap();
contract.modify_balance(m.clone(), &wh).unwrap();
let actual = contract.query_modification(m.sequence).unwrap();
assert_eq!(m, actual);
@ -134,7 +114,6 @@ fn round_trip() {
fn missing_guardian_set() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let m = Modification {
sequence: 0,
chain_id: 1,
@ -142,13 +121,14 @@ fn missing_guardian_set() {
token_address: [0x7c; 32].into(),
kind: Kind::Add,
amount: Uint256::from(300u128),
reason: "test".into(),
reason: "test".try_into().unwrap(),
};
let modification = to_binary(&m).unwrap();
let signatures = wh.sign(&modification);
contract
.modify_balance(modification, index + 1, signatures)
.modify_balance_with(m, &wh, |mut vaa| {
vaa.guardian_set_index += 1;
serde_wormhole::to_vec(&vaa).map(From::from).unwrap()
})
.expect_err("successfully modified balance with invalid guardian set");
}
@ -156,7 +136,6 @@ fn missing_guardian_set() {
fn expired_guardian_set() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let mut block = contract.app().block_info();
wh.set_expiration(block.height);
block.height += 1;
@ -169,13 +148,10 @@ fn expired_guardian_set() {
token_address: [0x7c; 32].into(),
kind: Kind::Add,
amount: Uint256::from(300u128),
reason: "test".into(),
reason: "test".try_into().unwrap(),
};
let modification = to_binary(&m).unwrap();
let signatures = wh.sign(&modification);
contract
.modify_balance(modification, index, signatures)
.modify_balance(m, &wh)
.expect_err("successfully modified balance with expired guardian set");
}
@ -183,7 +159,6 @@ fn expired_guardian_set() {
fn no_quorum() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let m = Modification {
sequence: 0,
chain_id: 1,
@ -191,19 +166,19 @@ fn no_quorum() {
token_address: [0x7c; 32].into(),
kind: Kind::Add,
amount: Uint256::from(300u128),
reason: "test".into(),
reason: "test".try_into().unwrap(),
};
let modification = to_binary(&m).unwrap();
let mut signatures = wh.sign(&modification);
let newlen = wh
.calculate_quorum(0, contract.app().block_info().height)
.map(|q| (q - 1) as usize)
.unwrap();
signatures.truncate(newlen);
contract
.modify_balance(modification, index, signatures)
.modify_balance_with(m, &wh, |mut vaa| {
vaa.signatures.truncate(newlen);
serde_wormhole::to_vec(&vaa).map(From::from).unwrap()
})
.expect_err("successfully submitted modification without quorum");
}
@ -213,7 +188,6 @@ fn repeat() {
let (wh, mut contract) = proper_instantiate();
let index = wh.guardian_set_index();
let mut m = Modification {
sequence: 0,
chain_id: 1,
@ -221,18 +195,13 @@ fn repeat() {
token_address: [0x7c; 32].into(),
kind: Kind::Add,
amount: Uint256::from(300u128),
reason: "test".into(),
reason: "test".try_into().unwrap(),
};
for _ in 0..ITERATIONS {
m.sequence += 1;
let modification = to_binary(&m).unwrap();
let signatures = wh.sign(&modification);
contract
.modify_balance(modification, index, signatures)
.unwrap();
contract.modify_balance(m.clone(), &wh).unwrap();
let actual = contract.query_modification(m.sequence).unwrap();
assert_eq!(m, actual);

View File

@ -6,7 +6,7 @@ use accountant::state::{
account::{self, Balance},
transfer, Kind, Modification, Transfer,
};
use cosmwasm_std::{to_binary, Uint256};
use cosmwasm_std::Uint256;
use global_accountant::msg::TransferStatus;
use helpers::*;
use wormhole::{token::Message, vaa::Body, Address, Amount};
@ -17,21 +17,17 @@ fn create_accounts(wh: &fake::WormholeKeeper, contract: &mut Contract, count: us
for i in 0..count {
for j in 0..count {
s += 1;
let m = to_binary(&Modification {
let m = Modification {
sequence: s,
chain_id: i as u16,
token_chain: j as u16,
token_address: [i as u8; 32].into(),
kind: Kind::Add,
amount: Uint256::from(j as u128),
reason: "create_accounts".into(),
})
.unwrap();
reason: "create_accounts".try_into().unwrap(),
};
let signatures = wh.sign(&m);
contract
.modify_balance(m, wh.guardian_set_index(), signatures)
.unwrap();
contract.modify_balance(m, wh).unwrap();
}
}
}
@ -101,14 +97,10 @@ pub fn create_modifications(
token_address: [i as u8; 32].into(),
kind: Kind::Add,
amount: Uint256::from(i as u128),
reason: format!("{i}"),
reason: format!("{i}").as_str().try_into().unwrap(),
};
let msg = to_binary(&m).unwrap();
let signatures = wh.sign(&msg);
contract
.modify_balance(msg, wh.guardian_set_index(), signatures)
.unwrap();
contract.modify_balance(m.clone(), wh).unwrap();
out.push(m);
}

View File

@ -515,19 +515,20 @@ fn missing_native_account() {
.calculate_quorum(index, contract.app().block_info().height)
.unwrap() as usize;
// increase the sequence to be large than the vaa's used to register emitters.
contract.sequence += 100;
// We need to set up a fake wrapped account so that the initial check succeeds.
let m = to_binary(&Modification {
let m = Modification {
sequence: 0,
chain_id: emitter_chain,
token_chain,
token_address: token_address.into(),
kind: Kind::Add,
amount: Uint256::new(amount.0),
reason: "fake wrapped balance for testing".into(),
})
.unwrap();
let signatures = wh.sign(&m);
contract.modify_balance(m, index, signatures).unwrap();
reason: "fake wrapped balance for testing".try_into().unwrap(),
};
contract.modify_balance(m, &wh).unwrap();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {
@ -633,20 +634,20 @@ fn wrapped_to_wrapped() {
register_emitters(&wh, &mut contract, 15);
let index = wh.guardian_set_index();
let num_guardians = wh.num_guardians();
// increase the sequence to be large than the vaa's used to register emitters.
contract.sequence += 100;
// We need an initial fake wrapped account.
let m = to_binary(&Modification {
let m = Modification {
sequence: 0,
chain_id: emitter_chain,
token_chain,
token_address: token_address.into(),
kind: Kind::Add,
amount: Uint256::new(amount.0),
reason: "fake wrapped balance for testing".into(),
})
.unwrap();
let signatures = wh.sign(&m);
contract.modify_balance(m, index, signatures).unwrap();
reason: "fake wrapped balance for testing".try_into().unwrap(),
};
contract.modify_balance(m, &wh).unwrap();
let key = transfer::Key::new(emitter_chain, [emitter_chain as u8; 32].into(), 37);
let msg = Message::Transfer {

View File

@ -227,7 +227,7 @@ pub enum ModifyBalanceError {
/// token_address: [3u8; 32].into(),
/// kind: Kind::Sub,
/// amount: Uint256::from(4u128),
/// reason: "test".into(),
/// reason: "test".try_into().unwrap(),
/// };
///
/// let err = modify_balance(deps.as_mut(), m)
@ -400,7 +400,7 @@ mod tests {
token_address: [i as u8; 32].into(),
kind: if i % 2 == 0 { Kind::Add } else { Kind::Sub },
amount: Uint256::from(i as u128),
reason: format!("{i}"),
reason: [0x20u8; 32].into(),
};
out.push(m);
}
@ -547,7 +547,7 @@ mod tests {
token_address: tx.data.token_address,
kind: Kind::Add,
amount: wrapped,
reason: "test".into(),
reason: [0x20u8; 32].into(),
};
modify_balance(deps.as_mut(), m).unwrap();
@ -960,7 +960,7 @@ mod tests {
token_address: [3u8; 32].into(),
kind: Kind::Add,
amount: Uint256::from(4u128),
reason: "test".into(),
reason: [0x20u8; 32].into(),
};
let evt = modify_balance(deps.as_mut(), m.clone()).unwrap();
@ -997,7 +997,7 @@ mod tests {
token_address: [3u8; 32].into(),
kind: Kind::Add,
amount: Uint256::from(4u128),
reason: "test".into(),
reason: [0x20u8; 32].into(),
};
modify_balance(deps.as_mut(), m.clone()).unwrap();
@ -1020,7 +1020,7 @@ mod tests {
token_address: [3u8; 32].into(),
kind: Kind::Add,
amount: Uint256::from(4u128),
reason: "test".into(),
reason: [0x20u8; 32].into(),
};
modify_balance(deps.as_mut(), m.clone()).unwrap();
@ -1044,7 +1044,7 @@ mod tests {
token_address: [3u8; 32].into(),
kind: Kind::Add,
amount: Uint256::from(4u128),
reason: "test".into(),
reason: [0x20u8; 32].into(),
};
for i in 0..ITERATIONS {
@ -1069,7 +1069,7 @@ mod tests {
token_address: [3u8; 32].into(),
kind: Kind::Sub,
amount: Uint256::from(4u128),
reason: "test".into(),
reason: [0x20u8; 32].into(),
};
let e = modify_balance(deps.as_mut(), m)
@ -1251,7 +1251,7 @@ mod tests {
token_address: [i as u8; 32].into(),
kind: if i % 2 == 0 { Kind::Add } else { Kind::Sub },
amount: Uint256::from(i as u128),
reason: format!("{i}"),
reason: [0x20u8; 32].into(),
};
let key = i as u64;

View File

@ -19,8 +19,8 @@ pub const TRANSFERS: Map<transfer::Key, transfer::Data> = Map::new("accountant/t
#[cw_serde]
#[derive(Eq, PartialOrd, Ord)]
pub enum Kind {
Add,
Sub,
Add = 1,
Sub = 2,
}
impl fmt::Display for Kind {
@ -32,6 +32,46 @@ impl fmt::Display for Kind {
}
}
#[cw_serde]
#[derive(Copy, Default, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Reason(
#[serde(with = "fixed_bytes_as_string_serde")]
#[schemars(with = "String")]
pub [u8; 32],
);
impl Reason {
pub const fn new(reason: [u8; 32]) -> Reason {
Reason(reason)
}
// pub fn to_string(&self) -> String {
// unsafe { std::str::from_utf8_unchecked(&self.0).to_string() }
// }
}
impl From<[u8; 32]> for Reason {
fn from(reason: [u8; 32]) -> Self {
Reason(reason)
}
}
impl TryFrom<&str> for Reason {
type Error = &'static str;
fn try_from(reason: &str) -> Result<Self, Self::Error> {
// default all spaces
let mut bz = [b' '; 32];
if reason.len() <= 32 {
for (i, byte) in reason.as_bytes().iter().enumerate() {
bz[i] = *byte;
}
Ok(Reason(bz))
} else {
Err("reason cannot be longer than 32 bytes")
}
}
}
#[cw_serde]
#[derive(Eq, PartialOrd, Ord)]
pub struct Modification {
@ -49,7 +89,30 @@ pub struct Modification {
// The amount to be modified.
pub amount: Uint256,
// A human-readable reason for the modification.
pub reason: String,
pub reason: Reason,
}
pub const MODIFICATIONS: Map<u64, Modification> = Map::new("accountant/modifications");
pub mod fixed_bytes_as_string_serde {
use serde::{Deserialize, Serialize};
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(v: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
let str = String::from_utf8((*v).into())
.map_err(|_| serde::ser::Error::custom("invalid utf8"))?;
String::serialize(&str, s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
static EXPECTING: &str = "reason string of 32 bytes";
let str = String::deserialize(d)?;
let bz = str.as_bytes();
if bz.len() != 32 {
return Err(serde::de::Error::invalid_length(32, &EXPECTING));
}
let mut bz32 = [0u8; 32];
bz32.clone_from_slice(bz);
Ok(bz32)
}
}

View File

@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
use serde_wormhole::RawMessage;
use crate::{Address, Amount, Chain};
use serde::{Deserializer, Serializer};
/// Represents a non-governance action targeted at the token bridge.
///
@ -201,6 +202,52 @@ pub enum Message<P = Box<RawMessage>> {
},
}
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
pub enum ModificationKind {
Unknown = 0,
Add = 1,
Subtract = 2,
}
impl From<u8> for ModificationKind {
fn from(other: u8) -> ModificationKind {
match other {
1 => ModificationKind::Add,
2 => ModificationKind::Subtract,
_ => ModificationKind::Unknown,
}
}
}
impl From<ModificationKind> for u8 {
fn from(other: ModificationKind) -> u8 {
match other {
ModificationKind::Unknown => 0,
ModificationKind::Add => 1,
ModificationKind::Subtract => 2,
}
}
}
impl Serialize for ModificationKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8((*self).into())
}
}
impl<'de> Deserialize<'de> for ModificationKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
<u8 as Deserialize>::deserialize(deserializer).map(Self::from)
}
}
/// Represents a governance action targeted at the token bridge.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Action {
@ -216,6 +263,28 @@ pub enum Action {
/// Upgrades the token bridge contract to a new address.
#[serde(rename = "2")]
ContractUpgrade { new_contract: Address },
// Modify balance for tokenbridge
#[serde(rename = "3")]
ModifyBalance {
// module: \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TokenBridge (32)
// chainId: \x03\x0c (2)
// action: \x03 (1)
// \x00\x00\x00\x00\x00\x00\x00$
sequence: u64, // 8
// \x00\x01
chain_id: u16, // 10
// \x00\x01
token_chain: u16, // 12
// ||||||||||||||||||||||||||||||||
token_address: Address, // 44
// \x01
kind: ModificationKind, // 45
amount: Amount, // 78
reason: [u8; 32], // 110
},
}
/// Represents the payload for a governance VAA targeted at the token bridge.
@ -241,7 +310,7 @@ mod governance_packet_impl {
use crate::{
token::{Action, GovernancePacket},
Address, Chain,
Address, Amount, Chain,
};
// MODULE = "TokenBridge"
@ -290,6 +359,17 @@ mod governance_packet_impl {
emitter_address: Address,
}
#[derive(Serialize, Deserialize)]
struct ModifyBalance {
sequence: u64,
chain_id: u16,
token_chain: u16,
token_address: Address,
kind: super::ModificationKind,
amount: Amount,
reason: [u8; 32],
}
impl Serialize for GovernancePacket {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -320,6 +400,30 @@ mod governance_packet_impl {
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field("payload", &ContractUpgrade { new_contract })?;
}
Action::ModifyBalance {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
} => {
seq.serialize_field("action", &3u8)?;
seq.serialize_field("chain", &self.chain)?;
seq.serialize_field(
"payload",
&ModifyBalance {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
},
)?;
}
}
seq.end()
@ -373,6 +477,28 @@ mod governance_packet_impl {
Action::ContractUpgrade { new_contract }
}
3 => {
let ModifyBalance {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
} = seq
.next_element()?
.ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
Action::ModifyBalance {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
}
}
v => {
return Err(Error::custom(format_args!(
"invalid value: {v}, expected one of 1, 2"
@ -450,6 +576,26 @@ mod governance_packet_impl {
Action::ContractUpgrade { new_contract }
}
3 => {
let ModifyBalance {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
} = map.next_value()?;
Action::ModifyBalance {
sequence,
chain_id,
token_chain,
token_address,
kind,
amount,
reason,
}
}
v => {
return Err(Error::custom(format_args!(
"invalid action: {v}, expected one of: 1, 2"