New tests and fixes in Terra Wormhole contracts

- added new descriptive errors in terra wormhole contract
- 0-expiration guardian sets error fixed
- added script for test VAAs generation
- added full test coverage for the VAA processing in the contract
- fixed cargo clippy/fmt warnings
This commit is contained in:
Yuriy Savchenko 2020-12-22 19:03:33 +02:00 committed by Leopold Schabel
parent eaee9c0638
commit 701154457c
11 changed files with 1070 additions and 475 deletions

View File

@ -0,0 +1,335 @@
// vaa-test generates VAA test fixtures used by the ETH devnet tests
package main
import (
"crypto/ecdsa"
"encoding/hex"
"fmt"
"math/big"
"math/rand"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/certusone/wormhole/bridge/pkg/vaa"
)
type signerInfo struct {
signer *ecdsa.PrivateKey
index int
}
var i = 0
var defaultTargetAddress = vaa.Address{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
func main() {
keys := generateKeys(6)
for i, key := range keys {
fmt.Printf("Key [%d]: %s\n", i, crypto.PubkeyToAddress(key.PublicKey).String())
}
// 0 - Valid transfer, single signer
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 1,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[0], 0}})
// 1 - 2 signers, invalid order
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 1,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDEthereum,
Address: hexToAddress("0xd833215cbcc3f914bd1c9ece3ee7bf8b14f841bb"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[1], 1}, {keys[0], 0}})
// 2 - Valid transfer, 2 signers
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 1,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDEthereum,
Address: hexToAddress("0xd833215cbcc3f914bd1c9ece3ee7bf8b14f841bb"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[0], 0}, {keys[1], 1}})
// 3 - Valid transfer, 3 signers
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 1,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDEthereum,
Address: hexToAddress("0xd833215cbcc3f914bd1c9ece3ee7bf8b14f841bb"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[0], 0}, {keys[1], 1}, {keys[2], 2}})
// 4 - Invalid signature, single signer
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 1,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[1], 0}})
// 5 - Valid guardian set change
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyGuardianSetUpdate{
Keys: []common.Address{
crypto.PubkeyToAddress(keys[1].PublicKey),
},
NewIndex: 1,
},
}, []*signerInfo{{keys[0], 0}})
// 6 - Change from set 0 to set 1, single guardian with key#2 (zero-based)
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyGuardianSetUpdate{
Keys: []common.Address{
crypto.PubkeyToAddress(keys[2].PublicKey),
},
NewIndex: 1,
},
}, []*signerInfo{{keys[0], 0}})
// 7 - Guardian set index jump
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyGuardianSetUpdate{
Keys: []common.Address{
crypto.PubkeyToAddress(keys[2].PublicKey),
},
NewIndex: 2,
},
}, []*signerInfo{{keys[0], 0}})
// 8 - Invalid target address format
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 1,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[0], 0}})
// 9 - Amount too high (max u128 + 1)
amount, ok := new(big.Int).SetString("100000000000000000000000000000000", 16)
if !ok {
panic("Cannot convert big amount")
}
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 1,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: amount,
},
}, []*signerInfo{{keys[0], 0}})
// 10 - Same source and target
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(1000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 3,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[0], 0}})
// 11 - Wrong target chain
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(1000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 56,
SourceChain: 1,
TargetChain: 2,
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[0], 0}})
// 12 - Change guardian set to 6 addresses
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 0,
Timestamp: time.Unix(4000, 0),
Payload: &vaa.BodyGuardianSetUpdate{
Keys: []common.Address{
crypto.PubkeyToAddress(keys[0].PublicKey),
crypto.PubkeyToAddress(keys[1].PublicKey),
crypto.PubkeyToAddress(keys[2].PublicKey),
crypto.PubkeyToAddress(keys[3].PublicKey),
crypto.PubkeyToAddress(keys[4].PublicKey),
crypto.PubkeyToAddress(keys[5].PublicKey),
},
NewIndex: 1,
},
}, []*signerInfo{{keys[0], 0}})
// 13 - Valid transfer, partial quorum
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 1,
Timestamp: time.Unix(4000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 57,
SourceChain: 1,
TargetChain: 3,
SourceAddress: vaa.Address{2, 1, 5},
TargetAddress: defaultTargetAddress,
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[0], 0}, {keys[1], 1}, {keys[3], 3}, {keys[4], 4}, {keys[5], 5}})
}
func signAndPrintVAA(vaa *vaa.VAA, signers []*signerInfo) {
for _, signer := range signers {
vaa.AddSignature(signer.signer, uint8(signer.index))
}
vData, err := vaa.Marshal()
if err != nil {
panic(err)
}
println(i, hex.EncodeToString(vData))
i++
}
func generateKeys(n int) (keys []*ecdsa.PrivateKey) {
r := rand.New(rand.NewSource(555))
for i := 0; i < n; i++ {
key, err := ecdsa.GenerateKey(crypto.S256(), r)
if err != nil {
panic(err)
}
keys = append(keys, key)
}
return
}
func hexToAddress(hex string) vaa.Address {
hexAddr := common.HexToAddress(hex)
return padAddress(hexAddr)
}
func padAddress(address common.Address) vaa.Address {
paddedAddress := common.LeftPadBytes(address[:], 32)
addr := vaa.Address{}
copy(addr[:], paddedAddress)
return addr
}

849
terra/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -119,7 +119,7 @@ fn handle_mint_wrapped<S: Storage, A: Api, Q: Querier>(
return Err(StdError::unauthorized());
}
return Ok(handle_mint(deps, env, recipient, amount)?);
Ok(handle_mint(deps, env, recipient, amount)?)
}
pub fn query<S: Storage, A: Api, Q: Querier>(

View File

@ -1,3 +1,4 @@
#![allow(clippy::field_reassign_with_default)]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

View File

@ -25,6 +25,7 @@ thiserror = { version = "1.0.20" }
k256 = { version = "0.5.9", default-features = false, features = ["ecdsa"] }
sha3 = { version = "0.9.1", default-features = false }
hex = "0.4.2"
lazy_static = "1.4.0"
[dev-dependencies]
cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] }

View File

@ -18,8 +18,6 @@ use cw20_base::msg::QueryMsg as TokenQuery;
use cw20::TokenInfoResponse;
use hex;
use cw20_wrapped::msg::HandleMsg as WrappedMsg;
use cw20_wrapped::msg::InitMsg as WrappedInit;
use cw20_wrapped::msg::QueryMsg as WrappedQuery;
@ -69,14 +67,24 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
) -> StdResult<HandleResponse> {
match msg {
HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, &vaa.as_slice()),
HandleMsg::RegisterAssetHook { asset_id } => handle_register_asset(deps, env, &asset_id.as_slice()),
HandleMsg::RegisterAssetHook { asset_id } => {
handle_register_asset(deps, env, &asset_id.as_slice())
}
HandleMsg::LockAssets {
asset,
recipient,
amount,
target_chain,
nonce,
} => handle_lock_assets(deps, env, asset, amount, recipient.as_slice(), target_chain, nonce),
} => handle_lock_assets(
deps,
env,
asset,
amount,
recipient.as_slice(),
target_chain,
nonce,
),
HandleMsg::SetActive { is_active } => handle_set_active(deps, env, is_active),
}
}
@ -87,7 +95,6 @@ fn handle_submit_vaa<S: Storage, A: Api, Q: Querier>(
env: Env,
data: &[u8],
) -> StdResult<HandleResponse> {
let state = config_read(&deps.storage).load()?;
if !state.is_active {
return ContractError::ContractInactive.std_err();
@ -132,13 +139,13 @@ fn handle_submit_vaa<S: Storage, A: Api, Q: Querier>(
if vaa_archive_check(&deps.storage, &hash) {
return ContractError::VaaAlreadyExecuted.std_err();
}
// Load and check guardian set
let guardian_set = guardian_set_get(&deps.storage, vaa_guardian_set_index);
let guardian_set: GuardianSetInfo =
guardian_set.or(ContractError::InvalidGuardianSetIndex.std_err())?;
guardian_set.or_else(|_| ContractError::InvalidGuardianSetIndex.std_err())?;
if guardian_set.expiration_time == 0 || guardian_set.expiration_time < env.block.time {
if guardian_set.expiration_time != 0 && guardian_set.expiration_time < env.block.time {
return ContractError::GuardianSetExpired.std_err();
}
if len_signers < guardian_set.quorum() {
@ -151,21 +158,26 @@ fn handle_submit_vaa<S: Storage, A: Api, Q: Querier>(
for _ in 0..len_signers {
let index = data.get_u8(pos) as i32;
if index <= last_index {
return Err(ContractError::WrongGuardianIndexOrder.std());
return ContractError::WrongGuardianIndexOrder.std_err();
}
last_index = index;
let signature = Signature::try_from(&data[pos + 1..pos + 1 + 64])
.or(ContractError::CannotDecodeSignature.std_err())?;
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let id = RecoverableId::new(data.get_u8(pos + 1 + 64))
.or(ContractError::CannotDecodeSignature.std_err())?;
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let recoverable_signature = RecoverableSignature::new(&signature, id)
.or(ContractError::CannotDecodeSignature.std_err())?;
.or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
let verify_key = recoverable_signature
.recover_verify_key_from_digest_bytes(&hash)
.or(ContractError::CannotRecoverKey.std_err())?;
if !keys_equal(&verify_key, &guardian_set.addresses[index as usize]) {
.or_else(|_| ContractError::CannotRecoverKey.std_err())?;
let index = index as usize;
if index >= guardian_set.addresses.len() {
return ContractError::TooManySignatures.std_err();
}
if !keys_equal(&verify_key, &guardian_set.addresses[index]) {
return ContractError::GuardianSignatureError.std_err();
}
pos += SIGNATURE_LEN;
@ -204,7 +216,7 @@ fn handle_register_asset<S: Storage, A: Api, Q: Querier>(
match result {
Ok(_) => {
// Asset already registered, return error
return ContractError::AssetAlreadyRegistered.std_err();
ContractError::AssetAlreadyRegistered.std_err()
}
Err(_) => {
bucket.save(asset_id, &env.message.sender)?;
@ -343,7 +355,10 @@ fn vaa_transfer<S: Storage, A: Api, Q: Querier>(
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg: to_binary(&WrappedMsg::Mint {
recipient: deps.api.human_address(&target_address)?,
recipient: deps
.api
.human_address(&target_address)
.or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?,
amount: Uint128::from(amount),
})?,
send: vec![],
@ -359,7 +374,10 @@ fn vaa_transfer<S: Storage, A: Api, Q: Querier>(
asset_address: asset_address.to_vec().into(),
decimals: data.get_u8(103),
mint: Some(InitMint {
recipient: deps.api.human_address(&target_address)?,
recipient: deps
.api
.human_address(&target_address)
.or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?,
amount: Uint128::from(amount),
}),
init_hook: Some(InitHook {
@ -443,7 +461,7 @@ fn handle_lock_assets<S: Storage, A: Api, Q: Querier>(
contract_addr: asset.clone(),
msg: to_binary(&WrappedMsg::Burn {
account: env.message.sender.clone(),
amount: Uint128::from(amount),
amount,
})?,
send: vec![],
}));
@ -459,11 +477,11 @@ fn handle_lock_assets<S: Storage, A: Api, Q: Querier>(
Err(_) => {
// This is a regular asset, transfer its balance
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: asset.clone(),
contract_addr: asset,
msg: to_binary(&TokenMsg::TransferFrom {
owner: env.message.sender.clone(),
recipient: env.contract.address.clone(),
amount: Uint128::from(amount),
amount,
})?,
send: vec![],
}));
@ -479,7 +497,12 @@ fn handle_lock_assets<S: Storage, A: Api, Q: Querier>(
log("locked.token_chain", asset_chain),
log("locked.token_decimals", decimals),
log("locked.token", hex::encode(asset_address)),
log("locked.sender", hex::encode(extend_address_to_32(&deps.api.canonical_address(&env.message.sender)?))),
log(
"locked.sender",
hex::encode(extend_address_to_32(
&deps.api.canonical_address(&env.message.sender)?,
)),
),
log("locked.recipient", hex::encode(recipient)),
log("locked.amount", amount),
log("locked.nonce", nonce),
@ -559,36 +582,77 @@ fn keys_equal(a: &VerifyKey, b: &GuardianAddress) -> bool {
#[cfg(test)]
mod tests {
use std::time::{UNIX_EPOCH, SystemTime};
use super::*;
use crate::state::GuardianSetInfo;
use cosmwasm_std::testing::{mock_dependencies, mock_env};
use cosmwasm_std::HumanAddr;
use std::time::{SystemTime, UNIX_EPOCH};
// Constants generated by bridge/cmd/vaa-test-terra/main.go
const ADDR_1: &str = "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe";
const ADDR_2: &str = "8575Df9b3c97B4E267Deb92d93137844A97A0132";
const VAA_VALID_TRANSFER: &str = "010000000001001063f503dd308134e0f158537f54c5799719f4fa2687dd276c72ef60ae0c82c47d4fb560545afaabdf60c15918e221763fd1892c75f2098c0ffd5db4af254a4501000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101000000000000000000000000010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_VALID_GUARDIAN_SET_CHANGE: &str = "01000000000100d90d6f9cbc0458599cbe4d267bc9221b54955b94cb5cb338aeb845bdc9dd275f558871ea479de9cc0b44cfb2a07344431a3adbd2f98aa86f4e12ff4aba061b7f00000007d00100000001018575df9b3c97b4e267deb92d93137844a97a0132";
const ADDR_2: &str = "E06A9ADfeB38a8eE4D00E89307C016D0749679bD";
const ADDR_3: &str = "8575Df9b3c97B4E267Deb92d93137844A97A0132";
const ADDR_4: &str = "0427cDA59902Dc6EB0c1bd2b6D38F87c5552b348";
const ADDR_5: &str = "bFEa822F75c42e1764c791B8fE04a7B10DDB3857";
const ADDR_6: &str = "2F5FE0B158147e7260f14062556AfC94Eece55fF";
const VAA_VALID_TRANSFER_1_SIG: &str = "01000000000100d106d4f363c6e3d0bf8ebf3cf8ef1ba35e66687b7613a826b5f5b68e0c346e1e0fdd6ceb332c87dad7d170ee6736571c0b75173787a8dcf41a492075e18a9a9601000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_VALID_TRANSFER_2_SIGS: &str = "0100000000020040d91705d211c52c9f120adb1b794355ba10ec1ff855295e677c5b341b2e5449684179f8ca4087e88de2cba0e6cbf6e0c7a353529800ccf96e5fdd80a85a59220001efb8a4825c87ab68190e1b184eeda5c45f82b22450ff113f2581a2f1bd3aeca60798392405cd4d3b523a5c3426d09b963c195c842a0040e93651cb700785d0e600000007d0100000003801030201040000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040506070809000102030405060708090002000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_VALID_TRANSFER_3_SIGS: &str = "0100000000030040d91705d211c52c9f120adb1b794355ba10ec1ff855295e677c5b341b2e5449684179f8ca4087e88de2cba0e6cbf6e0c7a353529800ccf96e5fdd80a85a59220001efb8a4825c87ab68190e1b184eeda5c45f82b22450ff113f2581a2f1bd3aeca60798392405cd4d3b523a5c3426d09b963c195c842a0040e93651cb700785d0e60002a5fb92ff2b5a5eed98e2909ed932e5d9328cb2527027cce8f40c4f5677c341c83fe9fac7bf39af60fe47ecfb6f52b22b9d817d24d4147684b08e2fe19ff3a3ef01000007d0100000003801030201040000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040506070809000102030405060708090002000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0: &str = "01000000000100a33c022217ccb87a5bc83b71e6377fff6639e7904d9e9995a42dc0867dc2b0bc5d1aacc3752ea71cf4d85278526b5dd40b0343667a2d4434a44cbf7844181a1000000007d0010000000101e06a9adfeb38a8ee4d00e89307c016d0749679bd";
const VAA_ERROR_SIGNATURE_SEQUENCE: &str = "01000000000201efb8a4825c87ab68190e1b184eeda5c45f82b22450ff113f2581a2f1bd3aeca60798392405cd4d3b523a5c3426d09b963c195c842a0040e93651cb700785d0e6000040d91705d211c52c9f120adb1b794355ba10ec1ff855295e677c5b341b2e5449684179f8ca4087e88de2cba0e6cbf6e0c7a353529800ccf96e5fdd80a85a592200000007d0100000003801030201040000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040506070809000102030405060708090002000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_ERROR_WRONG_SIGNATURE_1_SIG: &str = "0100000000010075c1b20fb59adc55a08f9778bc525507a36a29d1f0e2cb3fcc9c90f7331786263c4bd53ce5d3865b4f63cddeafb2c1026b5e13f1b66af7dabbd1f1af9f34fd3f01000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0_DIFF: &str = "01000000000100d90d6f9cbc0458599cbe4d267bc9221b54955b94cb5cb338aeb845bdc9dd275f558871ea479de9cc0b44cfb2a07344431a3adbd2f98aa86f4e12ff4aba061b7f00000007d00100000001018575df9b3c97b4e267deb92d93137844a97a0132";
const VAA_ERROR_INVALID_TARGET_ADDRESS: &str = "0100000000010092f32c76aa3a8d83de59b3f2281cfbf70af33d9bcfbaa78bd3e9cafc512335ab40b126a894f0182ee8c69f5324496eb681c1780ed39bcc80f589cfc0a5df144a01000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_VALID_GUARDIAN_SET_CHANGE_JUMP: &str = "010000000001004b179853b36b76446c72944d50551be814ab34f23da2124615315da71505df801b38355d741cdd65e856792e2a1435270abfe52ae005c4e3671c0b7aac36445a01000007d00100000002018575df9b3c97b4e267deb92d93137844a97a0132";
const VAA_ERROR_AMOUNT_TOO_HIGH: &str = "0100000000010055fdf76a64b779ac5b7a54dc181cf430f4d14a499b7933049d8bc94db529ed0a2d12d50ec2026883e59a5c64f2189b60c84a53b66113e8b52da66fd89f70495f00000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000100000000000000000000000000000000";
const VAA_ERROR_SAME_SOURCE_AND_TARGET: &str = "010000000001004c53dfce8fc9e781f0cfdc6592c00c337c1e109168ff17ee3bf4cf69ddb8a0a52a3c215093301d5459d282d625dc5125592609f06f14a57f61121e668b0ec10500000003e81000000038030302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_ERROR_WRONG_TARGET: &str = "01000000000100b19a265b1407e9619ffc29be9562161ed2c155db5ba68e01265a250a677eb0c62bb91e468da827e9ec4c1e9428ade97129126f56500c4a3c9f9803cc85f656d200000003e81000000038010202010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const VAA_VALID_GUARDIAN_SET_CHANGE_TO_6: &str = "01000000000100a5defbd912ef327d07afff71e0da9c2e2a13e5516255c62e249a6761afe2465c7b6fc1032451559551e76eb4a029474fd791b2250c4fd40a8b3f5d4f5f58e5a30000000fa0010000000106befa429d57cd18b7f8a4d91a2da9ab4af05d0fbee06a9adfeb38a8ee4d00e89307c016d0749679bd8575df9b3c97b4e267deb92d93137844a97a01320427cda59902dc6eb0c1bd2b6d38f87c5552b348bfea822f75c42e1764c791b8fe04a7b10ddb38572f5fe0b158147e7260f14062556afc94eece55ff";
const VAA_VALID_TRANSFER_5_SIGS_GS_1: &str = "01000000010500027eb7e87a9d0ab91ec53bb073c0f0acf189900139daa652666fd4cfe32a4ee42383c1a66e3a397c2de8ae485225357feb52f665952b1e384ef6dfcea1ba9f920001cfcacfad444ac3202f8f0d2252c69ee90d18c9105f7be3b5d361b7fcb0fbf7fa7287bac5de9cb02f86a28fdd7f24015991020431b0048aa3bbb29daed625e416000372f6c239ddeccded04a95a0cf0bfefe6e168148f1fe3b93e797eb2e74e098b890f2be341dd0f3c8172c2050154407cfdd1ea7bd6cce0b31f020ec7530ffb6109000449c025fe0630268983d57c4bd1546497788f810e427b6fd436cb1f048152375e1063422b4d1cc668a0612814c550ea7e3d1aa93404a0b6e089d210d4c937023a000548bf474fb350d5e482378c37404fb4d1421e262d13ebf6b11977214c789a246a6c278a522a9be4beba008f3d481b1ee35c5b0559bef474eb34b9e3e681947c230100000fa01000000039010302010500000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
const CANONICAL_LENGTH: usize = 20;
fn do_init_default_guardians<S: Storage, A: Api, Q: Querier>(deps: &mut Extern<S, A, Q>) {
do_init(deps, &vec![GuardianAddress::from(ADDR_1)]);
lazy_static! {
static ref ALL_GUARDIANS: Vec<GuardianAddress> = vec![
GuardianAddress::from(ADDR_1),
GuardianAddress::from(ADDR_2),
GuardianAddress::from(ADDR_3),
GuardianAddress::from(ADDR_4),
GuardianAddress::from(ADDR_5),
GuardianAddress::from(ADDR_6)
];
}
fn unix_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn do_init_with_guardians<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
number_of_guardians: usize,
) {
let expiration_time = unix_timestamp() + 1000;
do_init(deps, &ALL_GUARDIANS[..number_of_guardians], expiration_time);
}
fn do_init<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
guardians: &Vec<GuardianAddress>,
guardians: &[GuardianAddress],
expiration_time: u64,
) {
let init_msg = InitMsg {
initial_guardian_set: GuardianSetInfo {
addresses: guardians.clone(),
expiration_time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 1000,
addresses: guardians.to_vec(),
expiration_time,
},
guardian_set_expirity: 50,
wrapped_asset_code_id: 999,
};
let env = mock_env(&HumanAddr::from("creator"), &[]);
let mut env = mock_env(&HumanAddr::from("creator"), &[]);
env.block.time = unix_timestamp();
let res = init(deps, env, init_msg).unwrap();
assert_eq!(0, res.messages.len());
@ -602,7 +666,8 @@ mod tests {
let msg = HandleMsg::SubmitVAA {
vaa: hex::decode(vaa).expect("Decoding failed").into(),
};
let env = mock_env(&HumanAddr::from("creator"), &[]);
let mut env = mock_env(&HumanAddr::from("creator"), &[]);
env.block.time = unix_timestamp();
handle(deps, env, msg)
}
@ -610,15 +675,17 @@ mod tests {
#[test]
fn can_init() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_default_guardians(&mut deps);
do_init_with_guardians(&mut deps, 1);
}
#[test]
fn valid_vaa_token_transfer() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_default_guardians(&mut deps);
do_init_with_guardians(&mut deps, 1);
let messages = submit_vaa(&mut deps, VAA_VALID_TRANSFER).unwrap().messages;
let messages = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG)
.unwrap()
.messages;
assert_eq!(1, messages.len());
let msg = &messages[0];
match msg {
@ -640,21 +707,39 @@ mod tests {
}
#[test]
fn same_vaa_twice_error() {
fn valid_vaa_2_signatures() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_default_guardians(&mut deps);
do_init_with_guardians(&mut deps, 2);
let _ = submit_vaa(&mut deps, VAA_VALID_TRANSFER).unwrap();
let e = submit_vaa(&mut deps, VAA_VALID_TRANSFER).unwrap_err();
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_2_SIGS);
assert!(result.is_ok());
}
#[test]
fn valid_vaa_non_expiring_guardians() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init(&mut deps, &vec![GuardianAddress::from(ADDR_1)], 0);
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG);
assert!(result.is_ok());
}
#[test]
fn error_vaa_same_vaa_twice() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let _ = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG).unwrap();
let e = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG).unwrap_err();
assert_eq!(e, ContractError::VaaAlreadyExecuted.std());
}
#[test]
fn valid_vaa_guardian_set_change() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_default_guardians(&mut deps);
do_init_with_guardians(&mut deps, 1);
let messages = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE)
let messages = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0)
.unwrap()
.messages;
assert_eq!(0, messages.len());
@ -674,4 +759,158 @@ mod tests {
}
);
}
#[test]
fn error_vaa_guardian_set_expired() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
// Expiration time 1 second in the past
let expiration_time = unix_timestamp() - 1;
do_init(
&mut deps,
&vec![GuardianAddress::from(ADDR_1)],
expiration_time,
);
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG);
assert_eq!(result, ContractError::GuardianSetExpired.std_err());
}
#[test]
fn error_vaa_no_quorum() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 2);
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG);
assert_eq!(result, ContractError::NoQuorum.std_err());
}
#[test]
fn valid_partial_quorum() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 4);
// 3 signatures on 4-guardian set is quorum
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_3_SIGS);
assert!(result.is_ok());
}
#[test]
fn error_vaa_wrong_guardian_index_order() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 2);
let result = submit_vaa(&mut deps, VAA_ERROR_SIGNATURE_SEQUENCE);
assert_eq!(result, ContractError::WrongGuardianIndexOrder.std_err());
}
#[test]
fn error_vaa_too_many_signatures() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_2_SIGS);
assert_eq!(result, ContractError::TooManySignatures.std_err());
}
#[test]
fn error_vaa_invalid_signature() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init(
&mut deps,
// Use 1-2-4 guardians
&vec![
GuardianAddress::from(ADDR_1),
GuardianAddress::from(ADDR_2),
GuardianAddress::from(ADDR_4),
],
unix_timestamp(),
);
// Sign by 1-2-3 guardians
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_3_SIGS);
assert_eq!(result, ContractError::GuardianSignatureError.std_err());
// Single signature, wrong key
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_ERROR_WRONG_SIGNATURE_1_SIG);
assert_eq!(result, ContractError::GuardianSignatureError.std_err());
}
#[test]
fn error_vaa_not_current_quardian_set() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0);
assert!(result.is_ok());
// Submit another valid change, which will fail, because now set #1 is active
// (we need to send a different VAA, because otherwise it will be blocked by duplicate check)
let result = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0_DIFF);
assert_eq!(result, ContractError::NotCurrentGuardianSet.std_err());
}
#[test]
fn error_vaa_wrong_target_address_format() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_ERROR_INVALID_TARGET_ADDRESS);
assert_eq!(result, ContractError::WrongTargetAddressFormat.std_err());
}
#[test]
fn error_vaa_guardian_set_change_index_not_increasing() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_JUMP);
assert_eq!(
result,
ContractError::GuardianSetIndexIncreaseError.std_err()
);
}
#[test]
fn error_vaa_transfer_amount_too_high() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_ERROR_AMOUNT_TOO_HIGH);
assert_eq!(result, ContractError::AmountTooHigh.std_err());
}
#[test]
fn error_vaa_transfer_same_source_and_target() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_ERROR_SAME_SOURCE_AND_TARGET);
assert_eq!(result, ContractError::SameSourceAndTarget.std_err());
}
#[test]
fn error_vaa_transfer_wrong_target_chain() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_ERROR_WRONG_TARGET);
assert_eq!(result, ContractError::WrongTargetChain.std_err());
}
#[test]
fn valid_transfer_after_guardian_set_change() {
let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
do_init_with_guardians(&mut deps, 1);
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_5_SIGS_GS_1);
assert_eq!(result, ContractError::InvalidGuardianSetIndex.std_err());
let result = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_TO_6);
assert!(result.is_ok());
let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_5_SIGS_GS_1);
assert!(result.is_ok());
}
}

View File

@ -78,6 +78,14 @@ pub enum ContractError {
/// Attempt to execute contract action while it is inactive
#[error("ContractInactive")]
ContractInactive,
/// Could not decode target address from canonical to human-readable form
#[error("WrongTargetAddressFormat")]
WrongTargetAddressFormat,
/// More signatures than active guardians found
#[error("TooManySignatures")]
TooManySignatures,
}
impl ContractError {

View File

@ -1,3 +1,7 @@
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
mod byte_utils;
pub mod contract;
mod error;

View File

@ -1,4 +1,4 @@
use cosmwasm_std::{HumanAddr, Uint128, Binary};
use cosmwasm_std::{Binary, HumanAddr, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

View File

@ -1,7 +1,7 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{CanonicalAddr, HumanAddr, StdResult, Storage, Binary};
use cosmwasm_std::{Binary, CanonicalAddr, HumanAddr, StdResult, Storage};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
@ -122,7 +122,9 @@ mod tests {
fn build_guardian_set(length: usize) -> GuardianSetInfo {
let mut addresses: Vec<GuardianAddress> = Vec::with_capacity(length);
for _ in 0..length {
addresses.push(GuardianAddress{bytes: vec![].into()});
addresses.push(GuardianAddress {
bytes: vec![].into(),
});
}
GuardianSetInfo {
@ -149,4 +151,4 @@ mod tests {
assert_eq!(build_guardian_set(25).quorum(), 17);
assert_eq!(build_guardian_set(100).quorum(), 67);
}
}
}

View File

@ -3,7 +3,7 @@ static WASM: &[u8] = include_bytes!("../../../target/wasm32-unknown-unknown/rele
use cosmwasm_std::{from_slice, Env, HumanAddr, InitResponse};
use cosmwasm_storage::to_length_prefixed;
use cosmwasm_vm::testing::{init, mock_env, mock_instance, MockApi, MockQuerier, MockStorage};
use cosmwasm_vm::{Instance, Storage, Api};
use cosmwasm_vm::{Api, Instance, Storage};
use wormhole::msg::InitMsg;
use wormhole::state::{ConfigInfo, GuardianAddress, GuardianSetInfo, CONFIG_KEY};
@ -53,7 +53,11 @@ fn do_init(
wrapped_asset_code_id: 999,
};
let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0);
let owner = deps.api.canonical_address(&TestAddress::INITIALIZER.value()).0.unwrap();
let owner = deps
.api
.canonical_address(&TestAddress::INITIALIZER.value())
.0
.unwrap();
let res: InitResponse = init(&mut deps, env, init_msg).unwrap();
assert_eq!(0, res.messages.len());
@ -66,7 +70,7 @@ fn do_init(
guardian_set_expirity: 50,
wrapped_asset_code_id: 999,
owner,
is_active: true,
is_active: true,
}
);
Ok(())
@ -78,7 +82,9 @@ fn do_init(
#[test]
fn init_works() {
let guardians = vec![GuardianAddress::from(GuardianAddress {
bytes: hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe").expect("Decoding failed").into(),
bytes: hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe")
.expect("Decoding failed")
.into(),
})];
let _deps = do_init(111, &guardians);
}