aptos/contracts: implement wormhole
This commit is contained in:
parent
b37efa4c1b
commit
909238c51b
|
@ -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
|
|
@ -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 = "_"
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue