diff --git a/src/secret_network_client.ts b/src/secret_network_client.ts index dd3eb8ae..d74883ea 100644 --- a/src/secret_network_client.ts +++ b/src/secret_network_client.ts @@ -159,6 +159,7 @@ import { Any } from "./protobuf/google/protobuf/any"; import { MsgExecuteContractResponse, MsgInstantiateContractResponse, + MsgMigrateContractResponse, } from "./protobuf/secret/compute/v1beta1/msg"; import { AuthQuerier } from "./query/auth"; import { AuthzQuerier } from "./query/authz"; @@ -187,6 +188,10 @@ import { UpgradeQuerier } from "./query/upgrade"; import { AminoMsg, Msg, + MsgClearAdmin, + MsgClearAdminParams, + MsgMigrateContract, + MsgMigrateContractParams, MsgParams, MsgPayPacketFee, MsgPayPacketFeeAsync, @@ -199,6 +204,7 @@ import { MsgRegistry, MsgSetAutoRestake, MsgSetAutoRestakeParams, + MsgUpdateAdmin, ProtoMsg, } from "./tx"; import { @@ -691,12 +697,18 @@ export type TxSender = { send: SingleMsgTx; }; compute: { - /** Execute a function on a contract */ - executeContract: SingleMsgTx>; + /** Upload a compiled contract */ + storeCode: SingleMsgTx; /** Instantiate a contract from code id */ instantiateContract: SingleMsgTx; - /** Upload a compiled contract to Secret Network */ - storeCode: SingleMsgTx; + /** Execute a function on a contract */ + executeContract: SingleMsgTx>; + /** Runs a code upgrade/downgrade for a contract */ + migrateContract: SingleMsgTx>; + /** Update the admin of a contract */ + updateAdmin: SingleMsgTx; + /** Clear the admin of a contract */ + clearAdmin: SingleMsgTx; }; emergency_button: { toggleIbcSwitch: SingleMsgTx; @@ -931,9 +943,12 @@ export class SecretNetworkClient { send: doMsg(MsgSend), }, compute: { - executeContract: doMsg(MsgExecuteContract), - instantiateContract: doMsg(MsgInstantiateContract), storeCode: doMsg(MsgStoreCode), + instantiateContract: doMsg(MsgInstantiateContract), + executeContract: doMsg(MsgExecuteContract), + migrateContract: doMsg(MsgMigrateContract), + updateAdmin: doMsg(MsgUpdateAdmin), + clearAdmin: doMsg(MsgClearAdmin), }, emergency_button: { toggleIbcSwitch: doMsg(MsgToggleIbcSwitch), @@ -1151,7 +1166,8 @@ export class SecretNetworkClient { if (msg["@type"] === "/secret.compute.v1beta1.MsgInstantiateContract") { contractInputMsgFieldName = "init_msg"; } else if ( - msg["@type"] === "/secret.compute.v1beta1.MsgExecuteContract" + msg["@type"] === "/secret.compute.v1beta1.MsgExecuteContract" || + msg["@type"] === "/secret.compute.v1beta1.MsgMigrateContract" ) { contractInputMsgFieldName = "msg"; } @@ -1298,6 +1314,18 @@ export class SecretNetworkClient { data[msgIndex] = MsgExecuteContractResponse.encode({ data: decrypted, }).finish(); + } else if ( + type_url === "/secret.compute.v1beta1.MsgMigrateContract" + ) { + const decoded = MsgMigrateContractResponse.decode( + txMsgData.data[msgIndex].data, + ); + const decrypted = fromBase64( + fromUtf8(await this.encryptionUtils.decrypt(decoded.data, nonce)), + ); + data[msgIndex] = MsgMigrateContractResponse.encode({ + data: decrypted, + }).finish(); } } catch (decryptionError) { // Not encrypted or can't decrypt because not original sender @@ -1509,6 +1537,10 @@ export class SecretNetworkClient { } else if (msg.type_url === "/secret.compute.v1beta1.MsgStoreCode") { msg.value.sender = bytesToAddress(msg.value.sender); msg.value.wasm_byte_code = toBase64(msg.value.wasm_byte_code); + } else if ( + msg.type_url === "/secret.compute.v1beta1.MsgMigrateContract" + ) { + msg.value.msg = toBase64(msg.value.msg); } tx.body!.messages![i] = msg; @@ -1843,6 +1875,14 @@ export class SecretNetworkClient { }) ).code_hash!; } + } else if (msg instanceof MsgMigrateContract) { + if (!msg.codeHash) { + msg.codeHash = ( + await this.query.compute.codeHashByCodeId({ + code_id: msg.codeId, + }) + ).code_hash!; + } } } diff --git a/src/tx/compute.ts b/src/tx/compute.ts index 95cc6155..f9e07afd 100644 --- a/src/tx/compute.ts +++ b/src/tx/compute.ts @@ -19,7 +19,7 @@ export interface MsgInstantiateContractParams extends MsgParams { * character hex string. * This is used to make sure only the contract that's being invoked can decrypt the query data. * - * codeHash is an optional parameter but using it will result in way faster execution time. + * codeHash is an optional parameter but using it will result in way faster execution time (otherwise secret.js will make an query to get the code_hash from the chain). * * Valid examples: * - "af74387e276be8874f07bec3a87023ee49b0e7ebe08178c49d0a49c3c98ed60e" @@ -147,7 +147,7 @@ export interface MsgExecuteContractParams extends MsgParams { * character hex string. * This is used to make sure only the contract that's being invoked can decrypt the query data. * - * codeHash is an optional parameter but using it will result in way faster execution time. + * codeHash is an optional parameter but using it will result in way faster execution time (otherwise secret.js will make an query to get the code_hash from the chain). * * Valid examples: * - "af74387e276be8874f07bec3a87023ee49b0e7ebe08178c49d0a49c3c98ed60e" @@ -324,13 +324,13 @@ export interface MsgMigrateContractParams extends MsgParams { /** The contract's address */ contract_address: string; /** The new code id */ - new_code_id: number | string; + code_id: number | string; /** The input message */ msg: T; /** The SHA256 hash value of the contract's *new* WASM bytecode, represented as case-insensitive 64 character hex string. * This is used to make sure only the contract that's being invoked can decrypt the query data. * - * codeHash is an optional parameter but using it will result in way faster execution time. + * codeHash is an optional parameter but using it will result in way faster execution time (otherwise secret.js will make an query to get the code_hash from the chain). * * Valid examples: * - "af74387e276be8874f07bec3a87023ee49b0e7ebe08178c49d0a49c3c98ed60e" @@ -338,7 +338,7 @@ export interface MsgMigrateContractParams extends MsgParams { * - "AF74387E276BE8874F07BEC3A87023EE49B0E7EBE08178C49D0A49C3C98ED60E" * - "0xAF74387E276BE8874F07BEC3A87023EE49B0E7EBE08178C49D0A49C3C98ED60E" */ - new_code_hash?: string; + code_hash?: string; } /** Execute a function on a contract */ @@ -355,8 +355,8 @@ export class MsgMigrateContract implements Msg { sender, contract_address: contractAddress, msg, - new_code_id: codeId, - new_code_hash: codeHash, + code_id: codeId, + code_hash: codeHash, }: MsgMigrateContractParams) { this.sender = sender; this.contractAddress = contractAddress; diff --git a/src/tx/index.ts b/src/tx/index.ts index 502dd66c..396712a5 100644 --- a/src/tx/index.ts +++ b/src/tx/index.ts @@ -32,13 +32,13 @@ import { MsgUndelegate, } from "../protobuf/cosmos/staking/v1beta1/tx"; import { MsgCreateVestingAccount } from "../protobuf/cosmos/vesting/v1beta1/tx"; -import { MsgTransfer } from "../protobuf/ibc/applications/transfer/v1/tx"; import { MsgPayPacketFee, MsgPayPacketFeeAsync, - MsgRegisterPayee, MsgRegisterCounterpartyPayee, + MsgRegisterPayee, } from "../protobuf/ibc/applications/fee/v1/tx"; +import { MsgTransfer } from "../protobuf/ibc/applications/transfer/v1/tx"; import { MsgAcknowledgement, MsgChannelCloseConfirm, @@ -64,31 +64,34 @@ import { MsgConnectionOpenTry, } from "../protobuf/ibc/core/connection/v1/tx"; import { + MsgClearAdmin, MsgExecuteContract, MsgInstantiateContract, + MsgMigrateContract, MsgStoreCode, + MsgUpdateAdmin, } from "../protobuf/secret/compute/v1beta1/msg"; +import { MsgToggleIbcSwitch } from "../protobuf/secret/emergencybutton/v1beta1/tx"; import { RaAuthenticate } from "../protobuf/secret/registration/v1beta1/msg"; -import {MsgToggleIbcSwitch} from "../protobuf/secret/emergencybutton/v1beta1/tx"; export * from "./authz"; export * from "./bank"; export * from "./compute"; export * from "./crisis"; export * from "./distribution"; +export * from "./emergency_button"; export * from "./evidence"; export * from "./feegrant"; export * from "./gov"; export * from "./ibc_channel"; export * from "./ibc_client"; export * from "./ibc_connection"; -export * from "./ibc_transfer"; export * from "./ibc_fee"; +export * from "./ibc_transfer"; export * from "./slashing"; export * from "./staking"; -export * from "./vesting"; export * from "./types"; -export * from "./emergency_button"; +export * from "./vesting"; export type MsgDecoder = { decode(input: Uint8Array): any; @@ -157,6 +160,9 @@ export const MsgRegistry = new Map([ ["/secret.compute.v1beta1.MsgStoreCode", MsgStoreCode], ["/secret.compute.v1beta1.MsgInstantiateContract", MsgInstantiateContract], ["/secret.compute.v1beta1.MsgExecuteContract", MsgExecuteContract], + ["/secret.compute.v1beta1.MsgMigrateContract", MsgMigrateContract], + ["/secret.compute.v1beta1.MsgUpdateAdmin", MsgUpdateAdmin], + ["/secret.compute.v1beta1.MsgClearAdmin", MsgClearAdmin], ["/secret.registration.v1beta1.RaAuthenticate", RaAuthenticate], ["/cosmos.vesting.v1beta1.MsgCreateVestingAccount", MsgCreateVestingAccount], ["/secret.emergencybutton.v1beta1.MsgToggleIbcSwitch", MsgToggleIbcSwitch], diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 9ec84404..8b9f6ddd 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.9" services: localsecret-1: - image: "ghcr.io/scrtlabs/localsecret:v1.10.0-beta.0" + image: "ghcr.io/scrtlabs/localsecret:v0.0.0" ports: - "26657:26657" # RPC - "26656:26656" # P2P @@ -13,7 +13,7 @@ services: FAST_BLOCKS: "true" LOG_LEVEL: "TRACE" localsecret-2: - image: "ghcr.io/scrtlabs/localsecret:v1.10.0-beta.0" + image: "ghcr.io/scrtlabs/localsecret:v0.0.0" ports: - "36657:26657" # RPC - "36656:26656" # P2P diff --git a/test/ibc-hooks-contract/src/contract.rs b/test/ibc-hooks-contract/src/contract.rs index 63d4222e..f10438ca 100644 --- a/test/ibc-hooks-contract/src/contract.rs +++ b/test/ibc-hooks-contract/src/contract.rs @@ -91,3 +91,11 @@ pub fn execute(_deps: DepsMut, env: Env, info: MessageInfo, msg: Msg) -> StdResu } } } + +#[entry_point] +pub fn migrate(_deps: DepsMut, env: Env, msg: Msg) -> StdResult { + Ok(Response::default().add_attributes(vec![ + ("migrate.env", format!("{:?}", env)), + ("migrate.msg", format!("{:?}", msg)), + ])) +} diff --git a/test/ibc-hooks.wasm.gz b/test/ibc-hooks.wasm.gz index de18bb3c..ccbc2b7f 100644 Binary files a/test/ibc-hooks.wasm.gz and b/test/ibc-hooks.wasm.gz differ diff --git a/test/test.ts b/test/test.ts index 33461f30..452eaa34 100644 --- a/test/test.ts +++ b/test/test.ts @@ -2,9 +2,6 @@ import { fromBase64, fromUtf8, toBase64 } from "@cosmjs/encoding"; import { bech32 } from "bech32"; import fs from "fs"; import { - base64PubkeyToAddress, - base64TendermintPubkeyToValconsAddress, - gasToFee, MsgDelegate, MsgExecuteContract, MsgExecuteContractResponse, @@ -14,22 +11,26 @@ import { MsgSetAutoRestake, MsgSubmitProposal, ProposalType, - pubkeyToAddress, SecretNetworkClient, - selfDelegatorAddressToValidatorAddress, StakeAuthorizationType, + TxResultCode, + VoteOption, + base64PubkeyToAddress, + base64TendermintPubkeyToValconsAddress, + gasToFee, + pubkeyToAddress, + selfDelegatorAddressToValidatorAddress, stringToCoin, stringToCoins, tendermintPubkeyToValconsAddress, - TxResultCode, validateAddress, validatorAddressToSelfDelegatorAddress, - VoteOption, } from "../src"; import { BaseAccount } from "../src/grpc_gateway/cosmos/auth/v1beta1/auth.pb"; import { Proposal } from "../src/grpc_gateway/cosmos/gov/v1beta1/gov.pb"; import { BondStatus } from "../src/grpc_gateway/cosmos/staking/v1beta1/staking.pb"; import { MsgSubmitProposalResponse } from "../src/protobuf/cosmos/gov/v1beta1/tx"; +import { MsgStoreCodeResponse } from "../src/protobuf/secret/compute/v1beta1/msg"; import { AminoWallet } from "../src/wallet_amino"; import { accounts, @@ -40,11 +41,9 @@ import { getBalance, getValueFromRawLog, initContract, - passParameterChangeProposal, sleep, storeContract, storeSnip20Ibc, - waitForProposalToPass, } from "./utils"; beforeAll(() => { @@ -159,10 +158,9 @@ describe("query", () => { const code_id = await storeSnip20Ibc(secretjs, accounts[0].address); - const { code_hash: code_hash } = - await secretjs.query.compute.codeHashByCodeId({ - code_id: code_id, - }); + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); const txInit = await secretjs.tx.compute.instantiateContract( { @@ -749,10 +747,9 @@ describe("tx.compute", () => { const code_id = await storeSnip20Ibc(secretjs, accounts[0].address); - const { code_hash: code_hash } = - await secretjs.query.compute.codeHashByCodeId({ - code_id: code_id, - }); + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); const tx = await secretjs.tx.compute.instantiateContract( { @@ -791,10 +788,9 @@ describe("tx.compute", () => { const code_id = await storeSnip20Ibc(secretjs, accounts[0].address); - const { code_hash: code_hash } = - await secretjs.query.compute.codeHashByCodeId({ - code_id: code_id, - }); + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); const tx = await secretjs.tx.compute.instantiateContract( { @@ -844,10 +840,9 @@ describe("tx.compute", () => { const code_id = getValueFromRawLog(txStore.rawLog, "message.code_id"); - const { code_hash: code_hash } = - await secretjs.query.compute.codeHashByCodeId({ - code_id: code_id, - }); + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); const initInput = { sender: accounts[0].address, @@ -961,10 +956,9 @@ describe("tx.compute", () => { const code_id = await storeSnip20Ibc(secretjs, accounts[0].address); - const { code_hash: code_hash } = - await secretjs.query.compute.codeHashByCodeId({ - code_id: code_id, - }); + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); const txInit = await secretjs.tx.compute.instantiateContract( { @@ -1038,10 +1032,9 @@ describe("tx.compute", () => { const { secretjs } = accounts[0]; const code_id = await storeSnip20Ibc(secretjs, accounts[0].address); - const { code_hash: code_hash } = - await secretjs.query.compute.codeHashByCodeId({ - code_id: code_id, - }); + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); const txInit = await secretjs.tx.compute.instantiateContract( { @@ -1134,10 +1127,9 @@ describe("tx.compute", () => { const code_id = getValueFromRawLog(txStore.rawLog, "message.code_id"); - const { code_hash: code_hash } = - await secretjs.query.compute.codeHashByCodeId({ - code_id: code_id, - }); + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); const txInit = await secretjs.tx.compute.instantiateContract( { @@ -1204,6 +1196,195 @@ describe("tx.compute", () => { }); expect(tx.rawLog).toContain("failed to execute message; message index: 1"); }); + + test("MsgInstantiateContract admin", async () => { + const { secretjs } = accounts[0]; + + const code_id = await storeSnip20Ibc(secretjs, accounts[0].address); + + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); + + const tx = await secretjs.tx.compute.instantiateContract( + { + sender: accounts[0].address, + admin: accounts[0].address, + code_id, + code_hash, + init_msg: { + name: "Secret SCRT", + admin: accounts[0].address, + symbol: "SSCRT", + decimals: 6, + initial_balances: [{ address: accounts[0].address, amount: "1" }], + prng_seed: "eW8=", + config: { + public_total_supply: true, + enable_deposit: true, + enable_redeem: true, + enable_mint: false, + enable_burn: false, + }, + supported_denoms: ["uscrt"], + }, + label: `label-${Date.now()}`, + init_funds: [], + }, + { + broadcastCheckIntervalMs: 100, + gasLimit: 5_000_000, + }, + ); + checkInstantiateSuccess(tx); + }); + + test("MsgMigrateContract", async () => { + const { secretjs } = accounts[0]; + + const code_id = await storeSnip20Ibc(secretjs, secretjs.address); + + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); + + let tx = await secretjs.tx.compute.instantiateContract( + { + sender: secretjs.address, + admin: secretjs.address, + code_id, + code_hash, + init_msg: { + name: "Secret SCRT", + admin: secretjs.address, + symbol: "SSCRT", + decimals: 6, + initial_balances: [{ address: secretjs.address, amount: "1" }], + prng_seed: "eW8=", + config: { + public_total_supply: true, + enable_deposit: true, + enable_redeem: true, + enable_mint: false, + enable_burn: false, + }, + supported_denoms: ["uscrt"], + }, + label: `label-${Date.now()}`, + init_funds: [], + }, + { + broadcastCheckIntervalMs: 100, + gasLimit: 5_000_000, + }, + ); + checkInstantiateSuccess(tx); + + const contract_address = MsgInstantiateContractResponse.decode( + tx.data[0], + ).address; + + tx = await secretjs.tx.compute.storeCode( + { + sender: secretjs.address, + wasm_byte_code: fs.readFileSync( + `${__dirname}/ibc-hooks.wasm.gz`, + ) as Uint8Array, + source: "", + builder: "", + }, + { + broadcastCheckIntervalMs: 100, + gasLimit: 5_000_000, + }, + ); + if (tx.code !== TxResultCode.Success) { + console.error(tx.rawLog); + } + expect(tx.code).toBe(TxResultCode.Success); + + const new_code_id = MsgStoreCodeResponse.decode(tx.data[0]).code_id; + const { code_hash: new_code_hash } = + await secretjs.query.compute.codeHashByCodeId({ + code_id: new_code_id, + }); + + tx = await secretjs.tx.compute.migrateContract( + { + sender: secretjs.address, + contract_address, + code_id: new_code_id, + code_hash: new_code_hash, + msg: { nop: {} }, + }, + { + broadcastCheckIntervalMs: 100, + gasLimit: 5_000_000, + }, + ); + + if (tx.code !== TxResultCode.Success) { + console.error(tx.rawLog); + } + expect(tx.code).toBe(TxResultCode.Success); + + const { block } = await secretjs.query.tendermint.getBlockByHeight({ + height: String(tx.height), + }); + + const timestampRfc3339 = String(block?.header?.time); + const ns = timestampRfc3339.slice(-7).slice(0, 6); + const timestampMs = String(new Date(timestampRfc3339).getTime()); + const timestampNs = timestampMs + ns; + + expect(tx.arrayLog).toStrictEqual([ + { + msg: 0, + type: "message", + key: "action", + value: "/secret.compute.v1beta1.MsgMigrateContract", + }, + { msg: 0, type: "message", key: "module", value: "compute" }, + { + msg: 0, + type: "message", + key: "sender", + value: "secret1ap26qrlp8mcq2pg6r47w43l0y8zkqm8a450s03", + }, + { msg: 0, type: "migrate", key: "code_id", value: new_code_id }, + { + msg: 0, + type: "migrate", + key: "contract_address", + value: contract_address, + }, + { + msg: 0, + type: "migrate", + key: "contract_address", + value: contract_address, + }, + { msg: 0, type: "migrate", key: "code_id", value: new_code_id }, + { + msg: 0, + type: "wasm", + key: "contract_address", + value: contract_address, + }, + { + msg: 0, + type: "wasm", + key: "migrate.env", + value: `Env { block: BlockInfo { height: ${tx.height}, time: Timestamp(Uint64(${timestampNs})), chain_id: "secretdev-1" }, transaction: Some(TransactionInfo { index: 0 }), contract: ContractInfo { address: Addr("${contract_address}"), code_hash: "${new_code_hash}" } }`, + }, + { + msg: 0, + type: "wasm", + key: "migrate.msg", + value: "Nop", + }, + ]); + }); }); describe("tx.gov", () => { @@ -2961,10 +3142,9 @@ describe("tx broadcast multi", () => { const code_id = await storeSnip20Ibc(secretjs, accounts[0].address); - const { code_hash: code_hash } = - await secretjs.query.compute.codeHashByCodeId({ - code_id: code_id, - }); + const { code_hash } = await secretjs.query.compute.codeHashByCodeId({ + code_id, + }); let tx = await secretjs.tx.compute.instantiateContract( {