Merge pull request #1262 from cosmos/fee-payer-simon
Add StdFee.granter support (for feegrant)
This commit is contained in:
commit
efc2b4e99d
@ -71,6 +71,8 @@ and this project adheres to
|
||||
- @cosmjs/stargate: `BankExtension.totalSupply` now takes a pagination key
|
||||
argument and returns the full `QueryTotalSupplyResponse` including the next
|
||||
pagination key ([#1095]).
|
||||
- @cosmjs/proto-signing: `makeAuthInfoBytes` now expects a fee granter and fee
|
||||
payer argument in position 4 and 5.
|
||||
|
||||
[#1131]: https://github.com/cosmos/cosmjs/pull/1131
|
||||
[#1168]: https://github.com/cosmos/cosmjs/pull/1168
|
||||
|
||||
@ -12,6 +12,10 @@ export interface AminoMsg {
|
||||
export interface StdFee {
|
||||
readonly amount: readonly Coin[];
|
||||
readonly gas: string;
|
||||
/** The granter address that is used for paying with feegrants */
|
||||
readonly granter?: string;
|
||||
/** The fee payer address. The payer must have signed the transaction. */
|
||||
readonly payer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -79,7 +79,9 @@ async function sendTokens(
|
||||
},
|
||||
];
|
||||
const gasLimit = 200000;
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], feeAmount, gasLimit);
|
||||
const feeGranter = undefined;
|
||||
const feePayer = undefined;
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], feeAmount, gasLimit, feeGranter, feePayer);
|
||||
|
||||
const chainId = await client.getChainId();
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
|
||||
@ -203,7 +203,15 @@ describe("CosmWasmClient", () => {
|
||||
};
|
||||
const txBodyBytes = registry.encode(txBody);
|
||||
const gasLimit = Int53.fromString(fee.gas).toNumber();
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], fee.amount, gasLimit);
|
||||
const feeGranter = undefined;
|
||||
const feePayer = undefined;
|
||||
const authInfoBytes = makeAuthInfoBytes(
|
||||
[{ pubkey, sequence }],
|
||||
fee.amount,
|
||||
gasLimit,
|
||||
feeGranter,
|
||||
feePayer,
|
||||
);
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
const { signed, signature } = await wallet.signDirect(alice.address0, signDoc);
|
||||
const txRaw = TxRaw.fromPartial({
|
||||
|
||||
@ -586,6 +586,8 @@ export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
[{ pubkey, sequence: signedSequence }],
|
||||
signed.fee.amount,
|
||||
signedGasLimit,
|
||||
signed.fee.granter,
|
||||
signed.fee.payer,
|
||||
signMode,
|
||||
);
|
||||
return TxRaw.fromPartial({
|
||||
@ -619,7 +621,13 @@ export class SigningCosmWasmClient extends CosmWasmClient {
|
||||
};
|
||||
const txBodyBytes = this.registry.encode(txBody);
|
||||
const gasLimit = Int53.fromString(fee.gas).toNumber();
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], fee.amount, gasLimit);
|
||||
const authInfoBytes = makeAuthInfoBytes(
|
||||
[{ pubkey, sequence }],
|
||||
fee.amount,
|
||||
gasLimit,
|
||||
fee.granter,
|
||||
fee.payer,
|
||||
);
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc);
|
||||
return TxRaw.fromPartial({
|
||||
|
||||
@ -225,6 +225,8 @@ export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
|
||||
}));
|
||||
const modifiedFeeAmount = coins(3000, "ucosm");
|
||||
const modifiedGasLimit = 333333;
|
||||
const modifiedFeeGranter = undefined;
|
||||
const modifiedFeePayer = undefined;
|
||||
const modifiedSignDoc = {
|
||||
...signDoc,
|
||||
bodyBytes: Uint8Array.from(TxBody.encode(modifiedTxBody).finish()),
|
||||
@ -232,6 +234,8 @@ export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
|
||||
signers,
|
||||
modifiedFeeAmount,
|
||||
modifiedGasLimit,
|
||||
modifiedFeeGranter,
|
||||
modifiedFeePayer,
|
||||
SignMode.SIGN_MODE_DIRECT,
|
||||
),
|
||||
};
|
||||
|
||||
@ -261,10 +261,12 @@ describe("DirectSecp256k1HdWallet", () => {
|
||||
};
|
||||
const fee = coins(2000, "ucosm");
|
||||
const gasLimit = 200000;
|
||||
const feeGranter = undefined;
|
||||
const feePayer = undefined;
|
||||
const chainId = "simd-testing";
|
||||
const signDoc = makeSignDoc(
|
||||
fromHex(bodyBytes),
|
||||
makeAuthInfoBytes([{ pubkey, sequence }], fee, gasLimit),
|
||||
makeAuthInfoBytes([{ pubkey, sequence }], fee, gasLimit, feeGranter, feePayer),
|
||||
chainId,
|
||||
accountNumber,
|
||||
);
|
||||
|
||||
@ -44,9 +44,11 @@ describe("DirectSecp256k1Wallet", () => {
|
||||
const fee = coins(2000, "ucosm");
|
||||
const gasLimit = 200000;
|
||||
const chainId = "simd-testing";
|
||||
const feePayer = undefined;
|
||||
const feeGranter = undefined;
|
||||
const signDoc = makeSignDoc(
|
||||
fromHex(bodyBytes),
|
||||
makeAuthInfoBytes([{ pubkey, sequence }], fee, gasLimit),
|
||||
makeAuthInfoBytes([{ pubkey, sequence }], fee, gasLimit, feeGranter, feePayer),
|
||||
chainId,
|
||||
accountNumber,
|
||||
);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
|
||||
import { AuthInfo, SignDoc, SignerInfo } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
||||
@ -34,13 +35,24 @@ export function makeAuthInfoBytes(
|
||||
signers: ReadonlyArray<{ readonly pubkey: Any; readonly sequence: number }>,
|
||||
feeAmount: readonly Coin[],
|
||||
gasLimit: number,
|
||||
feeGranter: string | undefined,
|
||||
feePayer: string | undefined,
|
||||
signMode = SignMode.SIGN_MODE_DIRECT,
|
||||
): Uint8Array {
|
||||
// Required arguments 4 and 5 were added in CosmJS 0.29. Use runtime checks to help our non-TS users.
|
||||
assert(
|
||||
feeGranter === undefined || typeof feeGranter === "string",
|
||||
"feeGranter must be undefined or string",
|
||||
);
|
||||
assert(feePayer === undefined || typeof feePayer === "string", "feePayer must be undefined or string");
|
||||
|
||||
const authInfo = {
|
||||
signerInfos: makeSignerInfos(signers, signMode),
|
||||
fee: {
|
||||
amount: [...feeAmount],
|
||||
gasLimit: Long.fromNumber(gasLimit),
|
||||
granter: feeGranter,
|
||||
payer: feePayer,
|
||||
},
|
||||
};
|
||||
return AuthInfo.encode(AuthInfo.fromPartial(authInfo)).finish();
|
||||
|
||||
40
packages/stargate/src/modules/feegrant/queries.ts
Normal file
40
packages/stargate/src/modules/feegrant/queries.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {
|
||||
QueryAllowanceResponse,
|
||||
QueryAllowancesResponse,
|
||||
QueryClientImpl,
|
||||
} from "cosmjs-types/cosmos/feegrant/v1beta1/query";
|
||||
|
||||
import { createPagination, createProtobufRpcClient, QueryClient } from "../../queryclient";
|
||||
|
||||
export interface FeegrantExtension {
|
||||
readonly feegrant: {
|
||||
readonly allowance: (granter: string, grantee: string) => Promise<QueryAllowanceResponse>;
|
||||
readonly allowances: (grantee: string, paginationKey?: Uint8Array) => Promise<QueryAllowancesResponse>;
|
||||
};
|
||||
}
|
||||
|
||||
export function setupFeegrantExtension(base: QueryClient): FeegrantExtension {
|
||||
// Use this service to get easy typed access to query methods
|
||||
// This cannot be used for proof verification
|
||||
const rpc = createProtobufRpcClient(base);
|
||||
const queryService = new QueryClientImpl(rpc);
|
||||
|
||||
return {
|
||||
feegrant: {
|
||||
allowance: async (granter: string, grantee: string) => {
|
||||
const response = await queryService.Allowance({
|
||||
granter: granter,
|
||||
grantee: grantee,
|
||||
});
|
||||
return response;
|
||||
},
|
||||
allowances: async (grantee: string, paginationKey?: Uint8Array) => {
|
||||
const response = await queryService.Allowances({
|
||||
grantee: grantee,
|
||||
pagination: createPagination(paginationKey),
|
||||
});
|
||||
return response;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -39,6 +39,7 @@ export {
|
||||
} from "./evidence/aminomessages";
|
||||
export { createFreegrantAminoConverters } from "./feegrant/aminomessages";
|
||||
export { feegrantTypes } from "./feegrant/messages";
|
||||
export { FeegrantExtension, setupFeegrantExtension } from "./feegrant/queries";
|
||||
export {
|
||||
AminoMsgDeposit,
|
||||
AminoMsgSubmitProposal,
|
||||
|
||||
@ -1,16 +1,33 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention,no-bitwise */
|
||||
import { Secp256k1HdWallet } from "@cosmjs/amino";
|
||||
import { coin, coins, decodeTxRaw, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing";
|
||||
import {
|
||||
coin,
|
||||
coins,
|
||||
decodeTxRaw,
|
||||
DirectSecp256k1HdWallet,
|
||||
makeCosmoshubPath,
|
||||
Registry,
|
||||
} from "@cosmjs/proto-signing";
|
||||
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
|
||||
import { assert, sleep } from "@cosmjs/utils";
|
||||
import { MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx";
|
||||
import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin";
|
||||
import { BasicAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant";
|
||||
import { MsgGrantAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/tx";
|
||||
import { DeepPartial, MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
|
||||
import { AuthInfo, TxBody, TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
||||
import { Any } from "cosmjs-types/google/protobuf/any";
|
||||
import Long from "long";
|
||||
import protobuf from "protobufjs/minimal";
|
||||
|
||||
import { AminoTypes } from "./aminotypes";
|
||||
import { AminoMsgDelegate, MsgDelegateEncodeObject, MsgSendEncodeObject } from "./modules";
|
||||
import {
|
||||
AminoMsgDelegate,
|
||||
MsgDelegateEncodeObject,
|
||||
MsgSendEncodeObject,
|
||||
setupFeegrantExtension,
|
||||
} from "./modules";
|
||||
import { QueryClient } from "./queryclient";
|
||||
import { PrivateSigningStargateClient, SigningStargateClient } from "./signingstargateclient";
|
||||
import { assertIsDeliverTxFailure, assertIsDeliverTxSuccess, isDeliverTxFailure } from "./stargateclient";
|
||||
import {
|
||||
@ -140,6 +157,78 @@ describe("SigningStargateClient", () => {
|
||||
const after = await client.getBalance(beneficiaryAddress, "ucosm");
|
||||
expect(after).toEqual(amount[0]);
|
||||
});
|
||||
|
||||
it("works with feegrant granter", async () => {
|
||||
pendingWithoutSimapp();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, {
|
||||
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1)],
|
||||
});
|
||||
const [{ address: signer }, { address: payer }] = await wallet.getAccounts();
|
||||
const client = await SigningStargateClient.connectWithSigner(
|
||||
simapp.tendermintUrl,
|
||||
wallet,
|
||||
defaultSigningClientOptions,
|
||||
);
|
||||
|
||||
const tmClient = await Tendermint34Client.connect(simapp.tendermintUrl);
|
||||
const queryClient = QueryClient.withExtensions(tmClient, setupFeegrantExtension);
|
||||
let allowanceExists: boolean;
|
||||
try {
|
||||
const _existingAllowance = await queryClient.feegrant.allowance(payer, signer);
|
||||
allowanceExists = true;
|
||||
} catch {
|
||||
allowanceExists = false;
|
||||
}
|
||||
|
||||
if (!allowanceExists) {
|
||||
// Create feegrant allowance
|
||||
const allowance: Any = {
|
||||
typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
|
||||
value: Uint8Array.from(
|
||||
BasicAllowance.encode({
|
||||
spendLimit: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
}).finish(),
|
||||
),
|
||||
};
|
||||
const grantMsg = {
|
||||
typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance",
|
||||
value: MsgGrantAllowance.fromPartial({
|
||||
granter: payer,
|
||||
grantee: signer,
|
||||
allowance: allowance,
|
||||
}),
|
||||
};
|
||||
const grantResult = await client.signAndBroadcast(payer, [grantMsg], "auto", "Create allowance");
|
||||
assertIsDeliverTxSuccess(grantResult);
|
||||
}
|
||||
|
||||
const balanceSigner1 = await client.getBalance(signer, "ucosm");
|
||||
const balancePayer1 = await client.getBalance(payer, "ucosm");
|
||||
|
||||
const sendAmount = coins(7890, "ucosm");
|
||||
const feeAmount = coins(4444, "ucosm");
|
||||
|
||||
// send
|
||||
const result = await client.sendTokens(signer, makeRandomAddress(), sendAmount, {
|
||||
amount: feeAmount,
|
||||
gas: "120000",
|
||||
granter: payer,
|
||||
});
|
||||
assertIsDeliverTxSuccess(result);
|
||||
|
||||
const balanceSigner2 = await client.getBalance(signer, "ucosm");
|
||||
const balancePayer2 = await client.getBalance(payer, "ucosm");
|
||||
|
||||
const diffSigner = Number(BigInt(balanceSigner1.amount) - BigInt(balanceSigner2.amount));
|
||||
const diffPayer = Number(BigInt(balancePayer1.amount) - BigInt(balancePayer2.amount));
|
||||
expect(diffSigner).toEqual(7890); // the send amount
|
||||
expect(diffPayer).toEqual(4444); // the fee
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendIbcTokens", () => {
|
||||
|
||||
@ -366,6 +366,8 @@ export class SigningStargateClient extends StargateClient {
|
||||
[{ pubkey, sequence: signedSequence }],
|
||||
signed.fee.amount,
|
||||
signedGasLimit,
|
||||
signed.fee.granter,
|
||||
signed.fee.payer,
|
||||
signMode,
|
||||
);
|
||||
return TxRaw.fromPartial({
|
||||
@ -399,7 +401,13 @@ export class SigningStargateClient extends StargateClient {
|
||||
};
|
||||
const txBodyBytes = this.registry.encode(txBodyEncodeObject);
|
||||
const gasLimit = Int53.fromString(fee.gas).toNumber();
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], fee.amount, gasLimit);
|
||||
const authInfoBytes = makeAuthInfoBytes(
|
||||
[{ pubkey, sequence }],
|
||||
fee.amount,
|
||||
gasLimit,
|
||||
fee.granter,
|
||||
fee.payer,
|
||||
);
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc);
|
||||
return TxRaw.fromPartial({
|
||||
|
||||
@ -74,7 +74,9 @@ async function sendTokens(
|
||||
},
|
||||
];
|
||||
const gasLimit = 200000;
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], feeAmount, gasLimit);
|
||||
const feeGranter = undefined;
|
||||
const feePayer = undefined;
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], feeAmount, gasLimit, feeGranter, feePayer);
|
||||
|
||||
const chainId = await client.getChainId();
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
|
||||
@ -364,7 +364,15 @@ describe("StargateClient", () => {
|
||||
const { accountNumber, sequence } = (await client.getSequence(address))!;
|
||||
const feeAmount = coins(2000, "ucosm");
|
||||
const gasLimit = 200000;
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], feeAmount, gasLimit);
|
||||
const feeGranter = undefined;
|
||||
const feePayer = undefined;
|
||||
const authInfoBytes = makeAuthInfoBytes(
|
||||
[{ pubkey, sequence }],
|
||||
feeAmount,
|
||||
gasLimit,
|
||||
feeGranter,
|
||||
feePayer,
|
||||
);
|
||||
|
||||
const chainId = await client.getChainId();
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
@ -421,7 +429,16 @@ describe("StargateClient", () => {
|
||||
const { accountNumber, sequence } = (await client.getSequence(address))!;
|
||||
const feeAmount = coins(2000, "ucosm");
|
||||
const gasLimit = 200000;
|
||||
const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], feeAmount, gasLimit, sequence);
|
||||
const feeGranter = undefined;
|
||||
const feePayer = undefined;
|
||||
const authInfoBytes = makeAuthInfoBytes(
|
||||
[{ pubkey, sequence }],
|
||||
feeAmount,
|
||||
gasLimit,
|
||||
feeGranter,
|
||||
feePayer,
|
||||
sequence,
|
||||
);
|
||||
|
||||
const chainId = await client.getChainId();
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
@ -482,9 +499,17 @@ describe("StargateClient", () => {
|
||||
const chainId = await client.getChainId();
|
||||
const feeAmount = coins(2000, "ucosm");
|
||||
const gasLimit = 200000;
|
||||
const feeGranter = undefined;
|
||||
const feePayer = undefined;
|
||||
|
||||
const { accountNumber: accountNumber1, sequence: sequence1 } = (await client.getSequence(address))!;
|
||||
const authInfoBytes1 = makeAuthInfoBytes([{ pubkey, sequence: sequence1 }], feeAmount, gasLimit);
|
||||
const authInfoBytes1 = makeAuthInfoBytes(
|
||||
[{ pubkey, sequence: sequence1 }],
|
||||
feeAmount,
|
||||
gasLimit,
|
||||
feeGranter,
|
||||
feePayer,
|
||||
);
|
||||
const signDoc1 = makeSignDoc(txBodyBytes, authInfoBytes1, chainId, accountNumber1);
|
||||
const { signature: signature1 } = await wallet.signDirect(address, signDoc1);
|
||||
const txRaw1 = TxRaw.fromPartial({
|
||||
@ -498,7 +523,13 @@ describe("StargateClient", () => {
|
||||
assertIsDeliverTxSuccess(txResult);
|
||||
|
||||
const { accountNumber: accountNumber2, sequence: sequence2 } = (await client.getSequence(address))!;
|
||||
const authInfoBytes2 = makeAuthInfoBytes([{ pubkey, sequence: sequence2 }], feeAmount, gasLimit);
|
||||
const authInfoBytes2 = makeAuthInfoBytes(
|
||||
[{ pubkey, sequence: sequence2 }],
|
||||
feeAmount,
|
||||
gasLimit,
|
||||
feeGranter,
|
||||
feePayer,
|
||||
);
|
||||
const signDoc2 = makeSignDoc(txBodyBytes, authInfoBytes2, chainId, accountNumber2);
|
||||
const { signature: signature2 } = await wallet.signDirect(address, signDoc2);
|
||||
const txRaw2 = TxRaw.fromPartial({
|
||||
|
||||
@ -234,6 +234,8 @@ export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
|
||||
}));
|
||||
const modifiedFeeAmount = coins(3000, "ucosm");
|
||||
const modifiedGasLimit = 333333;
|
||||
const modifiedFeeGranter = undefined;
|
||||
const modifiedFeePayer = undefined;
|
||||
const modifiedSignDoc = {
|
||||
...signDoc,
|
||||
bodyBytes: Uint8Array.from(TxBody.encode(modifiedTxBody).finish()),
|
||||
@ -241,6 +243,8 @@ export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
|
||||
signers,
|
||||
modifiedFeeAmount,
|
||||
modifiedGasLimit,
|
||||
modifiedFeeGranter,
|
||||
modifiedFeePayer,
|
||||
SignMode.SIGN_MODE_DIRECT,
|
||||
),
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user