aptos/contracts: implement wormhole

This commit is contained in:
Csongor Kiss 2022-10-14 01:15:13 +00:00 committed by jumpsiegel
parent b37efa4c1b
commit 909238c51b
19 changed files with 2983 additions and 0 deletions

14
aptos/wormhole/Makefile Normal file
View File

@ -0,0 +1,14 @@
-include ../../Makefile.help
.PHONY: artifacts
artifacts: build
.PHONY: build
## Build contract
build:
aptos move compile --save-metadata
.PHONY: test
## Run tests
test:
aptos move test

25
aptos/wormhole/Move.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "Wormhole"
version = "0.0.1"
upgrade_policy = "compatible"
#TODO: pin versions before mainnet release
[dependencies]
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework/", rev = "main" }
MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib/", rev = "main" }
AptosStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-stdlib/", rev = "main" }
AptosToken = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-token/", rev = "main" }
Deployer = { local = "../deployer/" }
# U256 = { git = "https://github.com/pontem-network/u256", rev = "main" }
[dev-dependencies]
Deployer = { local = "../deployer/" }
[dev-addresses]
# derived address from sha3_256(deployer | "wormhole" | 0xFF) by running
# worm aptos derive-resource-account 0x277fa055b6a73c42c0662d5236c65c864ccbf2d4abd21f174a30c8b786eab84b wormhole
wormhole = "0xde0036a9600559e295d5f6802ef6f3f802f510366e0c23912b0655d972166017"
deployer = "0x277fa055b6a73c42c0662d5236c65c864ccbf2d4abd21f174a30c8b786eab84b"
[addresses]
wormhole = "_"

View File

@ -0,0 +1,201 @@
/// This module implements upgradeability for the wormhole contract.
///
/// Contract upgrades are authorised by governance, which means that performing
/// an upgrade requires a governance VAA signed by a supermajority of the
/// wormhole guardians.
///
/// Upgrades are performed in a commit-reveal scheme, where submitting the VAA
/// authorises a particular contract hash. Then in a subsequent transaction, the
/// bytecode is uploaded, and if the hash of the bytecode matches the committed
/// hash, then the upgrade proceeds.
///
/// This two-phase process has the advantage that even if the bytecode can't be
/// upgraded to for whatever reason, the governance VAA won't be possible to
/// replay in the future, since the commit transaction replay protects it.
///
/// Additionally, there is an optional migration step that may include one-off
/// logic to be executed after the upgrade. This has to be done in a separate
/// transaction, because the transaction that uploads bytecode cannot execute
/// it.
module wormhole::contract_upgrade {
use std::vector;
use aptos_framework::code;
use wormhole::deserialize;
use wormhole::cursor;
use wormhole::vaa;
use wormhole::state;
use wormhole::keccak256::keccak256;
/// "Core" (left padded)
const CORE: vector<u8> = x"00000000000000000000000000000000000000000000000000000000436f7265";
const E_UPGRADE_UNAUTHORIZED: u64 = 0;
const E_UNEXPECTED_HASH: u64 = 1;
const E_INVALID_MODULE: u64 = 2;
const E_INVALID_ACTION: u64 = 3;
const E_INVALID_TARGET: u64 = 4;
const E_NOT_MIGRATING: u64 = 5;
/// The `UpgradeAuthorized` type in the global storage represents the fact
/// there is an ongoing approved upgrade.
/// When the upgrade is finalised in `upgrade`, this object is deleted.
struct UpgradeAuthorized has key {
hash: vector<u8>
}
struct Hash {
hash: vector<u8>
}
fun parse_payload(payload: vector<u8>): Hash {
let cur = cursor::init(payload);
let target_module = deserialize::deserialize_vector(&mut cur, 32);
assert!(target_module == CORE, E_INVALID_MODULE);
let action = deserialize::deserialize_u8(&mut cur);
assert!(action == 0x01, E_INVALID_ACTION);
let chain = deserialize::deserialize_u16(&mut cur);
assert!(chain == state::get_chain_id(), E_INVALID_TARGET);
let hash = deserialize::deserialize_vector(&mut cur, 32);
cursor::destroy_empty(cur);
Hash { hash }
}
// -----------------------------------------------------------------------------
// Commit
public entry fun submit_vaa(
vaa: vector<u8>
) acquires UpgradeAuthorized {
let vaa = vaa::parse_and_verify(vaa);
vaa::assert_governance(&vaa);
vaa::replay_protect(&vaa);
authorize_upgrade(parse_payload(vaa::destroy(vaa)));
}
fun authorize_upgrade(hash: Hash) acquires UpgradeAuthorized {
let Hash { hash } = hash;
let wormhole = state::wormhole_signer();
if (exists<UpgradeAuthorized>(@wormhole)) {
// TODO(csongor): here we're dropping the upgrade hash, in case an
// upgrade fails for some reason. Should we emit a log or something?
let UpgradeAuthorized { hash: _ } = move_from<UpgradeAuthorized>(@wormhole);
};
move_to(&wormhole, UpgradeAuthorized { hash });
}
#[test_only]
public fun authorized_hash(): vector<u8> acquires UpgradeAuthorized {
let u = borrow_global<UpgradeAuthorized>(@wormhole);
u.hash
}
// -----------------------------------------------------------------------------
// Reveal
public entry fun upgrade(
metadata_serialized: vector<u8>,
code: vector<vector<u8>>
) acquires UpgradeAuthorized {
assert!(exists<UpgradeAuthorized>(@wormhole), E_UPGRADE_UNAUTHORIZED);
let UpgradeAuthorized { hash } = move_from<UpgradeAuthorized>(@wormhole);
// we compute the hash of hashes of the metadata and the bytecodes.
// the aptos framework appears to perform no validation of the metadata,
// so we check it here too.
let c = copy code;
vector::reverse(&mut c);
let a = keccak256(metadata_serialized);
while (!vector::is_empty(&c)) vector::append(&mut a, keccak256(vector::pop_back(&mut c)));
assert!(keccak256(a) == hash, E_UNEXPECTED_HASH);
let wormhole = state::wormhole_signer();
code::publish_package_txn(&wormhole, metadata_serialized, code);
// allow migration to be run.
if (!exists<Migrating>(@wormhole)) {
move_to(&wormhole, Migrating {});
}
}
// -----------------------------------------------------------------------------
// Migration
struct Migrating has key {}
public fun is_migrating(): bool {
exists<Migrating>(@wormhole)
}
public entry fun migrate() acquires Migrating {
assert!(exists<Migrating>(@wormhole), E_NOT_MIGRATING);
let Migrating { } = move_from<Migrating>(@wormhole);
// NOTE: put any one-off migration logic here.
// Most upgrades likely won't need to do anything, in which case the
// rest of this function's body may be empty.
// Make sure to delete it after the migration has gone through
// successfully.
// WARNING: the migration does *not* proceed atomically with the
// upgrade (as they are done in separate transactions).
// If the nature of your migration absolutely requires the migration to
// happen before certain other functionality is available, then guard
// that functionality with `assert!(!is_migrating())` (from above).
}
}
#[test_only]
module wormhole::contract_upgrade_test {
use wormhole::contract_upgrade;
use wormhole::wormhole;
const UPGRADE_VAA: vector<u8> = x"010000000001000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a30100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000020b10360000000000000000000000000000000000000000000000000000000000436f7265010016d8f30e4a345ea0fa5df11daac4e1866ee368d253209cf9eda012d915a2db09e6";
fun setup() {
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
wormhole::init_test(
22,
1,
x"0000000000000000000000000000000000000000000000000000000000000004",
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
0
);
}
#[test]
public fun test_contract_upgrade_authorize() {
setup();
contract_upgrade::submit_vaa(UPGRADE_VAA);
let expected_hash = x"d8f30e4a345ea0fa5df11daac4e1866ee368d253209cf9eda012d915a2db09e6";
assert!(contract_upgrade::authorized_hash() == expected_hash, 0);
}
#[test]
#[expected_failure(abort_code = 0x6407)]
public fun test_contract_upgrade_double() {
setup();
// make sure we can't replay a VAA
contract_upgrade::submit_vaa(UPGRADE_VAA);
contract_upgrade::submit_vaa(UPGRADE_VAA);
}
#[test]
#[expected_failure(abort_code = 4)]
public fun test_contract_upgrade_wrong_chain() {
setup();
let eth_upgrade = x"01000000000100d46215cd004a6a9d50114d31efdcba3e769dc559a7550c5e90618cacc5808d1e52d982d68e98369946fcfa46d47ade3ad88d9e4c2634a2d1a564b7aecb33e0d7000000000100000001000100000000000000000000000000000000000000000000000000000000000000040000000000c88e420000000000000000000000000000000000000000000000000000000000436f72650100029fc26e02f9bc648d48b7076571f1790049b2049d0101d4c52419c9ab8134ecb6";
contract_upgrade::submit_vaa(eth_upgrade);
}
}

View File

@ -0,0 +1,46 @@
module wormhole::cursor {
use std::vector;
/// A cursor allows consuming a vector incrementally for parsing operations.
/// It has no drop ability, and the only way to deallocate it is by calling the
/// `destroy_empty` method, which will fail if the whole input hasn't been consumed.
///
/// This setup statically guarantees that the parsing methods consume the
/// full input.
struct Cursor<T> {
data: vector<T>,
}
/// Initialises a cursor from a vector.
public fun init<T>(data: vector<T>): Cursor<T> {
// reverse the array so we have access to the first element easily
vector::reverse(&mut data);
Cursor<T> {
data,
}
}
/// Destroys an empty cursor.
/// Aborts if the cursor is not empty.
public fun destroy_empty<T>(cur: Cursor<T>) {
let Cursor { data } = cur;
vector::destroy_empty(data);
}
/// Consumes the rest of the cursor (thus destroying it) and returns the
/// remaining bytes.
/// NOTE: Only use this function if you intend to consume the rest of the
/// bytes. Since the result is a vector, which can be dropped, it is not
/// possible to statically guarantee that the rest will be used.
public fun rest<T>(cur: Cursor<T>): vector<T> {
let Cursor { data } = cur;
// re-reverse the data so it is in the same order as the original input
vector::reverse(&mut data);
data
}
/// Returns the first element of the cursor and advances it.
public fun poke<T>(cur: &mut Cursor<T>): T {
vector::pop_back(&mut cur.data)
}
}

View File

@ -0,0 +1,137 @@
module wormhole::deserialize {
use std::vector;
use wormhole::cursor::{Self, Cursor};
use wormhole::u16::{Self, U16};
use wormhole::u32::{Self, U32};
use wormhole::u256::{Self, U256};
public fun deserialize_u8(cur: &mut Cursor<u8>): u8 {
cursor::poke(cur)
}
public fun deserialize_u16(cur: &mut Cursor<u8>): U16 {
let res: u64 = 0;
let i = 0;
while (i < 2) {
let b = cursor::poke(cur);
res = (res << 8) + (b as u64);
i = i + 1;
};
u16::from_u64(res)
}
public fun deserialize_u32(cur: &mut Cursor<u8>): U32 {
let res: u64 = 0;
let i = 0;
while (i < 4) {
let b = cursor::poke(cur);
res = (res << 8) + (b as u64);
i = i + 1;
};
u32::from_u64(res)
}
public fun deserialize_u64(cur: &mut Cursor<u8>): u64 {
let res: u64 = 0;
let i = 0;
while (i < 8) {
let b = cursor::poke(cur);
res = (res << 8) + (b as u64);
i = i + 1;
};
res
}
public fun deserialize_u128(cur: &mut Cursor<u8>): u128 {
let res: u128 = 0;
let i = 0;
while (i < 16) {
let b = cursor::poke(cur);
res = (res << 8) + (b as u128);
i = i + 1;
};
res
}
public fun deserialize_u256(cur: &mut Cursor<u8>): U256 {
let v0 = deserialize_u128(cur);
let v1 = deserialize_u128(cur);
u256::add(u256::shl(u256::from_u128(v0), 128), u256::from_u128(v1))
}
public fun deserialize_vector(cur: &mut Cursor<u8>, len: u64): vector<u8> {
let result = vector::empty();
while (len > 0) {
vector::push_back(&mut result, cursor::poke(cur));
len = len - 1;
};
result
}
}
#[test_only]
module wormhole::deserialize_test {
use wormhole::cursor;
use wormhole::u16;
use wormhole::u32;
use wormhole::deserialize::{
deserialize_u8,
deserialize_u16,
deserialize_u32,
deserialize_u64,
deserialize_u128,
deserialize_vector,
};
#[test]
fun test_deserialize_u8() {
let cur = cursor::init(x"99");
let byte = deserialize_u8(&mut cur);
assert!(byte==0x99, 0);
cursor::destroy_empty(cur);
}
#[test]
fun test_deserialize_u16() {
let cur = cursor::init(x"9987");
let u = deserialize_u16(&mut cur);
assert!(u == u16::from_u64(0x9987), 0);
cursor::destroy_empty(cur);
}
#[test]
fun test_deserialize_u32() {
let cur = cursor::init(x"99876543");
let u = deserialize_u32(&mut cur);
assert!(u == u32::from_u64(0x99876543), 0);
cursor::destroy_empty(cur);
}
#[test]
fun test_deserialize_u64() {
let cur = cursor::init(x"1300000025000001");
let u = deserialize_u64(&mut cur);
assert!(u==0x1300000025000001, 0);
cursor::destroy_empty(cur);
}
#[test]
fun test_deserialize_u128() {
let cur = cursor::init(x"130209AB2500FA0113CD00AE25000001");
let u = deserialize_u128(&mut cur);
assert!(u==0x130209AB2500FA0113CD00AE25000001, 0);
cursor::destroy_empty(cur);
}
#[test]
fun test_deserialize_vector() {
let cur = cursor::init(b"hello world");
let hello = deserialize_vector(&mut cur, 5);
deserialize_u8(&mut cur);
let world = deserialize_vector(&mut cur, 5);
assert!(hello == b"hello", 0);
assert!(world == b"world", 0);
cursor::destroy_empty(cur);
}
}

View File

@ -0,0 +1,88 @@
/// TODO(csongor): document
/// TODO(csongor): should we rename emitter to something else? It's used in a
/// couple of places to authenticate contracts
module wormhole::emitter {
use wormhole::serialize;
use wormhole::external_address::{Self, ExternalAddress};
friend wormhole::state;
friend wormhole::wormhole;
#[test_only]
friend wormhole::emitter_test;
struct EmitterRegistry has store {
next_id: u64
}
// TODO(csongor): document that this has to be globally unique.
// The friend modifier is very important here.
public(friend) fun init_emitter_registry(): EmitterRegistry {
EmitterRegistry { next_id: 0 }
}
#[test_only]
public fun destroy_emitter_registry(registry: EmitterRegistry) {
let EmitterRegistry { next_id: _ } = registry;
}
public(friend) fun new_emitter(registry: &mut EmitterRegistry): EmitterCapability {
let emitter = registry.next_id;
registry.next_id = emitter + 1;
EmitterCapability { emitter, sequence: 0 }
}
struct EmitterCapability has store {
/// Unique identifier of the emitter
emitter: u64,
/// Sequence number of the next wormhole message
sequence: u64
}
/// Destroys an emitter capability.
///
/// Note that this operation removes the ability to send messages using the
/// emitter id, and is irreversible.
public fun destroy_emitter_cap(emitter_cap: EmitterCapability) {
let EmitterCapability { emitter: _, sequence: _ } = emitter_cap;
}
public fun get_emitter(emitter_cap: &EmitterCapability): u64 {
emitter_cap.emitter
}
/// Returns the external address of the emitter.
///
/// The 16 byte (u128) emitter id left-padded to u256
public fun get_external_address(emitter_cap: &EmitterCapability): ExternalAddress {
let emitter_bytes = vector<u8>[];
serialize::serialize_u64(&mut emitter_bytes, emitter_cap.emitter);
external_address::from_bytes(emitter_bytes)
}
public(friend) fun use_sequence(emitter_cap: &mut EmitterCapability): u64 {
let sequence = emitter_cap.sequence;
emitter_cap.sequence = sequence + 1;
sequence
}
}
#[test_only]
module wormhole::emitter_test {
use wormhole::emitter;
#[test]
public fun test_increasing_emitters() {
let registry = emitter::init_emitter_registry();
let emitter1 = emitter::new_emitter(&mut registry);
let emitter2 = emitter::new_emitter(&mut registry);
assert!(emitter::get_emitter(&emitter1) == 0, 0);
assert!(emitter::get_emitter(&emitter2) == 1, 0);
emitter::destroy_emitter_cap(emitter1);
emitter::destroy_emitter_cap(emitter2);
emitter::destroy_emitter_registry(registry);
}
}

View File

@ -0,0 +1,124 @@
/// 32 byte, left-padded address representing an arbitrary address, to be used in VAAs to
/// refer to addresses.
module wormhole::external_address {
use std::vector;
use wormhole::cursor::Cursor;
use wormhole::deserialize;
use wormhole::serialize;
const E_VECTOR_TOO_LONG: u64 = 0;
struct ExternalAddress has drop, copy, store {
external_address: vector<u8>,
}
public fun get_bytes(e: &ExternalAddress): vector<u8> {
e.external_address
}
public entry fun pad_left_32(input: &vector<u8>): vector<u8>{
let len = vector::length<u8>(input);
assert!(len <= 32, E_VECTOR_TOO_LONG);
let ret = vector::empty<u8>();
let zeros_remaining = 32 - len;
while (zeros_remaining > 0){
vector::push_back<u8>(&mut ret, 0);
zeros_remaining = zeros_remaining - 1;
};
vector::append<u8>(&mut ret, *input);
ret
}
public fun left_pad(s: &vector<u8>): ExternalAddress {
let padded_vector = pad_left_32(s);
ExternalAddress { external_address: padded_vector}
}
public fun from_bytes(bytes: vector<u8>): ExternalAddress {
left_pad(&bytes)
}
public fun deserialize(cur: &mut Cursor<u8>): ExternalAddress {
let bytes = deserialize::deserialize_vector(cur, 32);
from_bytes(bytes)
}
public fun serialize(buf: &mut vector<u8>, e: ExternalAddress) {
serialize::serialize_vector(buf, e.external_address)
}
}
#[test_only]
module wormhole::external_address_test {
use wormhole::external_address::{get_bytes, left_pad, from_bytes, pad_left_32};
use aptos_framework::vector::{Self};
// test get_bytes and left_pad
#[test]
public fun test_left_pad() {
let v = x"123456789123456789123456789123451234567891234567891234"; // less than 32 bytes
let res = left_pad(&v);
let bytes = get_bytes(&res);
let m = x"0000000000";
vector::append(&mut m, v);
assert!(bytes==m, 0);
}
#[test]
public fun test_left_pad_length_32_vector() {
let v = x"1234567891234567891234567891234512345678912345678912345678912345"; //32 bytes
let res = left_pad(&v);
let bytes = get_bytes(&res);
assert!(bytes==v, 0);
}
#[test]
#[expected_failure(abort_code = 0)]
public fun test_left_pad_vector_too_long() {
let v = x"123456789123456789123456789123451234567891234567891234567891234500"; //33 bytes
let res = left_pad(&v);
let bytes = get_bytes(&res);
assert!(bytes==v, 0);
}
#[test]
public fun test_from_bytes() {
let v = x"1234";
let ea = from_bytes(v);
let bytes = get_bytes(&ea);
let w = x"000000000000000000000000000000000000000000000000000000000000";
vector::append(&mut w, v);
assert!(bytes==w, 0);
}
#[test]
#[expected_failure(abort_code = 0)]
public fun test_from_bytes_over_32_bytes() {
let v = x"00000000000000000000000000000000000000000000000000000000000000001234";
let ea = from_bytes(v);
let _bytes = get_bytes(&ea);
}
#[test]
fun test_pad_left_short() {
let v = x"11";
let pad_left_v = pad_left_32(&v);
assert!(pad_left_v==x"0000000000000000000000000000000000000000000000000000000000000011", 0);
}
#[test]
fun test_pad_left_exact() {
let v = x"5555555555555555555555555555555555555555555555555555555555555555";
let pad_left_v = pad_left_32(&v);
assert!(pad_left_v==x"5555555555555555555555555555555555555555555555555555555555555555", 0);
}
#[test]
#[expected_failure(abort_code = 0)]
fun test_pad_left_long() {
let v = x"665555555555555555555555555555555555555555555555555555555555555555";
pad_left_32(&v);
}
}

View File

@ -0,0 +1,83 @@
/// Guardian keys are EVM-style 20 byte addresses
/// That is, they are computed by taking the last 20 bytes of the keccak256
/// hash of their 64 byte secp256k1 public key.
module wormhole::guardian_pubkey {
use aptos_std::secp256k1::{
ECDSARawPublicKey,
ECDSASignature,
ecdsa_raw_public_key_to_bytes,
ecdsa_recover,
};
use std::vector;
use wormhole::keccak256::keccak256;
/// An error occurred while deserializing, for example due to wrong input size.
const E_DESERIALIZE: u64 = 1;
struct Address has key, store, drop, copy {
bytes: vector<u8>
}
/// Deserializes a raw byte sequence into an address.
/// Aborts if the input is not 20 bytes long.
public fun from_bytes(bytes: vector<u8>): Address {
assert!(std::vector::length(&bytes) == 20, std::error::invalid_argument(E_DESERIALIZE));
Address { bytes }
}
/// Computes the address from a 64 byte public key.
public fun from_pubkey(pubkey: &ECDSARawPublicKey): Address {
let bytes = ecdsa_raw_public_key_to_bytes(pubkey);
let hash = keccak256(bytes);
let address = vector::empty<u8>();
let i = 0;
while (i < 20) {
vector::push_back(&mut address, vector::pop_back(&mut hash));
i = i + 1;
};
vector::reverse(&mut address);
Address { bytes: address }
}
/// Recovers the address from a signature and message.
/// This is known as 'ecrecover' in EVM.
public fun from_signature(
message: vector<u8>,
recovery_id: u8,
sig: &ECDSASignature,
): Address {
let pubkey = ecdsa_recover(message, recovery_id, sig);
let pubkey = std::option::extract(&mut pubkey);
from_pubkey(&pubkey)
}
}
#[test_only]
module wormhole::guardian_pubkey_test {
use wormhole::guardian_pubkey;
use aptos_std::secp256k1::{
ecdsa_raw_public_key_from_64_bytes,
ecdsa_signature_from_bytes
};
#[test]
public fun from_pubkey_test() {
// devnet guardian public key
let pubkey = x"d4a4629979f0c9fa0f0bb54edf33f87c8c5a1f42c0350a30d68f7e967023e34e495a8ebf5101036d0fd66e3b0a8c7c61b65fceeaf487ab3cd1b5b7b50beb7970";
let pubkey = ecdsa_raw_public_key_from_64_bytes(pubkey);
let expected_address = guardian_pubkey::from_bytes(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe");
let address = guardian_pubkey::from_pubkey(&pubkey);
assert!(address == expected_address, 0);
}
#[test]
public fun from_signature() {
let sig = ecdsa_signature_from_bytes(x"38535089d6eec412a00066f84084212316ee3451145a75591dbd4a1c2a2bff442223f81e58821bfa4e8ffb80a881daf7a37500b04dfa5719fff25ed4cec8dda3");
let msg = x"43f3693ccdcb4400e1d1c5c8cec200153bd4b3d167e5b9fe5400508cf8717880";
let addr = guardian_pubkey::from_signature(msg, 1, &sig);
let expected_addr = guardian_pubkey::from_bytes(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe");
assert!(addr == expected_addr, 0);
}
}

View File

@ -0,0 +1,169 @@
module wormhole::guardian_set_upgrade {
use wormhole::deserialize;
use wormhole::cursor::{Self};
use wormhole::vaa::{Self};
use wormhole::state;
use wormhole::structs::{
Guardian,
create_guardian,
create_guardian_set
};
use wormhole::u32::{Self,U32};
use wormhole::u16;
use std::vector;
const E_WRONG_GUARDIAN_LEN: u64 = 0x0;
const E_NO_GUARDIAN_SET: u64 = 0x1;
const E_INVALID_MODULE: u64 = 0x2;
const E_INVALID_ACTION: u64 = 0x3;
const E_INVALID_TARGET: u64 = 0x4;
const E_NON_INCREMENTAL_GUARDIAN_SETS: u64 = 0x5;
struct GuardianSetUpgrade {
new_index: U32,
guardians: vector<Guardian>,
}
public entry fun submit_vaa(vaa: vector<u8>) {
let vaa = vaa::parse_and_verify(vaa);
vaa::assert_governance(&vaa);
vaa::replay_protect(&vaa);
do_upgrade(parse_payload(vaa::destroy(vaa)))
}
fun do_upgrade(upgrade: GuardianSetUpgrade) {
let current_index = state::get_current_guardian_set_index();
let GuardianSetUpgrade {
new_index,
guardians,
} = upgrade;
assert!(
u32::to_u64(new_index) == u32::to_u64(current_index) + 1,
E_NON_INCREMENTAL_GUARDIAN_SETS
);
state::update_guardian_set_index(new_index);
state::store_guardian_set(create_guardian_set(new_index, guardians));
state::expire_guardian_set(current_index);
}
#[test_only]
public fun do_upgrade_test(new_index: U32, guardians: vector<Guardian>) {
do_upgrade(GuardianSetUpgrade { new_index, guardians })
}
public fun parse_payload(bytes: vector<u8>): GuardianSetUpgrade {
let cur = cursor::init(bytes);
let guardians = vector::empty<Guardian>();
let target_module = deserialize::deserialize_vector(&mut cur, 32);
let expected_module = x"00000000000000000000000000000000000000000000000000000000436f7265"; // Core
assert!(target_module == expected_module, E_INVALID_MODULE);
let action = deserialize::deserialize_u8(&mut cur);
assert!(action == 0x02, E_INVALID_ACTION);
let chain = deserialize::deserialize_u16(&mut cur);
assert!(chain == u16::from_u64(0x00), E_INVALID_TARGET);
let new_index = deserialize::deserialize_u32(&mut cur);
let guardian_len = deserialize::deserialize_u8(&mut cur);
while (guardian_len > 0) {
let key = deserialize::deserialize_vector(&mut cur, 20);
vector::push_back(&mut guardians, create_guardian(key));
guardian_len = guardian_len - 1;
};
cursor::destroy_empty(cur);
GuardianSetUpgrade {
new_index: new_index,
guardians: guardians,
}
}
#[test_only]
public fun split(upgrade: GuardianSetUpgrade): (U32, vector<Guardian>) {
let GuardianSetUpgrade { new_index, guardians } = upgrade;
(new_index, guardians)
}
}
#[test_only]
module wormhole::guardian_set_upgrade_test {
use wormhole::guardian_set_upgrade;
use wormhole::wormhole;
use wormhole::state;
use std::vector;
use wormhole::structs::{create_guardian};
use wormhole::u32;
#[test]
public fun test_parse_guardian_set_upgrade() {
use wormhole::u32;
let b = x"00000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d";
let (new_index, guardians) = guardian_set_upgrade::split(guardian_set_upgrade::parse_payload(b));
assert!(new_index == u32::from_u64(1), 0);
assert!(vector::length(&guardians) == 19, 0);
let expected = vector[
create_guardian(x"58cc3ae5c097b213ce3c81979e1b9f9570746aa5"),
create_guardian(x"ff6cb952589bde862c25ef4392132fb9d4a42157"),
create_guardian(x"114de8460193bdf3a2fcf81f86a09765f4762fd1"),
create_guardian(x"107a0086b32d7a0977926a205131d8731d39cbeb"),
create_guardian(x"8c82b2fd82faed2711d59af0f2499d16e726f6b2"),
create_guardian(x"11b39756c042441be6d8650b69b54ebe715e2343"),
create_guardian(x"54ce5b4d348fb74b958e8966e2ec3dbd4958a7cd"),
create_guardian(x"eb5f7389fa26941519f0863349c223b73a6ddee7"),
create_guardian(x"74a3bf913953d695260d88bc1aa25a4eee363ef0"),
create_guardian(x"000ac0076727b35fbea2dac28fee5ccb0fea768e"),
create_guardian(x"af45ced136b9d9e24903464ae889f5c8a723fc14"),
create_guardian(x"f93124b7c738843cbb89e864c862c38cddcccf95"),
create_guardian(x"d2cc37a4dc036a8d232b48f62cdd4731412f4890"),
create_guardian(x"da798f6896a3331f64b48c12d1d57fd9cbe70811"),
create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3"),
create_guardian(x"8192b6e7387ccd768277c17dab1b7a5027c0b3cf"),
create_guardian(x"178e21ad2e77ae06711549cfbb1f9c7a9d8096e8"),
create_guardian(x"5e1487f35515d02a92753504a8d75471b9f49edb"),
create_guardian(x"6fbebc898f403e4773e95feb15e80c9a99c8348d"),
];
assert!(expected == guardians, 0);
}
#[test]
public fun test_guardian_set_expiry() {
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
let _wormhole = wormhole::init_test(
22,
1,
x"0000000000000000000000000000000000000000000000000000000000000004",
x"f93124b7c738843cbb89e864c862c38cddcccf95",
0
);
let first_index = state::get_current_guardian_set_index();
let guardian_set = state::get_guardian_set(first_index);
// make sure guardian set is active
assert!(state::guardian_set_is_active(&guardian_set), 0);
// do an upgrade
guardian_set_upgrade::do_upgrade_test(
u32::from_u64(1),
vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")]);
// make sure old guardian set is still active
let guardian_set = state::get_guardian_set(first_index);
assert!(state::guardian_set_is_active(&guardian_set), 0);
// fast forward time beyond expiration
std::timestamp::fast_forward_seconds(90000);
// make sure old guardian set is no longer active
assert!(!state::guardian_set_is_active(&guardian_set), 0);
}
}

View File

@ -0,0 +1,16 @@
module wormhole::keccak256 {
use aptos_framework::aptos_hash;
spec module {
pragma verify=false;
}
public fun keccak256(bytes: vector<u8>): vector<u8> {
aptos_hash::keccak256(bytes)
}
spec keccak256 {
pragma opaque;
}
}

View File

@ -0,0 +1,136 @@
module wormhole::serialize {
use std::vector;
use wormhole::u16::{Self, U16};
use wormhole::u32::{Self, U32};
use wormhole::u256::U256;
// we reuse the native bcs serialiser -- it uses little-endian encoding, and
// we need big-endian, so the results are reversed
use std::bcs;
public fun serialize_u8(buf: &mut vector<u8>, v: u8) {
vector::push_back<u8>(buf, v);
}
public fun serialize_u16(buf: &mut vector<u8>, v: U16) {
let (v0, v1) = u16::split_u8(v);
serialize_u8(buf, v0);
serialize_u8(buf, v1);
}
public fun serialize_u32(buf: &mut vector<u8>, v: U32) {
let (v0, v1, v2, v3) = u32::split_u8(v);
serialize_u8(buf, v0);
serialize_u8(buf, v1);
serialize_u8(buf, v2);
serialize_u8(buf, v3);
}
public fun serialize_u64(buf: &mut vector<u8>, v: u64) {
let v = bcs::to_bytes(&v);
vector::reverse(&mut v);
vector::append(buf, v);
}
public fun serialize_u128(buf: &mut vector<u8>, v: u128) {
let v = bcs::to_bytes(&v);
vector::reverse(&mut v);
vector::append(buf, v);
}
public fun serialize_u256(buf: &mut vector<u8>, v: U256) {
let v = bcs::to_bytes(&v);
vector::reverse(&mut v);
vector::append(buf, v);
}
public fun serialize_vector(buf: &mut vector<u8>, v: vector<u8>){
vector::append(buf, v)
}
}
#[test_only]
module wormhole::test_serialize {
use wormhole::serialize;
use wormhole::deserialize;
use wormhole::cursor;
use wormhole::u32;
use wormhole::u16;
use wormhole::u256;
use std::vector;
#[test]
fun test_serialize_u8(){
let u = 0x12;
let s = vector::empty();
serialize::serialize_u8(&mut s, u);
let cur = cursor::init(s);
let p = deserialize::deserialize_u8(&mut cur);
cursor::destroy_empty(cur);
assert!(p==u, 0);
}
#[test]
fun test_serialize_u16(){
let u = u16::from_u64((0x1234 as u64));
let s = vector::empty();
serialize::serialize_u16(&mut s, u);
let cur = cursor::init(s);
let p = deserialize::deserialize_u16(&mut cur);
cursor::destroy_empty(cur);
assert!(p==u, 0);
}
#[test]
fun test_serialize_u32(){
let u = u32::from_u64((0x12345678 as u64));
let s = vector::empty();
serialize::serialize_u32(&mut s, u);
let cur = cursor::init(s);
let p = deserialize::deserialize_u32(&mut cur);
cursor::destroy_empty(cur);
assert!(p==u, 0);
}
#[test]
fun test_serialize_u64(){
let u = 0x1234567812345678;
let s = vector::empty();
serialize::serialize_u64(&mut s, u);
let cur = cursor::init(s);
let p = deserialize::deserialize_u64(&mut cur);
cursor::destroy_empty(cur);
assert!(p==u, 0);
}
#[test]
fun test_serialize_u128(){
let u = 0x12345678123456781234567812345678;
let s = vector::empty();
serialize::serialize_u128(&mut s, u);
let cur = cursor::init(s);
let p = deserialize::deserialize_u128(&mut cur);
cursor::destroy_empty(cur);
assert!(p==u, 0);
}
#[test]
fun test_serialize_u256(){
let u = u256::add(u256::shl(u256::from_u128(0x47386917590997937461700473756125), 128), u256::from_u128(0x9876));
let s = vector::empty();
serialize::serialize_u256(&mut s, u);
let exp = x"4738691759099793746170047375612500000000000000000000000000009876";
assert!(s == exp, 0);
}
#[test]
fun test_serialize_vector(){
let x = vector::empty<u8>();
let y = vector::empty<u8>();
vector::push_back<u8>(&mut x, 0x12);
vector::push_back<u8>(&mut x, 0x34);
vector::push_back<u8>(&mut x, 0x56);
serialize::serialize_vector(&mut y, x);
assert!(y == x"123456", 0);
}
}

View File

@ -0,0 +1,32 @@
/// A set data structure.
module wormhole::set {
use aptos_std::table::{Self, Table};
/// Empty struct. Used as the value type in mappings to encode a set
struct Unit has store, copy, drop {}
/// A set containing elements of type `A` with support for membership
/// checking.
struct Set<phantom A: copy + drop> has store {
elems: Table<A, Unit>
}
/// Create a new Set.
public fun new<A: copy + drop>(): Set<A> {
Set {
elems: table::new()
}
}
/// Add a new element to the set.
/// Aborts if the element already exists
public fun add<A: copy + drop>(set: &mut Set<A>, key: A) {
table::add(&mut set.elems, key, Unit {})
}
/// Returns true iff `set` contains an entry for `key`.
public fun contains<A: copy + drop>(set: &Set<A>, key: A): bool {
table::contains(&set.elems, key)
}
}

View File

@ -0,0 +1,228 @@
module wormhole::state {
use std::table::{Self, Table};
use std::event::{Self, EventHandle};
use std::account;
use std::timestamp;
use wormhole::structs::{Self, GuardianSet};
use wormhole::u16::{U16};
use wormhole::u32::{Self, U32};
use wormhole::emitter;
use wormhole::set::{Self, Set};
use wormhole::external_address::{ExternalAddress};
friend wormhole::guardian_set_upgrade;
friend wormhole::contract_upgrade;
friend wormhole::wormhole;
friend wormhole::vaa;
struct GuardianSetChanged has store, drop {
oldGuardianIndex: U32,
newGuardianIndex: U32,
}
struct WormholeMessage has store, drop {
sender: u64,
sequence: u64,
nonce: u64,
payload: vector<u8>,
consistency_level: u8,
timestamp: u64,
}
struct WormholeMessageHandle has key, store {
event: EventHandle<WormholeMessage>
}
struct GuardianSetChangedHandle has key, store {
event: EventHandle<GuardianSetChanged>
}
struct WormholeState has key {
/// This chain's wormhole id
chain_id: U16,
/// Governance chain's id
governance_chain_id: U16,
/// Address of governance contract on governance chain
governance_contract: ExternalAddress,
/// Mapping of guardian_set_index => guardian set
guardian_sets: Table<u64, GuardianSet>,
/// Current active guardian set index
guardian_set_index: U32,
/// Period for which a guardian set stays active after it has been replaced
guardian_set_expiry: U32,
/// Consumed governance actions
consumed_governance_actions: Set<vector<u8>>,
message_fee: u64,
/// The signer capability for wormhole itself
signer_cap: account::SignerCapability,
/// Capability for creating new emitters
emitter_registry: emitter::EmitterRegistry
}
//create some empty tables and stuff...
public(friend) fun init_wormhole_state(
wormhole: &signer,
chain_id: U16,
governance_chain_id: U16,
governance_contract: ExternalAddress,
guardian_set_expiry: U32,
message_fee: u64,
signer_cap: account::SignerCapability
) {
move_to(wormhole, WormholeState {
chain_id,
governance_chain_id,
governance_contract,
guardian_sets: table::new<u64, GuardianSet>(),
guardian_set_index: u32::from_u64(0),
guardian_set_expiry,
consumed_governance_actions: set::new<vector<u8>>(),
message_fee,
signer_cap,
emitter_registry: emitter::init_emitter_registry(),
});
}
public fun create_wormhole_message_handle(e: EventHandle<WormholeMessage>): WormholeMessageHandle {
WormholeMessageHandle {
event: e
}
}
public fun create_guardian_set_changed_handle(e: EventHandle<GuardianSetChanged>): GuardianSetChangedHandle {
GuardianSetChangedHandle {
event: e
}
}
public(friend) fun init_message_handles(admin: &signer) {
move_to(admin, create_wormhole_message_handle(account::new_event_handle<WormholeMessage>(admin)));
move_to(admin, create_guardian_set_changed_handle(account::new_event_handle<GuardianSetChanged>(admin)));
}
public(friend) entry fun publish_event(
sender: u64,
sequence: u64,
nonce: u64,
payload: vector<u8>,
) acquires WormholeMessageHandle {
let event_handle = borrow_global_mut<WormholeMessageHandle>(@wormhole);
let now = aptos_framework::timestamp::now_seconds();
event::emit_event<WormholeMessage>(
&mut event_handle.event,
WormholeMessage {
sender,
sequence,
nonce: nonce,
payload,
// Aptos is an instant finality chain, so we don't need
// confirmations
consistency_level: 0,
timestamp: now
}
);
}
public(friend) fun update_guardian_set_index(new_index: U32) acquires WormholeState {
let state = borrow_global_mut<WormholeState>(@wormhole);
state.guardian_set_index= new_index;
}
public fun get_guardian_set(index: U32): GuardianSet acquires WormholeState {
let state = borrow_global_mut<WormholeState>(@wormhole);
*table::borrow<u64, GuardianSet>(&mut state.guardian_sets, u32::to_u64(index))
}
public(friend) fun expire_guardian_set(index: U32) acquires WormholeState {
let state = borrow_global_mut<WormholeState>(@wormhole);
let guardian_set: &mut GuardianSet = table::borrow_mut<u64, GuardianSet>(&mut state.guardian_sets, u32::to_u64(index));
let expiry = state.guardian_set_expiry;
structs::expire_guardian_set(guardian_set, expiry);
}
public(friend) fun store_guardian_set(set: GuardianSet) acquires WormholeState {
let state = borrow_global_mut<WormholeState>(@wormhole);
let index: u64 = u32::to_u64(structs::get_guardian_set_index(&set));
table::add(&mut state.guardian_sets, index, set);
}
public fun guardian_set_is_active(guardian_set: &GuardianSet): bool acquires WormholeState {
let index = structs::get_guardian_set_index(guardian_set);
let current_index = get_current_guardian_set_index();
let now = timestamp::now_seconds();
index == current_index ||
u32::to_u64(structs::get_guardian_set_expiry(guardian_set)) > now
}
public(friend) fun set_governance_action_consumed(hash: vector<u8>) acquires WormholeState {
let state = borrow_global_mut<WormholeState>(@wormhole);
set::add(&mut state.consumed_governance_actions, hash);
}
public(friend) fun set_chain_id(chain_id: U16) acquires WormholeState {
borrow_global_mut<WormholeState>(@wormhole).chain_id = chain_id;
}
public(friend) fun set_governance_chain_id(chain_id: U16) acquires WormholeState {
borrow_global_mut<WormholeState>(@wormhole).governance_chain_id = chain_id;
}
public(friend) fun set_governance_contract(governance_contract: ExternalAddress) acquires WormholeState {
borrow_global_mut<WormholeState>(@wormhole).governance_contract = governance_contract;
}
public(friend) fun set_message_fee(new_fee: u64) acquires WormholeState {
borrow_global_mut<WormholeState>(@wormhole).message_fee = new_fee;
}
// getters
public fun get_current_guardian_set_index(): U32 acquires WormholeState {
let state = borrow_global<WormholeState>(@wormhole);
state.guardian_set_index
}
public fun get_current_guardian_set(): GuardianSet acquires WormholeState {
let state = borrow_global<WormholeState>(@wormhole);
let ind = u32::to_u64(state.guardian_set_index);
*table::borrow(&state.guardian_sets, ind)
}
public fun get_governance_contract(): ExternalAddress acquires WormholeState {
borrow_global<WormholeState>(@wormhole).governance_contract
}
public fun get_governance_chain(): U16 acquires WormholeState {
borrow_global<WormholeState>(@wormhole).governance_chain_id
}
public fun get_chain_id(): U16 acquires WormholeState {
borrow_global<WormholeState>(@wormhole).chain_id
}
public fun get_message_fee(): u64 acquires WormholeState {
borrow_global<WormholeState>(@wormhole).message_fee
}
/// Provide access to the wormhole contract signer. Be *very* careful who
/// gets access to this!
public(friend) fun wormhole_signer(): signer acquires WormholeState {
account::create_signer_with_capability(&borrow_global<WormholeState>(@wormhole).signer_cap)
}
public(friend) fun new_emitter(): emitter::EmitterCapability acquires WormholeState {
let registry = &mut borrow_global_mut<WormholeState>(@wormhole).emitter_registry;
emitter::new_emitter(registry)
}
}

View File

@ -0,0 +1,71 @@
module wormhole::structs {
use wormhole::u32::{Self, U32};
use std::secp256k1;
use std::timestamp;
friend wormhole::state;
use wormhole::guardian_pubkey::{Self};
struct Signature has key, store, copy, drop {
sig: secp256k1::ECDSASignature,
recovery_id: u8,
guardian_index: u8,
}
struct Guardian has key, store, drop, copy {
address: guardian_pubkey::Address
}
struct GuardianSet has key, store, copy, drop {
index: U32,
guardians: vector<Guardian>,
expiration_time: U32,
}
public fun create_guardian(address: vector<u8>): Guardian {
Guardian {
address: guardian_pubkey::from_bytes(address)
}
}
public fun create_guardian_set(index: U32, guardians: vector<Guardian>): GuardianSet {
GuardianSet {
index: index,
guardians: guardians,
expiration_time: u32::from_u64(0),
}
}
public(friend) fun expire_guardian_set(guardian_set: &mut GuardianSet, delta: U32) {
guardian_set.expiration_time = u32::from_u64(timestamp::now_seconds() + u32::to_u64(delta));
}
public fun unpack_signature(s: &Signature): (secp256k1::ECDSASignature, u8, u8) {
(s.sig, s.recovery_id, s.guardian_index)
}
public fun create_signature(
sig: secp256k1::ECDSASignature,
recovery_id: u8,
guardian_index: u8
): Signature {
Signature{ sig, recovery_id, guardian_index }
}
public fun get_address(guardian: &Guardian): guardian_pubkey::Address {
guardian.address
}
public fun get_guardian_set_index(guardian_set: &GuardianSet): U32 {
guardian_set.index
}
public fun get_guardians(guardian_set: &GuardianSet): vector<Guardian> {
guardian_set.guardians
}
public fun get_guardian_set_expiry(guardian_set: &GuardianSet): U32 {
guardian_set.expiration_time
}
}

View File

@ -0,0 +1,39 @@
module wormhole::u16 {
const MAX_U16: u64 = (1 << 16) - 1;
const E_OVERFLOW: u64 = 0x0;
struct U16 has key, store, copy, drop {
number: u64
}
fun check_overflow(u: &U16) {
assert!(u.number <= MAX_U16, E_OVERFLOW)
}
public fun from_u64(number: u64): U16 {
let u = U16 { number };
check_overflow(&u);
u
}
public fun to_u64(u: U16): u64 {
u.number
}
public fun split_u8(number: U16): (u8, u8) {
let U16 { number } = number;
let v0: u8 = ((number >> 8) % (0xFF + 1) as u8);
let v1: u8 = (number % (0xFF + 1) as u8);
(v0, v1)
}
#[test]
public fun test_split_u8() {
let u = from_u64(0x1234);
let (v0, v1) = split_u8(u);
assert!(v0 == 0x12, 0);
assert!(v1 == 0x34, 0);
}
}

View File

@ -0,0 +1,887 @@
/// The implementation of large numbers written in Move language.
/// Code derived from original work by Andrew Poelstra <apoelstra@wpsoftware.net>
///
/// Rust Bitcoin Library
/// Written in 2014 by
/// Andrew Poelstra <apoelstra@wpsoftware.net>
///
/// To the extent possible under law, the author(s) have dedicated all
/// copyright and related and neighboring rights to this software to
/// the public domain worldwide. This software is distributed without
/// any warranty.
///
/// Simplified impl by Parity Team - https://github.com/paritytech/parity-common/blob/master/uint/src/uint.rs
///
/// Features:
/// * mul
/// * div
/// * add
/// * sub
/// * shift left
/// * shift right
/// * compare
/// * if math overflows the contract crashes.
///
/// Would be nice to help with the following TODO list:
/// * pow() , sqrt().
/// * math funcs that don't abort on overflows, but just returns reminders.
/// * Export of low_u128 (see original implementation).
/// * Export of low_u64 (see original implementation).
/// * Gas Optimisation:
/// * We can optimize by replacing bytecode, as far as we know Move VM itself support slices, so probably
/// we can try to replace parts works with (`v0`,`v1`,`v2`,`v3` etc) works.
/// * More?
/// * More tests (see current tests and TODOs i left):
/// * u256_arithmetic_test - https://github.com/paritytech/bigint/blob/master/src/uint.rs#L1338
/// * More from - https://github.com/paritytech/bigint/blob/master/src/uint.rs
/// * Division:
/// * Could be improved with div_mod_small (current version probably would took a lot of resources for small numbers).
/// * Also could be improved with Knuth, TAOCP, Volume 2, section 4.3.1, Algorithm D (see link to Parity above).
module wormhole::u256 {
// Errors.
/// When can't cast `U256` to `u128` (e.g. number too large).
const ECAST_OVERFLOW: u64 = 0;
/// When trying to get or put word into U256 but it's out of index.
const EWORDS_OVERFLOW: u64 = 1;
/// When math overflows.
const EOVERFLOW: u64 = 2;
/// When attempted to divide by zero.
const EDIV_BY_ZERO: u64 = 3;
/// TODO: removed some functionality that the prover was breaking on.
/// In order to keep the functions backwards compatible, we keep the
/// signatures but revert immediately.
/// A better solution would be figuring out a way to skip checking them
/// in the prover, and just restore the original functionality.
const EUNSUPPORTED: u64 = 4;
// Constants.
/// Max `u64` value.
const U64_MAX: u128 = 18446744073709551615;
/// Max `u128` value.
const U128_MAX: u128 = 340282366920938463463374607431768211455;
/// Total words in `U256` (64 * 4 = 256).
const WORDS: u64 = 4;
/// When both `U256` equal.
const EQUAL: u8 = 0;
/// When `a` is less than `b`.
const LESS_THAN: u8 = 1;
/// When `b` is greater than `b`.
const GREATER_THAN: u8 = 2;
// Data structs.
/// The `U256` resource.
/// Contains 4 u64 numbers.
struct U256 has copy, drop, store {
v0: u64,
v1: u64,
v2: u64,
v3: u64,
}
/// Double `U256` used for multiple (to store overflow).
struct DU256 has copy, drop, store {
v0: u64,
v1: u64,
v2: u64,
v3: u64,
v4: u64,
v5: u64,
v6: u64,
v7: u64,
}
// Public functions.
/// Adds two `U256` and returns sum.
public fun add(a: U256, b: U256): U256 {
let ret = zero();
let carry = 0u64;
let i = 0;
while (i < WORDS) {
let a1 = get(&a, i);
let b1 = get(&b, i);
if (carry != 0) {
let (res1, is_overflow1) = overflowing_add(a1, b1);
let (res2, is_overflow2) = overflowing_add(res1, carry);
put(&mut ret, i, res2);
carry = 0;
if (is_overflow1) {
carry = carry + 1;
};
if (is_overflow2) {
carry = carry + 1;
}
} else {
let (res, is_overflow) = overflowing_add(a1, b1);
put(&mut ret, i, res);
carry = 0;
if (is_overflow) {
carry = 1;
};
};
i = i + 1;
};
assert!(carry == 0, EOVERFLOW);
ret
}
/// Convert `U256` to `u128` value if possible (otherwise it aborts).
public fun as_u128(a: U256): u128 {
assert!(a.v2 == 0 && a.v3 == 0, ECAST_OVERFLOW);
((a.v1 as u128) << 64) + (a.v0 as u128)
}
/// Convert `U256` to `u64` value if possible (otherwise it aborts).
public fun as_u64(a: U256): u64 {
assert!(a.v1 == 0 && a.v2 == 0 && a.v3 == 0, ECAST_OVERFLOW);
a.v0
}
/// Compares two `U256` numbers.
public fun compare(a: &U256, b: &U256): u8 {
let i = WORDS;
while (i > 0) {
i = i - 1;
let a1 = get(a, i);
let b1 = get(b, i);
if (a1 != b1) {
if (a1 < b1) {
return LESS_THAN
} else {
return GREATER_THAN
}
}
};
EQUAL
}
/// Returns a `U256` from `u64` value.
public fun from_u64(val: u64): U256 {
from_u128((val as u128))
}
/// Returns a `U256` from `u128` value.
public fun from_u128(val: u128): U256 {
let (a2, a1) = split_u128(val);
U256 {
v0: a1,
v1: a2,
v2: 0,
v3: 0,
}
}
/// Multiples two `U256`.
public fun mul(a: U256, b: U256): U256 {
let ret = DU256 {
v0: 0,
v1: 0,
v2: 0,
v3: 0,
v4: 0,
v5: 0,
v6: 0,
v7: 0,
};
let i = 0;
while (i < WORDS) {
let carry = 0u64;
let b1 = get(&b, i);
let j = 0;
while (j < WORDS) {
let a1 = get(&a, j);
if (a1 != 0 || carry != 0) {
let (hi, low) = split_u128((a1 as u128) * (b1 as u128));
let overflow = {
let existing_low = get_d(&ret, i + j);
let (low, o) = overflowing_add(low, existing_low);
put_d(&mut ret, i + j, low);
if (o) {
1
} else {
0
}
};
carry = {
let existing_hi = get_d(&ret, i + j + 1);
let hi = hi + overflow;
let (hi, o0) = overflowing_add(hi, carry);
let (hi, o1) = overflowing_add(hi, existing_hi);
put_d(&mut ret, i + j + 1, hi);
if (o0 || o1) {
1
} else {
0
}
};
};
j = j + 1;
};
i = i + 1;
};
let (r, overflow) = du256_to_u256(ret);
assert!(!overflow, EOVERFLOW);
r
}
/// Subtracts two `U256`, returns result.
public fun sub(a: U256, b: U256): U256 {
let ret = zero();
let carry = 0u64;
let i = 0;
while (i < WORDS) {
let a1 = get(&a, i);
let b1 = get(&b, i);
if (carry != 0) {
let (res1, is_overflow1) = overflowing_sub(a1, b1);
let (res2, is_overflow2) = overflowing_sub(res1, carry);
put(&mut ret, i, res2);
carry = 0;
if (is_overflow1) {
carry = carry + 1;
};
if (is_overflow2) {
carry = carry + 1;
}
} else {
let (res, is_overflow) = overflowing_sub(a1, b1);
put(&mut ret, i, res);
carry = 0;
if (is_overflow) {
carry = 1;
};
};
i = i + 1;
};
assert!(carry == 0, EOVERFLOW);
ret
}
public fun div(_a: U256, _b: U256): U256 {
abort EUNSUPPORTED
}
/// Shift right `a` by `shift`.
public fun shr(a: U256, shift: u8): U256 {
let ret = zero();
let word_shift = (shift as u64) / 64;
let bit_shift = (shift as u64) % 64;
let i = word_shift;
while (i < WORDS) {
let m = get(&a, i) >> (bit_shift as u8);
put(&mut ret, i - word_shift, m);
i = i + 1;
};
if (bit_shift > 0) {
let j = word_shift + 1;
while (j < WORDS) {
let m = get(&ret, j - word_shift - 1) + (get(&a, j) << (64 - (bit_shift as u8)));
put(&mut ret, j - word_shift - 1, m);
j = j + 1;
};
};
ret
}
/// Shift left `a` by `shift`.
public fun shl(a: U256, shift: u8): U256 {
let ret = zero();
let word_shift = (shift as u64) / 64;
let bit_shift = (shift as u64) % 64;
let i = word_shift;
while (i < WORDS) {
let m = get(&a, i - word_shift) << (bit_shift as u8);
put(&mut ret, i, m);
i = i + 1;
};
if (bit_shift > 0) {
let j = word_shift + 1;
while (j < WORDS) {
let m = get(&ret, j) + (get(&a, j - 1 - word_shift) >> (64 - (bit_shift as u8)));
put(&mut ret, j, m);
j = j + 1;
};
};
ret
}
/// Returns `U256` equals to zero.
public fun zero(): U256 {
U256 {
v0: 0,
v1: 0,
v2: 0,
v3: 0,
}
}
// Private functions.
/// Similar to Rust `overflowing_add`.
/// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur.
/// If an overflow would have occurred then the wrapped value is returned.
fun overflowing_add(a: u64, b: u64): (u64, bool) {
let a128 = (a as u128);
let b128 = (b as u128);
let r = a128 + b128;
if (r > U64_MAX) {
// overflow
let overflow = r - U64_MAX - 1;
((overflow as u64), true)
} else {
(((a128 + b128) as u64), false)
}
}
/// Similar to Rust `overflowing_sub`.
/// Returns a tuple of the addition along with a boolean indicating whether an arithmetic overflow would occur.
/// If an overflow would have occurred then the wrapped value is returned.
fun overflowing_sub(a: u64, b: u64): (u64, bool) {
if (a < b) {
let r = b - a;
((U64_MAX as u64) - r + 1, true)
} else {
(a - b, false)
}
}
/// Extracts two `u64` from `a` `u128`.
fun split_u128(a: u128): (u64, u64) {
let a1 = ((a >> 64) as u64);
let a2 = ((a % (0xFFFFFFFFFFFFFFFF + 1)) as u64);
(a1, a2)
}
/// Get word from `a` by index `i`.
public fun get(a: &U256, i: u64): u64 {
if (i == 0) {
a.v0
} else if (i == 1) {
a.v1
} else if (i == 2) {
a.v2
} else if (i == 3) {
a.v3
} else {
abort EWORDS_OVERFLOW
}
}
/// Get word from `DU256` by index.
fun get_d(a: &DU256, i: u64): u64 {
if (i == 0) {
a.v0
} else if (i == 1) {
a.v1
} else if (i == 2) {
a.v2
} else if (i == 3) {
a.v3
} else if (i == 4) {
a.v4
} else if (i == 5) {
a.v5
} else if (i == 6) {
a.v6
} else if (i == 7) {
a.v7
} else {
abort EWORDS_OVERFLOW
}
}
/// Put new word `val` into `U256` by index `i`.
fun put(a: &mut U256, i: u64, val: u64) {
if (i == 0) {
a.v0 = val;
} else if (i == 1) {
a.v1 = val;
} else if (i == 2) {
a.v2 = val;
} else if (i == 3) {
a.v3 = val;
} else {
abort EWORDS_OVERFLOW
}
}
/// Put new word into `DU256` by index `i`.
fun put_d(a: &mut DU256, i: u64, val: u64) {
if (i == 0) {
a.v0 = val;
} else if (i == 1) {
a.v1 = val;
} else if (i == 2) {
a.v2 = val;
} else if (i == 3) {
a.v3 = val;
} else if (i == 4) {
a.v4 = val;
} else if (i == 5) {
a.v5 = val;
} else if (i == 6) {
a.v6 = val;
} else if (i == 7) {
a.v7 = val;
} else {
abort EWORDS_OVERFLOW
}
}
/// Convert `DU256` to `U256`.
fun du256_to_u256(a: DU256): (U256, bool) {
let b = U256 {
v0: a.v0,
v1: a.v1,
v2: a.v2,
v3: a.v3,
};
let overflow = false;
if (a.v4 != 0 || a.v5 != 0 || a.v6 != 0 || a.v7 != 0) {
overflow = true;
};
(b, overflow)
}
// Tests.
#[test]
fun test_get_d() {
let a = DU256 {
v0: 1,
v1: 2,
v2: 3,
v3: 4,
v4: 5,
v5: 6,
v6: 7,
v7: 8,
};
assert!(get_d(&a, 0) == 1, 0);
assert!(get_d(&a, 1) == 2, 1);
assert!(get_d(&a, 2) == 3, 2);
assert!(get_d(&a, 3) == 4, 3);
assert!(get_d(&a, 4) == 5, 4);
assert!(get_d(&a, 5) == 6, 5);
assert!(get_d(&a, 6) == 7, 6);
assert!(get_d(&a, 7) == 8, 7);
}
#[test]
#[expected_failure(abort_code = 1)]
fun test_get_d_overflow() {
let a = DU256 {
v0: 1,
v1: 2,
v2: 3,
v3: 4,
v4: 5,
v5: 6,
v6: 7,
v7: 8,
};
get_d(&a, 8);
}
#[test]
fun test_put_d() {
let a = DU256 {
v0: 1,
v1: 2,
v2: 3,
v3: 4,
v4: 5,
v5: 6,
v6: 7,
v7: 8,
};
put_d(&mut a, 0, 10);
put_d(&mut a, 1, 20);
put_d(&mut a, 2, 30);
put_d(&mut a, 3, 40);
put_d(&mut a, 4, 50);
put_d(&mut a, 5, 60);
put_d(&mut a, 6, 70);
put_d(&mut a, 7, 80);
assert!(get_d(&a, 0) == 10, 0);
assert!(get_d(&a, 1) == 20, 1);
assert!(get_d(&a, 2) == 30, 2);
assert!(get_d(&a, 3) == 40, 3);
assert!(get_d(&a, 4) == 50, 4);
assert!(get_d(&a, 5) == 60, 5);
assert!(get_d(&a, 6) == 70, 6);
assert!(get_d(&a, 7) == 80, 7);
}
#[test]
#[expected_failure(abort_code = 1)]
fun test_put_d_overflow() {
let a = DU256 {
v0: 1,
v1: 2,
v2: 3,
v3: 4,
v4: 5,
v5: 6,
v6: 7,
v7: 8,
};
put_d(&mut a, 8, 0);
}
#[test]
fun test_du256_to_u256() {
let a = DU256 {
v0: 255,
v1: 100,
v2: 50,
v3: 300,
v4: 0,
v5: 0,
v6: 0,
v7: 0,
};
let (m, overflow) = du256_to_u256(a);
assert!(!overflow, 0);
assert!(m.v0 == a.v0, 1);
assert!(m.v1 == a.v1, 2);
assert!(m.v2 == a.v2, 3);
assert!(m.v3 == a.v3, 4);
a.v4 = 100;
a.v5 = 5;
let (m, overflow) = du256_to_u256(a);
assert!(overflow, 5);
assert!(m.v0 == a.v0, 6);
assert!(m.v1 == a.v1, 7);
assert!(m.v2 == a.v2, 8);
assert!(m.v3 == a.v3, 9);
}
#[test]
fun test_get() {
let a = U256 {
v0: 1,
v1: 2,
v2: 3,
v3: 4,
};
assert!(get(&a, 0) == 1, 0);
assert!(get(&a, 1) == 2, 1);
assert!(get(&a, 2) == 3, 2);
assert!(get(&a, 3) == 4, 3);
}
#[test]
#[expected_failure(abort_code = 1)]
fun test_get_aborts() {
let _ = get(&zero(), 4);
}
#[test]
fun test_put() {
let a = zero();
put(&mut a, 0, 255);
assert!(get(&a, 0) == 255, 0);
put(&mut a, 1, (U64_MAX as u64));
assert!(get(&a, 1) == (U64_MAX as u64), 1);
put(&mut a, 2, 100);
assert!(get(&a, 2) == 100, 2);
put(&mut a, 3, 3);
assert!(get(&a, 3) == 3, 3);
put(&mut a, 2, 0);
assert!(get(&a, 2) == 0, 4);
}
#[test]
#[expected_failure(abort_code = 1)]
fun test_put_overflow() {
let a = zero();
put(&mut a, 6, 255);
}
#[test]
fun test_from_u128() {
let i = 0;
while (i < 1024) {
let big = from_u128(i);
assert!(as_u128(big) == i, 0);
i = i + 1;
};
}
#[test]
fun test_add() {
let a = from_u128(1000);
let b = from_u128(500);
let s = as_u128(add(a, b));
assert!(s == 1500, 0);
a = from_u128(U64_MAX);
b = from_u128(U64_MAX);
s = as_u128(add(a, b));
assert!(s == (U64_MAX + U64_MAX), 1);
}
#[test]
#[expected_failure(abort_code = 2)]
fun test_add_overflow() {
let max = (U64_MAX as u64);
let a = U256 {
v0: max,
v1: max,
v2: max,
v3: max
};
let _ = add(a, from_u128(1));
}
#[test]
fun test_sub() {
let a = from_u128(1000);
let b = from_u128(500);
let s = as_u128(sub(a, b));
assert!(s == 500, 0);
}
#[test]
#[expected_failure(abort_code = 2)]
fun test_sub_overflow() {
let a = from_u128(0);
let b = from_u128(1);
let _ = sub(a, b);
}
#[test]
#[expected_failure(abort_code = 0)]
fun test_too_big_to_cast_to_u128() {
let a = from_u128(U128_MAX);
let b = from_u128(U128_MAX);
let _ = as_u128(add(a, b));
}
#[test]
fun test_overflowing_add() {
let (n, z) = overflowing_add(10, 10);
assert!(n == 20, 0);
assert!(!z, 1);
(n, z) = overflowing_add((U64_MAX as u64), 1);
assert!(n == 0, 2);
assert!(z, 3);
(n, z) = overflowing_add((U64_MAX as u64), 10);
assert!(n == 9, 4);
assert!(z, 5);
(n, z) = overflowing_add(5, 8);
assert!(n == 13, 6);
assert!(!z, 7);
}
#[test]
fun test_overflowing_sub() {
let (n, z) = overflowing_sub(10, 5);
assert!(n == 5, 0);
assert!(!z, 1);
(n, z) = overflowing_sub(0, 1);
assert!(n == (U64_MAX as u64), 2);
assert!(z, 3);
(n, z) = overflowing_sub(10, 10);
assert!(n == 0, 4);
assert!(!z, 5);
}
#[test]
fun test_split_u128() {
let (a1, a2) = split_u128(100);
assert!(a1 == 0, 0);
assert!(a2 == 100, 1);
(a1, a2) = split_u128(U64_MAX + 1);
assert!(a1 == 1, 2);
assert!(a2 == 0, 3);
}
#[test]
fun test_mul() {
let a = from_u128(285);
let b = from_u128(375);
let c = as_u128(mul(a, b));
assert!(c == 106875, 0);
a = from_u128(0);
b = from_u128(1);
c = as_u128(mul(a, b));
assert!(c == 0, 1);
a = from_u128(U64_MAX);
b = from_u128(2);
c = as_u128(mul(a, b));
assert!(c == 36893488147419103230, 2);
}
#[test]
#[expected_failure(abort_code = 2)]
fun test_mul_overflow() {
let max = (U64_MAX as u64);
let a = U256 {
v0: max,
v1: max,
v2: max,
v3: max,
};
let _ = mul(a, from_u128(2));
}
#[test]
fun test_zero() {
let a = as_u128(zero());
assert!(a == 0, 0);
let a = zero();
assert!(a.v0 == 0, 1);
assert!(a.v1 == 0, 2);
assert!(a.v2 == 0, 3);
assert!(a.v3 == 0, 4);
}
#[test]
fun test_from_u64() {
let a = as_u128(from_u64(100));
assert!(a == 100, 0);
// TODO: more tests.
}
#[test]
fun test_compare() {
let a = from_u128(1000);
let b = from_u128(50);
let cmp = compare(&a, &b);
assert!(cmp == 2, 0);
a = from_u128(100);
b = from_u128(100);
cmp = compare(&a, &b);
assert!(cmp == 0, 1);
a = from_u128(50);
b = from_u128(75);
cmp = compare(&a, &b);
assert!(cmp == 1, 2);
}
#[test]
fun test_shift_left() {
let a = from_u128(100);
let b = shl(a, 2);
assert!(as_u128(b) == 400, 0);
// TODO: more shift left tests.
}
#[test]
fun test_shift_right() {
let a = from_u128(100);
let b = shr(a, 2);
assert!(as_u128(b) == 25, 0);
// TODO: more shift right tests.
}
#[test]
fun test_as_u64() {
let _ = as_u64(from_u64((U64_MAX as u64)));
let _ = as_u64(from_u128(1));
}
#[test]
#[expected_failure(abort_code=0)]
fun test_as_u64_overflow() {
let _ = as_u64(from_u128(U128_MAX));
}
}

View File

@ -0,0 +1,43 @@
module wormhole::u32 {
const MAX_U32: u64 = (1 << 32) - 1;
const E_OVERFLOW: u64 = 0x0;
struct U32 has key, store, copy, drop {
number: u64
}
fun check_overflow(u: &U32) {
assert!(u.number <= MAX_U32, E_OVERFLOW)
}
public fun from_u64(number: u64): U32 {
let u = U32 { number };
check_overflow(&u);
u
}
public fun to_u64(u: U32): u64 {
u.number
}
public fun split_u8(number: U32): (u8, u8, u8, u8) {
let U32 { number } = number;
let v0: u8 = ((number >> 24) % (0xFF + 1) as u8);
let v1: u8 = ((number >> 16) % (0xFF + 1) as u8);
let v2: u8 = ((number >> 8) % (0xFF + 1) as u8);
let v3: u8 = (number % (0xFF + 1) as u8);
(v0, v1, v2, v3)
}
#[test]
public fun test_split_u8() {
let u = from_u64(0x12345678);
let (v0, v1, v2, v3) = split_u8(u);
assert!(v0 == 0x12, 0);
assert!(v1 == 0x34, 0);
assert!(v2 == 0x56, 0);
assert!(v3 == 0x78, 0);
}
}

View File

@ -0,0 +1,442 @@
module wormhole::vaa {
use std::vector;
use aptos_std::secp256k1;
use wormhole::u16::{U16};
use wormhole::u32::{U32};
use wormhole::deserialize;
use wormhole::cursor;
use wormhole::guardian_pubkey;
use wormhole::structs::{
Guardian,
GuardianSet,
Signature,
create_signature,
get_guardians,
unpack_signature,
get_address,
};
use wormhole::state;
use wormhole::external_address::{Self, ExternalAddress};
use wormhole::keccak256::keccak256;
friend wormhole::guardian_set_upgrade;
friend wormhole::contract_upgrade;
const E_NO_QUORUM: u64 = 0x0;
const E_TOO_MANY_SIGNATURES: u64 = 0x1;
const E_INVALID_SIGNATURE: u64 = 0x2;
const E_GUARDIAN_SET_EXPIRED: u64 = 0x3;
const E_INVALID_GOVERNANCE_CHAIN: u64 = 0x4;
const E_INVALID_GOVERNANCE_EMITTER: u64 = 0x5;
const E_WRONG_VERSION: u64 = 0x6;
const E_NON_INCREASING_SIGNERS: u64 = 0x7;
const E_OLD_GUARDIAN_SET_GOVERNANCE: u64 = 0x8;
struct VAA {
/// Header
guardian_set_index: U32,
signatures: vector<Signature>,
/// Body
timestamp: U32,
nonce: U32,
emitter_chain: U16,
emitter_address: ExternalAddress,
sequence: u64,
consistency_level: u8,
hash: vector<u8>, // 32 bytes
payload: vector<u8>, // variable bytes
}
//break
#[test_only]
public fun parse_test(bytes: vector<u8>): VAA {
parse(bytes)
}
/// Parses a VAA.
/// Does not do any verification, and is thus private.
/// This ensures the invariant that if an external module receives a `VAA`
/// object, its signatures must have been verified, because the only public
/// function that returns a VAA is `parse_and_verify`
fun parse(bytes: vector<u8>): VAA {
let cur = cursor::init(bytes);
let version = deserialize::deserialize_u8(&mut cur);
assert!(version == 1, E_WRONG_VERSION);
let guardian_set_index = deserialize::deserialize_u32(&mut cur);
let signatures_len = deserialize::deserialize_u8(&mut cur);
let signatures = vector::empty<Signature>();
while (signatures_len > 0) {
let guardian_index = deserialize::deserialize_u8(&mut cur);
let sig = deserialize::deserialize_vector(&mut cur, 64);
let recovery_id = deserialize::deserialize_u8(&mut cur);
let sig: secp256k1::ECDSASignature = secp256k1::ecdsa_signature_from_bytes(sig);
vector::push_back(&mut signatures, create_signature(sig, recovery_id, guardian_index));
signatures_len = signatures_len - 1;
};
let body = cursor::rest(cur);
let hash = keccak256(keccak256(body));
let cur = cursor::init(body);
let timestamp = deserialize::deserialize_u32(&mut cur);
let nonce = deserialize::deserialize_u32(&mut cur);
let emitter_chain = deserialize::deserialize_u16(&mut cur);
let emitter_address = external_address::deserialize(&mut cur);
let sequence = deserialize::deserialize_u64(&mut cur);
let consistency_level = deserialize::deserialize_u8(&mut cur);
let payload = cursor::rest(cur);
VAA {
guardian_set_index,
signatures,
timestamp,
nonce,
emitter_chain,
emitter_address,
sequence,
consistency_level,
hash,
payload,
}
}
public fun get_guardian_set_index(vaa: &VAA): U32 {
vaa.guardian_set_index
}
public fun get_timestamp(vaa: &VAA): U32 {
vaa.timestamp
}
public fun get_payload(vaa: &VAA): vector<u8> {
vaa.payload
}
public fun get_hash(vaa: &VAA): vector<u8> {
vaa.hash
}
public fun get_emitter_chain(vaa: &VAA): U16 {
vaa.emitter_chain
}
public fun get_emitter_address(vaa: &VAA): ExternalAddress {
vaa.emitter_address
}
public fun get_sequence(vaa: &VAA): u64 {
vaa.sequence
}
public fun get_consistency_level(vaa: &VAA): u8 {
vaa.consistency_level
}
// break
public fun destroy(vaa: VAA): vector<u8> {
let VAA {
guardian_set_index: _,
signatures: _,
timestamp: _,
nonce: _,
emitter_chain: _,
emitter_address: _,
sequence: _,
consistency_level: _,
hash: _,
payload,
} = vaa;
payload
}
/// Verifies the signatures of a VAA.
/// It's private, because there's no point calling it externally, since VAAs
/// external to this module have already been verified (by construction).
fun verify(vaa: &VAA, guardian_set: &GuardianSet) {
assert!(state::guardian_set_is_active(guardian_set), E_GUARDIAN_SET_EXPIRED);
let guardians = get_guardians(guardian_set);
let hash = vaa.hash;
let sigs_len = vector::length<Signature>(&vaa.signatures);
let guardians_len = vector::length<Guardian>(&guardians);
assert!(sigs_len >= quorum(guardians_len), E_NO_QUORUM);
let sig_i = 0;
let last_index = 0;
while (sig_i < sigs_len) {
let (sig, recovery_id, guardian_index) = unpack_signature(vector::borrow(&vaa.signatures, sig_i));
// Ensure that the provided signatures are strictly increasing.
// This check makes sure that no duplicate signers occur. The
// increasing order is guaranteed by the guardians, or can always be
// reordered by the client.
assert!(sig_i == 0 || guardian_index > last_index, E_NON_INCREASING_SIGNERS);
last_index = guardian_index;
let address = guardian_pubkey::from_signature(hash, recovery_id, &sig);
let cur_guardian = vector::borrow<Guardian>(&guardians, (guardian_index as u64));
let cur_address = get_address(cur_guardian);
assert!(address == cur_address, E_INVALID_SIGNATURE);
sig_i = sig_i + 1;
};
}
/// Parses and verifies the signatures of a VAA.
/// NOTE: this is the only public function that returns a VAA, and it should
/// be kept that way. This ensures that if an external module receives a
/// `VAA`, it has been verified.
public fun parse_and_verify(bytes: vector<u8>): VAA {
let vaa = parse(bytes);
let guardian_set = state::get_guardian_set(vaa.guardian_set_index);
verify(&vaa, &guardian_set);
vaa
}
/// Aborts if the VAA is not governance (i.e. sent from the governance
/// emitter on the governance chain)
public fun assert_governance(vaa: &VAA) {
let latest_guardian_set_index = state::get_current_guardian_set_index();
assert!(vaa.guardian_set_index == latest_guardian_set_index, E_OLD_GUARDIAN_SET_GOVERNANCE);
assert!(vaa.emitter_chain == state::get_governance_chain(), E_INVALID_GOVERNANCE_CHAIN);
assert!(vaa.emitter_address == state::get_governance_contract(), E_INVALID_GOVERNANCE_EMITTER);
}
/// Aborts if the VAA has already been consumed. Marks the VAA as consumed
/// the first time around.
/// Only to be used for core bridge messages. Protocols should implement
/// their own replay protection.
public(friend) fun replay_protect(vaa: &VAA) {
// this calls table::add which aborts if the key already exists
state::set_governance_action_consumed(vaa.hash);
}
/// Returns the minimum number of signatures required for a VAA to be valid.
public fun quorum(num_guardians: u64): u64 {
(num_guardians * 2) / 3 + 1
}
}
#[test_only]
module wormhole::vaa_test {
use wormhole::guardian_set_upgrade;
use wormhole::wormhole;
use wormhole::vaa;
use wormhole::structs::{create_guardian};
use wormhole::u32;
/// A test VAA signed by the first guardian set (index 0) containing guardian a single
/// guardian beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
/// It's a governance VAA (contract upgrade), so we can test all sorts of
/// properties
const GOV_VAA: vector<u8> = x"010000000001000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a30100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000020b10360000000000000000000000000000000000000000000000000000000000436f7265010016d8f30e4a345ea0fa5df11daac4e1866ee368d253209cf9eda012d915a2db09e6";
/// Identical VAA except it's signed by guardian set 1, and double signed by
/// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
/// Used to test that a single guardian can't supply multiple signatures
const GOV_VAA_DOUBLE_SIGNED: vector<u8> = x"010000000102000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a301000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a30100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000020b10360000000000000000000000000000000000000000000000000000000000436f7265010016d8f30e4a345ea0fa5df11daac4e1866ee368d253209cf9eda012d915a2db09e6";
/// A test VAA signed by the second guardian set (index 1) with the following two guardians:
/// 0: beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
/// 1: 90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
const GOV_VAA_2: vector<u8> = x"0100000001020052da07c7ba7d58661e22922a1130e75732f454e81086330f9a5337797ee7ee9d703fd55aabc257c4d53d8ab1e471e4eb1f2767bf37cc6d3d6774e2ca3ab429eb00018c9859f14027c2a62563028a2a9bbb30464ce5b86d13728b02fb85b34761d258154bb59bad87908c9b09342efa9045d4420d289bb0144729eb368ec50c45e719010000000100000001000100000000000000000000000000000000000000000000000000000000000000040000000004cdedc90000000000000000000000000000000000000000000000000000000000436f72650100167759324e86f870265b8648ef8d5ef505b2ae99840a616081eb7adc13995204a4";
/// Set up wormhole with the initial guardian
/// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
fun setup() {
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
let _wormhole = wormhole::init_test(
22,
1,
x"0000000000000000000000000000000000000000000000000000000000000004",
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
0
);
}
#[test]
/// Ensures that the GOV_VAA can still be verified after the guardian set
/// upgrade before expiry
public fun test_guardian_set_not_expired() {
setup();
// do an upgrade
guardian_set_upgrade::do_upgrade_test(
u32::from_u64(1),
vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")]);
// fast forward time before expiration
std::timestamp::fast_forward_seconds(80000);
// we still expect this to verify
vaa::destroy(vaa::parse_and_verify(GOV_VAA));
}
#[test]
#[expected_failure(abort_code = 3)] // E_GUARDIAN_SET_EXPIRED
/// Ensures that the GOV_VAA can no longer be verified after the guardian set
/// upgrade after expiry
public fun test_guardian_set_expired() {
setup();
// do an upgrade
guardian_set_upgrade::do_upgrade_test(
u32::from_u64(1),
vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")]);
// fast forward time beyond expiration
std::timestamp::fast_forward_seconds(90000);
// we expect this to fail because the guardian set has expired
vaa::destroy(vaa::parse_and_verify(GOV_VAA));
}
#[test]
#[expected_failure(abort_code = 8)] // E_OLD_GUARDIAN_SET_GOVERNANCE
/// Ensures that governance GOV_VAAs can only be verified by the latest guardian
/// set, even if the signer hasn't expired yet
public fun test_governance_guardian_set_latest() {
setup();
// do an upgrade
guardian_set_upgrade::do_upgrade_test(
u32::from_u64(1),
vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")]);
// fast forward time before expiration
std::timestamp::fast_forward_seconds(80000);
// we still expect this to verify
let vaa = vaa::parse_and_verify(GOV_VAA);
// but fail here
vaa::assert_governance(&vaa);
vaa::destroy(vaa);
}
#[test]
#[expected_failure(abort_code = 5)] // E_INVALID_GOVERNANCE_EMITTER
/// Ensures that governance GOV_VAAs can only be sent from the correct governance emitter
public fun test_invalid_governance_emitter() {
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
let _wormhole = wormhole::init_test(
22,
1,
// Note the different governance emitter address here
x"0000000000000000000000000000000000000000000000000000000000000003",
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
0
);
// we still expect this to verify
let vaa = vaa::parse_and_verify(GOV_VAA);
// but fail here
vaa::assert_governance(&vaa);
vaa::destroy(vaa);
}
#[test]
#[expected_failure(abort_code = 4)] // E_INVALID_GOVERNANCE_CHAIN
/// Ensures that governance GOV_VAAs can only be sent from the correct governance chain
public fun test_invalid_governance_chain() {
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
let _wormhole = wormhole::init_test(
22,
// Note the different governance chain here
2,
x"0000000000000000000000000000000000000000000000000000000000000004",
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
0
);
// we still expect this to verify
let vaa = vaa::parse_and_verify(GOV_VAA);
// but fail here
vaa::assert_governance(&vaa);
vaa::destroy(vaa);
}
#[test]
public fun test_quorum() {
setup();
// do an upgrade
guardian_set_upgrade::do_upgrade_test(
u32::from_u64(1),
vector[
create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"),
create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")
]);
// we expect this to succeed because both guardians signed in the correct order
vaa::destroy(vaa::parse_and_verify(GOV_VAA_2));
}
#[test]
#[expected_failure(abort_code = 0)] // NO_QUORUM
public fun test_no_quorum() {
setup();
// do an upgrade
guardian_set_upgrade::do_upgrade_test(
u32::from_u64(1),
vector[
create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"),
create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"),
create_guardian(x"5e1487f35515d02a92753504a8d75471b9f49edb")
]);
// we expect this to fail because we don't have enough signatures
vaa::destroy(vaa::parse_and_verify(GOV_VAA_2));
}
#[test]
#[expected_failure(abort_code = 7)] // E_NON_INCREASING_SIGNERS
public fun test_double_signed() {
setup();
// do an upgrade
guardian_set_upgrade::do_upgrade_test(
u32::from_u64(1),
vector[
create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"),
create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"),
]);
// we expect this to fail because
// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe signed this twice
vaa::destroy(vaa::parse_and_verify(GOV_VAA_DOUBLE_SIGNED));
}
#[test]
#[expected_failure(abort_code = 2)] // E_INVALID_SIGNATURE
public fun test_out_of_order_signers() {
setup();
// do an upgrade
guardian_set_upgrade::do_upgrade_test(
u32::from_u64(1),
vector[
// note that the guardians are set up in the other way arond now
create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"),
create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"),
]);
// we expect this to fail because
// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe signed this twice
vaa::destroy(vaa::parse_and_verify(GOV_VAA_2));
}
}

View File

@ -0,0 +1,202 @@
module wormhole::wormhole {
use aptos_framework::account;
use aptos_framework::coin::{Self, Coin};
use aptos_framework::aptos_coin::{AptosCoin};
use wormhole::structs::{create_guardian, create_guardian_set};
use wormhole::state;
use deployer::deployer;
use wormhole::u16;
use wormhole::u32::{Self, U32};
use wormhole::emitter;
use wormhole::external_address::{Self};
use std::signer;
const E_INSUFFICIENT_FEE: u64 = 0;
// -----------------------------------------------------------------------------
// Sending messages
public fun publish_message(
emitter_cap: &mut emitter::EmitterCapability,
nonce: u64,
payload: vector<u8>,
message_fee: Coin<AptosCoin>
): u64 {
// ensure that provided fee is sufficient to cover message fees
let expected_fee = state::get_message_fee();
assert!(expected_fee <= coin::value(&message_fee), E_INSUFFICIENT_FEE);
// deposit the fees into the wormhole account
coin::deposit(@wormhole, message_fee);
let sequence = emitter::use_sequence(emitter_cap);
state::publish_event(
emitter::get_emitter(emitter_cap),
sequence,
nonce,
payload,
);
sequence
}
// -----------------------------------------------------------------------------
// Emitter registration
public fun register_emitter(): emitter::EmitterCapability {
state::new_emitter()
}
// -----------------------------------------------------------------------------
// Contract initialization
/// Initializes the contract. Note that this function takes additional
/// arguments, so the native `init_module` function (which takes no
/// arguments) cannot be used.
/// Can only be called by the deployer (checked by the
/// `deployer::claim_signer_capability` function).
public entry fun init(
deployer: &signer,
chain_id: u64,
governance_chain_id: u64,
governance_contract: vector<u8>,
initial_guardian: vector<u8>
) {
// account::SignerCapability can't be copied, so once it's stored into
// state, the init function can no longer be called (since
// the deployer signer capability must have been unlocked).
let signer_cap = deployer::claim_signer_capability(deployer, @wormhole);
let message_fee = 0;
init_internal(
signer_cap,
chain_id,
governance_chain_id,
governance_contract,
initial_guardian,
u32::from_u64(86400),
message_fee
)
}
fun init_internal(
signer_cap: account::SignerCapability,
chain_id: u64,
governance_chain_id: u64,
governance_contract: vector<u8>,
initial_guardian: vector<u8>,
guardian_set_expiry: U32,
message_fee: u64,
) {
let wormhole = account::create_signer_with_capability(&signer_cap);
state::init_wormhole_state(
&wormhole,
u16::from_u64(chain_id),
u16::from_u64(governance_chain_id),
external_address::from_bytes(governance_contract),
guardian_set_expiry,
message_fee,
signer_cap
);
state::init_message_handles(&wormhole);
state::store_guardian_set(
create_guardian_set(
u32::from_u64(0),
vector[create_guardian(initial_guardian)]
)
);
// register wormhole to be able to receive fees
// `aptos_account::create_account` is a permissionless operation that
// performs aptos coin registration too, so it's possible that this is
// already performed, in which case we just skip
// (thanks ottersec for point this out)
if (!coin::is_account_registered<AptosCoin>(signer::address_of(&wormhole))){
coin::register<AptosCoin>(&wormhole);
};
}
#[test_only]
/// Initialise a dummy contract for testing. Returns the wormhole signer.
public fun init_test(
chain_id: u64,
governance_chain_id: u64,
governance_contract: vector<u8>,
initial_guardian: vector<u8>,
message_fee: u64,
): signer {
let deployer = account::create_account_for_test(@deployer);
let (wormhole, signer_cap) = account::create_resource_account(&deployer, b"wormhole");
init_internal(
signer_cap,
chain_id,
governance_chain_id,
governance_contract,
initial_guardian,
u32::from_u64(86400),
message_fee
);
wormhole
}
}
#[test_only]
module wormhole::wormhole_test {
use std::hash;
use wormhole::wormhole;
use wormhole::keccak256::keccak256;
use aptos_framework::aptos_coin::{Self};
use aptos_framework::coin;
// public so we an re-use this in token_bridge test
public fun setup(message_fee: u64) {
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
wormhole::init_test(
22,
1,
x"0000000000000000000000000000000000000000000000000000000000000004",
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
message_fee
);
}
#[test]
public fun test_hash() {
assert!(hash::sha3_256(vector[0]) == x"5d53469f20fef4f8eab52b88044ede69c77a6a68a60728609fc4a65ff531e7d0", 0);
assert!(keccak256(vector[0]) == x"bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", 0);
}
#[test(aptos_framework = @aptos_framework)]
public fun test_publish_message(aptos_framework: &signer) {
setup(100);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(aptos_framework);
let fees = coin::mint(100, &mint_cap);
let emitter_cap = wormhole::register_emitter();
wormhole::publish_message(
&mut emitter_cap,
0,
b"hi mom",
fees
);
//TODO - check if event is actually emitted?
wormhole::emitter::destroy_emitter_cap(emitter_cap);
coin::destroy_mint_cap(mint_cap);
coin::destroy_burn_cap(burn_cap);
}
#[test]
#[expected_failure(abort_code = 0x0)]
public fun test_publish_message_insufficient_fee() {
setup(100);
let emitter_cap = wormhole::register_emitter();
wormhole::publish_message(
&mut emitter_cap,
0,
b"hi mom",
coin::zero()
);
wormhole::emitter::destroy_emitter_cap(emitter_cap);
}
}