From 862978dd9f44e270f9debf8a56220fbe308edb0a Mon Sep 17 00:00:00 2001 From: jinoosss Date: Mon, 5 Feb 2024 13:15:05 +0900 Subject: [PATCH] feat: Add the MsgRun transaction execution function --- proto/gno/vm.proto | 11 ++++ src/proto/gno/vm.ts | 111 ++++++++++++++++++++++++++++++++++ src/wallet/endpoints.ts | 1 + src/wallet/utility/utility.ts | 39 +++++++++++- src/wallet/wallet.ts | 55 +++++++++++++++++ 5 files changed, 215 insertions(+), 2 deletions(-) diff --git a/proto/gno/vm.proto b/proto/gno/vm.proto index 5db786e..bc05efc 100644 --- a/proto/gno/vm.proto +++ b/proto/gno/vm.proto @@ -30,6 +30,17 @@ message MsgAddPackage { string deposit = 3; } +// MsgRun is the execute arbitrary Gno code tx message, +// denoted as "m_run" +message MsgRun { + // the bech32 address of the caller + string caller = 1; + // the amount of funds to be deposited to the package, if any ("") + string send = 2; + // the package being execute + MemPackage package = 3; +} + // MemPackage is the metadata information tied to // package / realm deployment message MemPackage { diff --git a/src/proto/gno/vm.ts b/src/proto/gno/vm.ts index 78ebd72..dbe9ecd 100644 --- a/src/proto/gno/vm.ts +++ b/src/proto/gno/vm.ts @@ -34,6 +34,19 @@ export interface MsgAddPackage { deposit: string; } +/** + * MsgRun is the execute arbitrary Gno code tx message, + * denoted as "m_run" + */ +export interface MsgRun { + /** the bech32 address of the caller */ + caller: string; + /** the amount of funds to be deposited to the package, if any ("") */ + send: string; + /** the package being execute */ + package?: MemPackage | undefined; +} + /** * MemPackage is the metadata information tied to * package / realm deployment @@ -291,6 +304,104 @@ export const MsgAddPackage = { }, }; +function createBaseMsgRun(): MsgRun { + return { caller: '', send: '', package: undefined }; +} + +export const MsgRun = { + encode( + message: MsgRun, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.caller !== '') { + writer.uint32(10).string(message.caller); + } + if (message.send !== '') { + writer.uint32(18).string(message.send); + } + if (message.package !== undefined) { + MemPackage.encode(message.package, writer.uint32(26).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgRun { + const reader = + input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgRun(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.caller = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.send = reader.string(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.package = MemPackage.decode(reader, reader.uint32()); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): MsgRun { + return { + caller: isSet(object.caller) ? String(object.caller) : '', + send: isSet(object.send) ? String(object.send) : '', + package: isSet(object.package) + ? MemPackage.fromJSON(object.package) + : undefined, + }; + }, + + toJSON(message: MsgRun): unknown { + const obj: any = {}; + if (message.caller !== '') { + obj.caller = message.caller; + } + if (message.send !== '') { + obj.send = message.send; + } + if (message.package !== undefined) { + obj.package = MemPackage.toJSON(message.package); + } + return obj; + }, + + create, I>>(base?: I): MsgRun { + return MsgRun.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): MsgRun { + const message = createBaseMsgRun(); + message.caller = object.caller ?? ''; + message.send = object.send ?? ''; + message.package = + object.package !== undefined && object.package !== null + ? MemPackage.fromPartial(object.package) + : undefined; + return message; + }, +}; + function createBaseMemPackage(): MemPackage { return { name: '', path: '', files: [] }; } diff --git a/src/wallet/endpoints.ts b/src/wallet/endpoints.ts index 7f9e397..4c7e3df 100644 --- a/src/wallet/endpoints.ts +++ b/src/wallet/endpoints.ts @@ -2,4 +2,5 @@ export enum MsgEndpoint { MSG_SEND = '/bank.MsgSend', MSG_ADD_PKG = '/vm.m_addpkg', MSG_CALL = '/vm.m_call', + MSG_RUN = '/vm.m_run', } diff --git a/src/wallet/utility/utility.ts b/src/wallet/utility/utility.ts index fd67b43..401a0dc 100644 --- a/src/wallet/utility/utility.ts +++ b/src/wallet/utility/utility.ts @@ -1,4 +1,5 @@ -import { Any, MsgAddPackage, MsgCall, MsgSend } from '../../proto'; +import { Any, MemPackage, MsgAddPackage, MsgCall, MsgSend } from '../../proto'; +import { MsgRun } from '../../proto/gno/vm'; import { MsgEndpoint } from '../endpoints'; /** @@ -49,12 +50,46 @@ export const decodeTxMessages = (messages: Any[]): any[] => { ...MsgSend.decode(m.value), }; case MsgEndpoint.MSG_ADD_PKG: + const msgAddPkg = MsgAddPackage.decode(m.value); return { '@type': m.typeUrl, - ...MsgAddPackage.decode(m.value), + ...msgAddPkg, + package: msgAddPkg.package + ? mapToStdMemPackage(msgAddPkg.package) + : undefined, + }; + case MsgEndpoint.MSG_RUN: + const msgRun = MsgRun.decode(m.value); + return { + '@type': m.typeUrl, + ...msgRun, + package: msgRun.package + ? mapToStdMemPackage(msgRun.package) + : undefined, }; default: throw new Error(`unsupported message type ${m.typeUrl}`); } }); }; + +interface StdMemPackage { + Name: string; + Path: string; + Files: { + Name: string; + Body: string; + }[]; +} + +const mapToStdMemPackage = (memPackage: MemPackage): StdMemPackage => { + const { name, path, files } = memPackage; + return { + Name: name, + Path: path, + Files: files.map((file) => ({ + Name: file.name, + Body: file.body, + })), + }; +}; diff --git a/src/wallet/wallet.ts b/src/wallet/wallet.ts index cbff7c8..2e46263 100644 --- a/src/wallet/wallet.ts +++ b/src/wallet/wallet.ts @@ -11,6 +11,7 @@ import Long from 'long'; import { MemPackage, MsgAddPackage, MsgCall, MsgSend } from '../proto'; import { MsgEndpoint } from './endpoints'; import { LedgerConnector } from '@cosmjs/ledger-amino'; +import { MsgRun } from '../proto/gno/vm'; /** * GnoWallet is an extension of the TM2 wallet with @@ -254,4 +255,58 @@ export class GnoWallet extends Wallet { // Send the transaction return this.sendTransaction(signedTx, endpoint); }; + + /** + * Executes arbitrary Gno code + * @param {MemPackage} gnoPackage the package being execute + * @param {TransactionEndpoint} endpoint the transaction broadcast type (sync / commit) + * @param {Map} [funds] the denomination -> value map for funds, if any + * @param {TxFee} [fee] the custom transaction fee, if any + */ + executePackage = async ( + gnoPackage: MemPackage, + endpoint: K, + funds?: Map, + fee?: TxFee + ): Promise => { + // Convert the funds into the correct representation + const amount: string = fundsToCoins(funds); + + // Fetch the wallet address + const caller: string = await this.getAddress(); + + // Construct the transaction fee + const txFee: TxFee = fee + ? fee + : { + gasWanted: new Long(60000), + gasFee: defaultTxFee, + }; + + // Prepare the Msg + const runMsg: MsgRun = { + caller, + send: amount, + package: gnoPackage, + }; + + // Construct the transfer transaction + const tx: Tx = { + messages: [ + { + typeUrl: MsgEndpoint.MSG_RUN, + value: MsgRun.encode(runMsg).finish(), + }, + ], + fee: txFee, + memo: '', + signatures: [], // No signature yet + }; + + // Sign the transaction + const signedTx: Tx = await this.signTransaction(tx, decodeTxMessages); + + // Send the transaction + return this.sendTransaction(signedTx, endpoint); + }; }