switch terra contracts over to using governance packet structure

Change-Id: I10b53cb0cfdb86ca2aa57cab20ec375a26fd252e
This commit is contained in:
Alwin 2021-06-01 17:10:34 -04:00
parent 08ca466a40
commit 2025efd50a
7 changed files with 287 additions and 150 deletions

View File

@ -8,7 +8,7 @@ use crate::msg::{HandleMsg, InitMsg, QueryMsg};
use crate::state::{
bridge_contracts, bridge_contracts_read, config, config_read, wrapped_asset,
wrapped_asset_address, wrapped_asset_address_read, wrapped_asset_read, Action, AssetMeta,
ConfigInfo, TokenBridgeMessage, TransferInfo,
ConfigInfo, RegisterChain, TokenBridgeMessage, TransferInfo,
};
use wormhole::byte_utils::ByteUtils;
use wormhole::byte_utils::{extend_address_to_32, extend_string_to_32};
@ -20,7 +20,7 @@ use cw20_base::msg::QueryMsg as TokenQuery;
use wormhole::msg::HandleMsg as WormholeHandleMsg;
use wormhole::msg::QueryMsg as WormholeQueryMsg;
use wormhole::state::ParsedVAA;
use wormhole::state::{GovernancePacket, ParsedVAA};
use cw20::TokenInfoResponse;
@ -43,7 +43,8 @@ pub fn init<S: Storage, A: Api, Q: Querier>(
) -> StdResult<InitResponse> {
// Save general wormhole info
let state = ConfigInfo {
owner: msg.owner,
gov_chain: msg.gov_chain,
gov_address: msg.gov_address.as_slice().to_vec(),
wormhole_contract: msg.wormhole_contract,
wrapped_asset_code_id: msg.wrapped_asset_code_id,
};
@ -93,10 +94,6 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
nonce,
),
HandleMsg::SubmitVaa { data } => submit_vaa(deps, env, &data),
HandleMsg::RegisterChain {
chain_id,
chain_address,
} => handle_register_chain(deps, env, chain_id, chain_address.as_slice().to_vec()),
HandleMsg::CreateAssetMeta {
asset_address,
nonce,
@ -104,38 +101,6 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
}
}
fn handle_register_chain<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
chain_id: u16,
chain_address: Vec<u8>,
) -> StdResult<HandleResponse> {
let cfg = config_read(&deps.storage).load()?;
if env.message.sender != cfg.owner {
return Err(StdError::unauthorized());
}
let existing = bridge_contracts_read(&deps.storage).load(&chain_id.to_be_bytes());
if existing.is_ok() {
return Err(StdError::generic_err(
"bridge contract already exists for this chain",
));
}
let mut bucket = bridge_contracts(&mut deps.storage);
bucket.save(&chain_id.to_be_bytes(), &chain_address)?;
Ok(HandleResponse {
messages: vec![],
log: vec![
log("chain_id", chain_id),
log("chain_address", hex::encode(chain_address)),
],
data: None,
})
}
/// Handle wrapped asset registration messages
fn handle_register_asset<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
@ -269,9 +234,16 @@ fn submit_vaa<S: Storage, A: Api, Q: Querier>(
env: Env,
data: &Binary,
) -> StdResult<HandleResponse> {
let state = config_read(&deps.storage).load()?;
let vaa = parse_vaa(deps, env.block.time, data)?;
let data = vaa.payload;
// check if vaa is from governance
if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
return handle_governance_payload(deps, env, &data);
}
let message = TokenBridgeMessage::deserialize(&data)?;
let result = match message.action {
@ -288,6 +260,56 @@ fn submit_vaa<S: Storage, A: Api, Q: Querier>(
return result;
}
fn handle_governance_payload<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
let gov_packet = GovernancePacket::deserialize(&data)?;
let module = String::from_utf8(gov_packet.module).unwrap();
let module: String = module.chars().filter(|c| !c.is_whitespace()).collect();
if module != "token_bridge" {
return Err(StdError::generic_err("this is not a valid module"))
}
match gov_packet.action {
0u8 => handle_register_chain(deps, env, &gov_packet.payload),
_ => ContractError::InvalidVAAAction.std_err(),
}
}
fn handle_register_chain<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
let RegisterChain {
chain_id,
chain_address,
} = RegisterChain::deserialize(&data)?;
let existing = bridge_contracts_read(&deps.storage).load(&chain_id.to_be_bytes());
if existing.is_ok() {
return Err(StdError::generic_err(
"bridge contract already exists for this chain",
));
}
let mut bucket = bridge_contracts(&mut deps.storage);
bucket.save(&chain_id.to_be_bytes(), &chain_address)?;
Ok(HandleResponse {
messages: vec![],
log: vec![
log("chain_id", chain_id),
log("chain_address", hex::encode(chain_address)),
],
data: None,
})
}
fn handle_complete_transfer<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,

View File

@ -4,7 +4,11 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
pub owner: HumanAddr,
// governance contract details
pub gov_chain: u16,
pub gov_address: Binary,
pub wormhole_contract: HumanAddr,
pub wrapped_asset_code_id: u64,
}
@ -30,11 +34,6 @@ pub enum HandleMsg {
data: Binary,
},
RegisterChain {
chain_id: u16,
chain_address: Binary,
},
CreateAssetMeta {
asset_address: HumanAddr,
nonce: u32,

View File

@ -1,7 +1,7 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{HumanAddr, StdResult, Storage};
use cosmwasm_std::{HumanAddr, StdResult, Storage, Binary};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
@ -18,8 +18,10 @@ pub static BRIDGE_CONTRACTS: &[u8] = b"bridge_contracts";
// Guardian set information
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ConfigInfo {
// Current active guardian set
pub owner: HumanAddr,
// governance contract details
pub gov_chain: u16,
pub gov_address: Vec<u8>,
pub wormhole_contract: HumanAddr,
pub wrapped_asset_code_id: u64,
}
@ -184,3 +186,23 @@ impl AssetMeta {
.concat()
}
}
pub struct RegisterChain {
pub chain_id: u16,
pub chain_address: Vec<u8>,
}
impl RegisterChain {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let chain_id = data.get_u16(0);
let chain_address = data[2..].to_vec();
Ok(RegisterChain {
chain_id,
chain_address
})
}
}

View File

@ -1,17 +1,17 @@
use cosmwasm_std::{
has_coins, log, to_binary, Api, BankMsg, Binary, Coin, CosmosMsg, Env, Extern, HandleResponse,
HumanAddr, InitResponse, Querier, StdError, StdResult, Storage,
WasmMsg
};
use crate::byte_utils::extend_address_to_32;
use crate::byte_utils::ByteUtils;
use crate::error::ContractError;
use crate::msg::{
GetAddressHexResponse, GetStateResponse, GuardianSetInfoResponse, HandleMsg, InitMsg, QueryMsg,
};
use crate::msg::{GetAddressHexResponse, GetStateResponse, GuardianSetInfoResponse, HandleMsg, InitMsg, QueryMsg};
use crate::state::{
config, config_read, guardian_set_get, guardian_set_set, vaa_archive_add, vaa_archive_check,
ConfigInfo, GuardianAddress, GuardianSetInfo, ParsedVAA, WormholeGovernance,
ConfigInfo, GovernancePacket, GuardianAddress, GuardianSetInfo, GuardianSetUpgrade, ParsedVAA,
TransferFee,
};
use k256::ecdsa::recoverable::Id as RecoverableId;
@ -22,7 +22,6 @@ use k256::EncodedPoint;
use sha3::{Digest, Keccak256};
use generic_array::GenericArray;
use std::convert::TryFrom;
// Chain ID of Terra
@ -39,9 +38,10 @@ pub fn init<S: Storage, A: Api, Q: Querier>(
) -> StdResult<InitResponse> {
// Save general wormhole info
let state = ConfigInfo {
gov_chain: msg.gov_chain,
gov_address: msg.gov_address.as_slice().to_vec(),
guardian_set_index: 0,
guardian_set_expirity: msg.guardian_set_expirity,
owner: deps.api.canonical_address(&env.message.sender)?,
fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION), // 0.01 Luna (or 10000 uluna) fee by default
};
config(&mut deps.storage).save(&state)?;
@ -65,10 +65,6 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
HandleMsg::PostMessage { message, nonce } => {
handle_post_message(deps, env, &message.as_slice(), nonce)
}
// HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, &vaa.as_slice()),
HandleMsg::TransferFee { amount, recipient } => {
handle_transfer_fee(deps, env, amount, recipient)
}
HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, vaa.as_slice()),
}
}
@ -82,30 +78,33 @@ fn handle_submit_vaa<S: Storage, A: Api, Q: Querier>(
let state = config_read(&deps.storage).load()?;
let vaa = parse_and_verify_vaa(&deps.storage, data, env.block.time)?;
if vaa.emitter_chain != 0u16 {
// chain 0 is the wormhole chain ?
return Err(StdError::generic_err(
"governance actions may only come from chain 0",
));
if state.gov_chain == vaa.emitter_chain && state.gov_address == vaa.emitter_address {
return handle_governance_payload(deps, env, &vaa.payload);
}
let gov = WormholeGovernance::deserialize(&vaa.payload)?;
ContractError::InvalidVAAAction.std_err()
}
let result = match gov.action {
0u8 => {
if vaa.guardian_set_index != state.guardian_set_index {
return ContractError::NotCurrentGuardianSet.std_err();
}
vaa_update_guardian_set(deps, env, gov.payload.as_slice())
}
fn handle_governance_payload<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
let gov_packet = GovernancePacket::deserialize(&data)?;
let module = String::from_utf8(gov_packet.module).unwrap();
let module: String = module.chars().filter(|c| !c.is_whitespace()).collect();
if module != "core" {
return Err(StdError::generic_err("this is not a valid module"))
}
match gov_packet.action {
// 0 is reserved for upgrade / migration
1u8 => vaa_update_guardian_set(deps, env, &gov_packet.payload),
2u8 => handle_transfer_fee(deps, env, &gov_packet.payload),
_ => ContractError::InvalidVAAAction.std_err(),
};
if result.is_ok() {
vaa_archive_add(&mut deps.storage, vaa.hash.as_slice())?;
}
result
}
/// Parses raw VAA data into a struct and verifies whether it contains sufficient signatures of an
@ -182,7 +181,7 @@ fn parse_and_verify_vaa<S: Storage>(
fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &[u8],
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
/* Payload format
0 uint32 new_index
@ -190,41 +189,19 @@ fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
5 [][20]uint8 guardian addresses
*/
const GUARDIAN_INDEX_POS: usize = 0;
const LENGTH_POS: usize = 4;
const ADDRESS_POS: usize = 5;
const ADDRESS_LEN: usize = 20;
if ADDRESS_POS >= data.len() {
return ContractError::InvalidVAA.std_err();
}
let mut state = config_read(&deps.storage).load()?;
let new_guardian_set_index = data.get_u32(GUARDIAN_INDEX_POS);
let GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set,
} = GuardianSetUpgrade::deserialize(&data)?;
if new_guardian_set_index != state.guardian_set_index + 1 {
return ContractError::GuardianSetIndexIncreaseError.std_err();
}
let len = data.get_u8(LENGTH_POS);
let mut new_guardian_set = GuardianSetInfo {
addresses: vec![],
expiration_time: 0,
};
let mut pos = ADDRESS_POS;
for _ in 0..len {
if pos + ADDRESS_LEN > data.len() {
return ContractError::InvalidVAA.std_err();
}
new_guardian_set.addresses.push(GuardianAddress {
bytes: data[pos..pos + ADDRESS_LEN].to_vec().into(),
});
pos += ADDRESS_LEN;
}
let old_guardian_set_index = state.guardian_set_index;
state.guardian_set_index = new_guardian_set_index;
guardian_set_set(
@ -232,6 +209,7 @@ fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
state.guardian_set_index,
&new_guardian_set,
)?;
config(&mut deps.storage).save(&state)?;
let mut old_guardian_set = guardian_set_get(&deps.storage, old_guardian_set_index)?;
@ -249,6 +227,24 @@ fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
})
}
pub fn handle_transfer_fee<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
data: &Vec<u8>,
) -> StdResult<HandleResponse> {
let transfer_msg = TransferFee::deserialize(&data)?;
Ok(HandleResponse {
messages: vec![CosmosMsg::Bank(BankMsg::Send {
from_address: env.contract.address,
to_address: deps.api.human_address(&transfer_msg.recipient)?,
amount: vec![transfer_msg.amount],
})],
log: vec![],
data: None,
})
}
fn handle_post_message<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
@ -280,29 +276,6 @@ fn handle_post_message<S: Storage, A: Api, Q: Querier>(
})
}
pub fn handle_transfer_fee<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
amount: Coin,
recipient: HumanAddr,
) -> StdResult<HandleResponse> {
let state = config_read(&deps.storage).load()?;
if deps.api.canonical_address(&env.message.sender)? != state.owner {
return ContractError::PermissionDenied.std_err();
}
Ok(HandleResponse {
messages: vec![CosmosMsg::Bank(BankMsg::Send {
from_address: env.contract.address,
to_address: recipient,
amount: vec![amount],
})],
log: vec![],
data: None,
})
}
pub fn query<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
msg: QueryMsg,

View File

@ -6,6 +6,9 @@ use crate::state::{GuardianAddress, GuardianSetInfo};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
pub gov_chain: u16,
pub gov_address: Binary,
pub initial_guardian_set: GuardianSetInfo,
pub guardian_set_expirity: u64,
}
@ -20,10 +23,6 @@ pub enum HandleMsg {
message: Binary,
nonce: u32
},
TransferFee {
amount: Coin,
recipient: HumanAddr,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]

View File

@ -1,7 +1,7 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{Binary, CanonicalAddr, HumanAddr, StdResult, Storage, Coin};
use cosmwasm_std::{Binary, CanonicalAddr, HumanAddr, StdResult, Storage, Coin, Uint128};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
@ -26,8 +26,9 @@ pub struct ConfigInfo {
// Period for which a guardian set stays active after it has been replaced
pub guardian_set_expirity: u64,
// Contract owner address, it can make contract active/inactive
pub owner: CanonicalAddr,
// governance contract details
pub gov_chain: u16,
pub gov_address: Vec<u8>,
// Asset locking fee
pub fee: Coin,
@ -219,20 +220,96 @@ pub fn wrapped_asset_address_read<S: Storage>(storage: &S) -> ReadonlyBucket<S,
}
pub struct WormholeGovernance {
pub struct GovernancePacket {
pub module: Vec<u8>,
pub chain: u16,
pub action: u8,
pub payload: Vec<u8>,
}
impl WormholeGovernance {
impl GovernancePacket {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let action = data.get_u8(0);
let payload = &data[1..];
let module = data.get_bytes32(0).to_vec();
let chain = data.get_u16(32);
let action = data.get_u8(34);
let payload = data[35..].to_vec();
Ok(WormholeGovernance {
Ok(GovernancePacket {
module,
chain,
action,
payload: payload.to_vec(),
payload
})
}
}
// action 1
pub struct GuardianSetUpgrade {
pub new_guardian_set_index: u32,
pub new_guardian_set: GuardianSetInfo,
}
impl GuardianSetUpgrade {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
const ADDRESS_LEN: usize = 20;
let data = data.as_slice();
let new_guardian_set_index = data.get_u32(0);
let n_guardians = data.get_u8(4);
let mut addresses = vec![];
for i in 0..n_guardians {
let pos = 5 + (i as usize) * ADDRESS_LEN;
if pos + ADDRESS_LEN > data.len() {
return ContractError::InvalidVAA.std_err();
}
addresses.push(GuardianAddress {
bytes: data[pos..pos + ADDRESS_LEN].to_vec().into(),
});
}
let new_guardian_set = GuardianSetInfo {
addresses,
expiration_time: 0
};
return Ok(
GuardianSetUpgrade {
new_guardian_set_index,
new_guardian_set
}
)
}
}
// action 2
pub struct TransferFee {
pub amount: Coin,
pub recipient: CanonicalAddr,
}
impl TransferFee {
pub fn deserialize(data: &Vec<u8>) -> StdResult<Self> {
let data = data.as_slice();
let recipient = data.get_address(0);
let amount = Uint128(data.get_u128_be(32));
let denom = match String::from_utf8(data[48..].to_vec()) {
Ok(s) => s,
Err(_) => return ContractError::InvalidVAA.std_err()
};
let amount = Coin {
denom,
amount,
};
Ok(TransferFee {
amount,
recipient
})
}
}

View File

@ -5,6 +5,7 @@ from terra_sdk.core.wasm import (
MsgStoreCode,
MsgInstantiateContract,
MsgExecuteContract,
MsgMigrateContract,
)
from terra_sdk.util.contract import get_code_id, get_contract_address, read_file_as_b64
import os
@ -74,9 +75,11 @@ class ContractQuerier:
class Contract:
@staticmethod
async def create(code_id, **kwargs):
async def create(code_id, migratable=False, **kwargs):
kwargs = convert_contracts_to_addr(kwargs)
instantiate = MsgInstantiateContract(deployer.key.acc_address, code_id, kwargs)
instantiate = MsgInstantiateContract(
deployer.key.acc_address, code_id, kwargs, migratable=migratable
)
result = await sign_and_broadcast(instantiate)
return Contract(get_contract_address(result))
@ -97,6 +100,15 @@ class Contract:
def query(self):
return ContractQuerier(self.address)
async def migrate(self, new_code_id):
migrate = MsgMigrateContract(
contract=self.address,
migrate_msg={},
new_code_id=new_code_id,
owner=deployer.key.acc_address,
)
return await sign_and_broadcast(migrate)
def convert_contracts_to_addr(obj):
if type(obj) == dict:
@ -128,15 +140,29 @@ def assemble_vaa(emitter_chain, emitter_address, payload):
async def main():
code_ids = await store_contracts()
print(code_ids)
# fake governance contract on solana
GOV_CHAIN = 1
GOV_ADDRESS = b"0" * 32
wormhole = await Contract.create(
code_id=code_ids["wormhole"],
gov_chain=GOV_CHAIN,
gov_address=base64.b64encode(GOV_ADDRESS).decode("utf-8"),
guardian_set_expirity=10 ** 15,
initial_guardian_set={"addresses": [], "expiration_time": 10 ** 15},
migratable=True,
)
# TODO:
# resp = await wormhole.migrate(code_ids["wormhole"])
# for event in resp.logs:
# pprint.pprint(event.events_by_type)
token_bridge = await Contract.create(
code_id=code_ids["token_bridge"],
owner=deployer.key.acc_address,
gov_chain=GOV_CHAIN,
gov_address=base64.b64encode(GOV_ADDRESS).decode("utf-8"),
wormhole_contract=wormhole,
wrapped_asset_code_id=int(code_ids["cw20_wrapped"]),
)
@ -163,9 +189,19 @@ async def main():
bridge_canonical = bytes.fromhex(
(await wormhole.query.query_address_hex(address=token_bridge))["hex"]
)
await token_bridge.register_chain(
chain_id=3, chain_address=base64.b64encode(bridge_canonical).decode("utf-8")
)
# fake a VAA from the gov contract
module = b"token_bridge"
module += b" " * (32 - len(module))
chain = to_bytes(0, 2)
action = to_bytes(0, 1)
# chain_id chain_address (pretend there's a bridge w/ the same address on solana)
payload = to_bytes(3, 2) + bridge_canonical
vaa = assemble_vaa(GOV_CHAIN, GOV_ADDRESS, module + chain + action + payload)
# register the chain
await token_bridge.submit_vaa(data=base64.b64encode(vaa).decode("utf-8"))
resp = await token_bridge.initiate_transfer(
asset=mock_token,
@ -199,9 +235,18 @@ async def main():
)
# pretend there exists another bridge contract with the same address but on solana
await token_bridge.register_chain(
chain_id=1, chain_address=base64.b64encode(bridge_canonical).decode("utf-8")
)
# fake a VAA from the gov contract
module = b"token_bridge"
module += b" " * (32 - len(module))
chain = to_bytes(0, 2)
action = to_bytes(0, 1)
# chain_id chain_address (pretend there's a bridge w/ the same address on solana)
payload = to_bytes(1, 2) + bridge_canonical
vaa = assemble_vaa(GOV_CHAIN, GOV_ADDRESS, module + chain + action + payload)
# register the chain
await token_bridge.submit_vaa(data=base64.b64encode(vaa).decode("utf-8"))
resp = await token_bridge.create_asset_meta(
asset_address=mock_token,