Upgrade contract & fix deployment script (#447)

* merge

* stuff

* gr

* config

* config

* config

* config

* config

* config

* config

* cleanup

* add commands

* cleaunp

* fix

* use u64

Co-authored-by: Jayant Krishnamurthy <jkrishnamurthy@jumptrading.com>
Co-authored-by: Jayant Krishnamurthy <jayant@jumpcrypto.com>
This commit is contained in:
Jayant Krishnamurthy 2022-12-30 07:36:55 -08:00 committed by GitHub
parent 6833f41424
commit cdf99f4c60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 56 deletions

View File

@ -21,7 +21,7 @@ First, build the contracts within [the current directory](./):
bash build.sh
```
This command will build and save the Pyth contract in the `artifact` directory.
This command will build and save the Pyth contract in the `artifacts` directory.
Then, to deploy the Pyth contract (`pyth_cosmwasm.wasm`), run the following command in the `tools` directory:

View File

@ -34,8 +34,10 @@ use {
entry_point,
has_coins,
to_binary,
Addr,
Binary,
Coin,
CosmosMsg,
Deps,
DepsMut,
Env,
@ -46,6 +48,7 @@ use {
Response,
StdResult,
Timestamp,
WasmMsg,
WasmQuery,
},
p2w_sdk::BatchPriceAttestation,
@ -67,9 +70,19 @@ use {
},
};
/// Migration code that runs once when the contract is upgraded. On upgrade, the migrate
/// function in the *new* code version is run, which allows the new code to update the on-chain
/// state before any of its other functions are invoked.
///
/// After the upgrade is complete, the code in this function can be deleted (and replaced with
/// different code for the next migration).
///
/// Most upgrades won't require any special migration logic. In those cases,
/// this function can safely be implemented as:
/// `Ok(Response::default())`
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::new())
Ok(Response::default())
}
#[cfg_attr(not(feature = "library"), entry_point)]
@ -182,9 +195,11 @@ fn execute_governance_instruction(
}
let response = match instruction.action {
UpgradeContract { .. } => {
// FIXME: implement this
Err(PythContractError::InvalidGovernancePayload)?
UpgradeContract { code_id } => {
if instruction.target_chain_id == 0 {
Err(PythContractError::InvalidGovernancePayload)?
}
upgrade_contract(&env.contract.address, code_id)?
}
AuthorizeGovernanceDataSourceTransfer { claim_vaa } => {
let parsed_claim_vaa = parse_vaa(deps.branch(), env.block.time.seconds(), &claim_vaa)?;
@ -286,6 +301,20 @@ fn transfer_governance(
}
}
/// Upgrades the contract at `address` to `new_code_id` (by sending a `Migrate` message). The
/// migration will fail unless this contract is the admin of the contract being upgraded.
/// (Typically, `address` is this contract's address, and the contract is its own admin.)
fn upgrade_contract(address: &Addr, new_code_id: u64) -> StdResult<Response> {
Ok(Response::new()
.add_message(CosmosMsg::Wasm(WasmMsg::Migrate {
contract_addr: address.to_string(),
new_code_id,
msg: to_binary(&MigrateMsg {})?,
}))
.add_attribute("action", "upgrade_contract")
.add_attribute("new_code_id", format!("{new_code_id}")))
}
/// Check that `vaa` is from a valid data source (and hence is a legitimate price update message).
fn verify_vaa_from_data_source(state: &ConfigInfo, vaa: &ParsedVAA) -> StdResult<()> {
let vaa_data_source = PythDataSource {

View File

@ -51,9 +51,9 @@ impl GovernanceModule {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[repr(u8)]
pub enum GovernanceAction {
UpgradeContract { address: [u8; 20] }, // 0
UpgradeContract { code_id: u64 }, // 0
AuthorizeGovernanceDataSourceTransfer { claim_vaa: Binary }, // 1
SetDataSources { data_sources: Vec<PythDataSource> }, // 2
SetDataSources { data_sources: Vec<PythDataSource> }, // 2
// Set the fee to val * (10 ** expo)
SetFee { val: u64, expo: u64 }, // 3
// Set the default valid period to the provided number of seconds
@ -92,9 +92,8 @@ impl GovernanceInstruction {
let action: Result<GovernanceAction, String> = match action_type {
0 => {
let mut address: [u8; 20] = [0; 20];
bytes.read_exact(&mut address)?;
Ok(GovernanceAction::UpgradeContract { address })
let code_id = bytes.read_u64::<BigEndian>()?;
Ok(GovernanceAction::UpgradeContract { code_id })
}
1 => {
let mut payload: Vec<u8> = vec![];
@ -161,10 +160,10 @@ impl GovernanceInstruction {
buf.write_u8(self.module.to_u8())?;
match &self.action {
GovernanceAction::UpgradeContract { address } => {
GovernanceAction::UpgradeContract { code_id } => {
buf.write_u8(0)?;
buf.write_u16::<BigEndian>(self.target_chain_id)?;
buf.write_all(address)?;
buf.write_u64::<BigEndian>(*code_id)?;
}
GovernanceAction::AuthorizeGovernanceDataSourceTransfer { claim_vaa } => {
buf.write_u8(1)?;

View File

@ -7,13 +7,10 @@ import {
import { readFileSync } from "fs";
import { Bech32, toHex } from "@cosmjs/encoding";
import { zeroPad } from "ethers/lib/utils.js";
import axios from "axios";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import assert from "assert";
export const TERRA_GAS_PRICES_URL = "https://fcd.terra.dev/v1/txs/gas_prices";
const argv = yargs(hideBin(process.argv))
.option("network", {
description: "Which network to deploy to",
@ -64,38 +61,96 @@ const artifact = argv.artifact;
const CONFIG = {
mainnet: {
terraHost: {
URL: "https://lcd.terra.dev",
chainID: "columbus-5",
URL: "https://phoenix-lcd.terra.dev",
chainID: "phoenix-1",
name: "mainnet",
},
wormholeContract: "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5",
pythEmitterAddress:
"6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
pyth_config: {
wormhole_contract:
"terra12mrnzvhx3rpej6843uge2yyfppfyd3u9c3uq223q8sl48huz9juqffcnh",
data_sources: [
{
emitter: Buffer.from(
"6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
"hex"
).toString("base64"),
chain_id: 1,
},
{
emitter: Buffer.from(
"f8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0",
"hex"
).toString("base64"),
chain_id: 26,
},
],
governance_source: {
emitter: Buffer.from(
"5635979a221c34931e32620b9293a463065555ea71fe97cd6237ade875b12e9e",
"hex"
).toString("base64"),
chain_id: 1,
},
governance_source_index: 0,
governance_sequence_number: 0,
chain_id: 18,
valid_time_period_secs: 60,
fee: {
amount: "1",
denom: "uluna",
},
},
},
testnet: {
terraHost: {
URL: "https://bombay-lcd.terra.dev",
chainID: "bombay-12",
URL: "https://pisco-lcd.terra.dev",
chainID: "pisco-1",
name: "testnet",
},
wormholeContract: "terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v",
pythEmitterAddress:
"f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0",
pyth_config: {
wormhole_contract:
"terra19nv3xr5lrmmr7egvrk2kqgw4kcn43xrtd5g0mpgwwvhetusk4k7s66jyv0",
data_sources: [
{
emitter: Buffer.from(
"f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0",
"hex"
).toString("base64"),
chain_id: 1,
},
{
emitter: Buffer.from(
"a27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6",
"hex"
).toString("base64"),
chain_id: 26,
},
],
governance_source: {
emitter: Buffer.from(
"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385",
"hex"
).toString("base64"),
chain_id: 1,
},
governance_source_index: 0,
governance_sequence_number: 0,
chain_id: 18,
valid_time_period_secs: 60,
fee: {
amount: "1",
denom: "uluna",
},
},
},
};
const terraHost = CONFIG[argv.network].terraHost;
const wormholeContract = CONFIG[argv.network].wormholeContract;
const pythEmitterAddress = CONFIG[argv.network].pythEmitterAddress;
const pythConfig = CONFIG[argv.network].pyth_config;
const lcd = new LCDClient(terraHost);
const feeDenoms = ["uluna"];
const gasPrices = await axios
.get(TERRA_GAS_PRICES_URL)
.then((result) => result.data);
const wallet = lcd.wallet(
new MnemonicKey({
mnemonic: argv.mnemonic,
@ -124,22 +179,9 @@ if (argv.codeId !== undefined) {
contract_bytes.toString("base64")
);
const feeEstimate = await lcd.tx.estimateFee(
wallet.key.accAddress,
[store_code],
{
feeDenoms,
gasPrices,
}
);
console.log("Deploy fee: ", feeEstimate.amount.toString());
const tx = await wallet.createAndSignTx({
msgs: [store_code],
feeDenoms,
gasPrices,
fee: feeEstimate,
});
const rs = await lcd.tx.broadcast(tx);
@ -166,7 +208,7 @@ if (argv.codeId !== undefined) {
if (argv.instantiate) {
console.log("Instantiating a contract");
async function instantiate(codeId, inst_msg) {
async function instantiate(codeId, inst_msg, label) {
var address;
await wallet
.createAndSignTx({
@ -175,7 +217,9 @@ if (argv.instantiate) {
wallet.key.accAddress,
wallet.key.accAddress,
codeId,
inst_msg
inst_msg,
undefined,
label
),
],
})
@ -199,13 +243,7 @@ if (argv.instantiate) {
return address;
}
const pythChain = 1;
const contractAddress = await instantiate(codeId, {
wormhole_contract: wormholeContract,
pyth_emitter: Buffer.from(pythEmitterAddress, "hex").toString("base64"),
pyth_emitter_chain: pythChain,
});
const contractAddress = await instantiate(codeId, pythConfig, "pyth");
console.log(`Deployed Pyth contract at ${contractAddress}`);
}
@ -233,7 +271,6 @@ if (argv.migrate) {
),
],
feeDenoms,
gasPrices,
});
const rs = await lcd.tx.broadcast(tx);

View File

@ -5,7 +5,7 @@
"main": "deploy.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"deploy-pyth": "node deploy-pyth-bridge.js"
},
"author": "",
"license": "ISC",