Governance-based contract upgrades on Aptos

Introduction

Contract deployment and upgrades on Aptos

public entry fun publish_package_txn(owner: &signer, metadata_serialized: vector<u8>, code: vector<vector<u8>>)

Resource accounts on Aptos

public fun create_resource_account(source: &signer, seed: vector<u8>): (signer, SignerCapability)
struct SignerCapability has drop, store { account: address }
public fun create_signer_with_capability(capability: &SignerCapability): signer

Deployer contract

public entry fun deploy_derived(
deployer: &signer,
metadata_serialized: vector<u8>,
code: vector<vector<u8>>,
seed: vector<u8>
) acquires DeployingSignerCapability {
let deployer_address = signer::address_of(deployer);
let (resource_signer, signer_cap) = account::create_resource_account(deployer, seed);
move_to(&resource_signer, DeployingSignerCapability { signer_cap, deployer: deployer_address });
code::publish_package_txn(&resource_signer, metadata_serialized, code);
}
struct DeployingSignerCapability has key {
signer_cap: account::SignerCapability,
deployer: address,
}
public fun claim_signer_capability(
caller: &signer,
resource: address
): account::SignerCapability acquires DeployingSignerCapability {
assert!(exists<DeployingSignerCapability>(resource), E_NO_DEPLOYING_SIGNER_CAPABILITY);
let DeployingSignerCapability { signer_cap, deployer } = move_from<DeployingSignerCapability>(resource);
let caller_addr = signer::address_of(caller);
assert!(caller_addr == deployer, E_INVALID_DEPLOYER);
signer_cap
}
public entry fun init(
deployer: &signer,
chain_id: u64,
governance_chain_id: u64,
governance_contract: vector<u8>,
initial_guardians: vector<vector<u8>>
) {
let signer_cap = deployer::claim_signer_capability(deployer, @wormhole);
...
struct WormholeState has key {
...
/// The signer capability for wormhole itself
signer_cap: account::SignerCapability,
...
}
public(friend) fun wormhole_signer(): signer acquires WormholeState {
account::create_signer_with_capability(&borrow_global<WormholeState>(@wormhole).signer_cap)
}
friend wormhole::contract_upgrade;

Contract upgrades

Upgrade authorisation

public entry fun submit_vaa_entry(vaa: vector<u8>) acquires UpgradeAuthorized {
let vaa = vaa::parse_and_verify(vaa);
vaa::assert_governance(&vaa);
vaa::replay_protect(&vaa);
let hash = parse_payload(vaa::destroy(vaa));
    authorize_upgrade(hash);
}
fun authorize_upgrade(hash: vector<u8>) acquires UpgradeAuthorized {
let wormhole = state::wormhole_signer();
if (exists<UpgradeAuthorized>(@wormhole)) {
let UpgradeAuthorized { hash: _ } = move_from<UpgradeAuthorized>(@wormhole);
};
move_to(&wormhole, UpgradeAuthorized { hash });
}
struct UpgradeAuthorized has key {
hash: vector<u8>
}

Performing the upgrade

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);
    // reconstruct the bytecode hash and check that it matches the
// authorized hash
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);
}

The off-chain part

$ aptos move compile --save-metadata --included-artifacts none --named-addresses wormhole=0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625,deployer=0x0108bc32f7de18a5f6e1e7d6ee7aff9f5fc858d0d87ac0da94dd8d2a5d267d6b 2&>/dev/null
{
"Result": [
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::u32",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::u16",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::keccak256",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::guardian_pubkey",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::structs",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::set",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::u256",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::serialize",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::cursor",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::deserialize",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::external_address",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::emitter",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::vaa",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::contract_upgrade",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::guardian_set_upgrade",
"5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::wormhole"
]
}
function buildPackage(dir: string, addrs?: string): Package {
const named_addresses =
addrs
? ["--named-addresses", addrs]
: [];
const aptos = spawnSync("aptos",
["move", "compile", "--save-metadata", "--included-artifacts", "none", "--package-dir", dir, ...named_addresses])
// error handling elided
  const result: any = JSON.parse(aptos.stdout.toString('utf8'))
const buildDirs =
fs.readdirSync(`${dir}/build`, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
// error handling elided
const buildDir = `${dir}/build/${buildDirs[0]}`
return {
meta_file: `${buildDir}/package-metadata.bcs`,
mv_files: result["Result"].map((mod: string) => `${buildDir}/bytecode_modules/${mod.split("::")[1]}.mv`)
}
}
function serializePackage(meta_file: string, mv_files: string[]): PackageBCS {
const metaBytes = fs.readFileSync(p.meta_file);
const packageMetadataSerializer = new BCS.Serializer();
packageMetadataSerializer.serializeBytes(metaBytes)
const serializedPackageMetadata = packageMetadataSerializer.getBytes();
  const modules = p.mv_files.map(file => fs.readFileSync(file))
const serializer = new BCS.Serializer();
serializer.serializeU32AsUleb128(modules.length);
modules.forEach(module => serializer.serializeBytes(module));
const serializedModules = serializer.getBytes();
const hashes = [metaBytes].concat(modules).map((x) => Buffer.from(sha3.keccak256(x), "hex"));
const codeHash = Buffer.from(sha3.keccak256(Buffer.concat(hashes)), "hex")
return {
meta: serializedPackageMetadata,
bytecodes: serializedModules,
codeHash
}
}

Closing thoughts

--

--

Cross-chain interoperability protocol connecting high value blockchains

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Wormhole

Cross-chain interoperability protocol connecting high value blockchains