Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simulation #208

Merged
merged 6 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/new-feet-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@burnt-labs/abstraxion-core": minor
"@burnt-labs/constants": minor
"@burnt-labs/signers": minor
---

Introduce gas simulation for AA transactions
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"packageManager": "[email protected]",
"dependencies": {
"@burnt-labs/tsconfig": "0.0.1-alpha.0",
"@burnt-labs/tsconfig": "workspace:*",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.1",
"eslint": "^8.48.0",
Expand Down
3 changes: 2 additions & 1 deletion packages/abstraxion-core/src/SignArbSecp256k1HdWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ export class SignArbSecp256k1HdWallet {
if (!accounts.every((account) => isDerivationJson(account))) {
throw new Error("Account is not in the correct format.");
}
const firstPrefix = (accounts[0] as Secp256k1Derivation).prefix;
const firstPrefix = (accounts[0] as unknown as Secp256k1Derivation)
.prefix;
if (!accounts.every(({ prefix }) => prefix === firstPrefix)) {
throw new Error("Accounts do not all have the same prefix");
}
Expand Down
6 changes: 6 additions & 0 deletions packages/constants/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export const xionCoin: Coin = {
},
};

export const xionGasValues = {
gasPrice: "0.025uxion",
gasAdjustment: 1.3,
gasAdjustmentMargin: 5000,
};

const commonInfo: ChainInfo = {
rpc: "undefined",
rest: "undefined",
Expand Down
1 change: 1 addition & 0 deletions packages/signers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"stytch": "^9.0.6"
},
"devDependencies": {
"@burnt-labs/constants": "workspace:*",
"@burnt-labs/eslint-config-custom": "workspace:*",
"@types/node": "^20",
"eslint": "^8.48.0",
Expand Down
166 changes: 160 additions & 6 deletions packages/signers/src/signers/utils/client.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { bech32 } from "bech32";
import { TxRaw, AuthInfo, SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import {
TxRaw,
AuthInfo,
SignDoc,
Fee,
} from "cosmjs-types/cosmos/tx/v1beta1/tx";
import {
GeneratedType,
Registry,
EncodeObject,
DirectSignResponse,
makeSignBytes,
} from "@cosmjs/proto-signing";
import {
Account,
calculateFee,
createProtobufRpcClient,
defaultRegistryTypes,
DeliverTxResponse,
GasPrice,
SignerData,
SigningStargateClientOptions,
StdFee,
} from "@cosmjs/stargate";
import { Tendermint37Client } from "@cosmjs/tendermint-rpc";
import { xionGasValues } from "@burnt-labs/constants";
import { MsgRegisterAccount } from "../../types/generated/abstractaccount/v1/tx";
import {
abstractAccountTypes,
Expand All @@ -34,6 +42,12 @@ import {
AddAuthenticator,
RemoveAuthenticator,
} from "../../interfaces/smartAccount";
import { Uint53 } from "@cosmjs/math";
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
import {
ServiceClientImpl,
SimulateRequest,
} from "cosmjs-types/cosmos/tx/v1beta1/service";

export const AADefaultRegistryTypes: ReadonlyArray<[string, GeneratedType]> = [
...defaultRegistryTypes,
Expand Down Expand Up @@ -93,7 +107,7 @@ export class AAClient extends SigningCosmWasmClient {
public async addAbstractAccountAuthenticator(
msg: AddAuthenticator,
memo = "",
fee: StdFee,
fee?: StdFee,
): Promise<DeliverTxResponse> {
if (!this.abstractSigner.abstractAccount) {
throw new Error("Abstract account address not set in signer");
Expand All @@ -108,7 +122,35 @@ export class AAClient extends SigningCosmWasmClient {
funds: [],
}),
};
const tx = await this.sign(sender, [addMsg], fee, memo);

const {
gasPrice: gasPriceString,
gasAdjustment,
gasAdjustmentMargin,
} = xionGasValues;

const simmedGas = await this.simulate(sender, [addMsg], memo);
const gasPrice = GasPrice.fromString(gasPriceString);
const calculatedFee: StdFee = calculateFee(
simmedGas * gasAdjustment,
gasPrice,
);

let defaultFee: StdFee;
let gas = (
parseInt(calculatedFee.gas) * gasAdjustment +
gasAdjustmentMargin
).toString();

const chainId = await this.getChainId();

if (/testnet/.test(chainId)) {
defaultFee = { amount: [{ amount: "0", denom: "uxion" }], gas: gas };
} else {
defaultFee = { amount: calculatedFee.amount, gas: gas };
}

const tx = await this.sign(sender, [addMsg], fee || defaultFee, memo);
return this.broadcastTx(TxRaw.encode(tx).finish());
}

Expand All @@ -120,7 +162,7 @@ export class AAClient extends SigningCosmWasmClient {
public async removeAbstractAccountAuthenticator(
msg: RemoveAuthenticator,
memo = "",
fee: StdFee,
fee?: StdFee,
): Promise<DeliverTxResponse> {
if (!this.abstractSigner.abstractAccount) {
throw new Error("Abstract account address not set in signer");
Expand All @@ -135,10 +177,122 @@ export class AAClient extends SigningCosmWasmClient {
funds: [],
}),
};
const tx = await this.sign(sender, [addMsg], fee, memo);

const {
gasPrice: gasPriceString,
gasAdjustment,
gasAdjustmentMargin,
} = xionGasValues;

const simmedGas = await this.simulate(sender, [addMsg], memo);
const gasPrice = GasPrice.fromString(gasPriceString);
const calculatedFee: StdFee = calculateFee(
simmedGas * gasAdjustment,
gasPrice,
);

let defaultFee: StdFee;
let gas = (
parseInt(calculatedFee.gas) * gasAdjustment +
gasAdjustmentMargin
).toString();

const chainId = await this.getChainId();

if (/testnet/.test(chainId)) {
defaultFee = { amount: [{ amount: "0", denom: "uxion" }], gas: gas };
} else {
defaultFee = { amount: calculatedFee.amount, gas: gas };
}

const tx = await this.sign(sender, [addMsg], fee || defaultFee, memo);
BurntVal marked this conversation as resolved.
Show resolved Hide resolved
return this.broadcastTx(TxRaw.encode(tx).finish());
}

/**
* Simulates a transaction and returns the gas used.
*
* @param {string} signerAddress - The address of the signer.
* @param {readonly EncodeObject[]} messages - An array of messages to include in the transaction.
* @param {string | undefined} memo - An optional memo to include in the transaction.
* @returns {Promise<number>} - The gas used by the simulated transaction.
* @throws Will throw an error if the account is not found or if the query client cannot be retrieved.
*/
public async simulate(
signerAddress: string,
messages: readonly EncodeObject[],
memo: string | undefined,
): Promise<number> {
const { sequence } = await this.getSequence(signerAddress);
const accountFromSigner = (await this.abstractSigner.getAccounts()).find(
(account) => account.address === signerAddress,
);

if (!accountFromSigner) {
throw new Error("No account found.");
}

const pubKeyBytes = bech32.fromWords(
bech32.decode(accountFromSigner.address).words,
);

const pubkey = Uint8Array.from(pubKeyBytes);

const queryClient = this.getQueryClient();
if (!queryClient) {
throw new Error("Couldn't get query client");
}

const rpc = createProtobufRpcClient(queryClient);
const queryService = new ServiceClientImpl(rpc);

const authInfo = AuthInfo.fromPartial({
fee: Fee.fromPartial({}),
signerInfos: [
{
publicKey: {
typeUrl: "/abstractaccount.v1.NilPubKey",
value: new Uint8Array([10, 32, ...pubkey]), // a little hack to encode the pk into proto bytes
},
modeInfo: {
single: {
mode: SignMode.SIGN_MODE_DIRECT,
},
},
sequence: BigInt(sequence),
},
],
});
const authInfoBytes = AuthInfo.encode(authInfo).finish();

const txBodyEncodeObject = {
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: {
messages: messages,
memo: memo || "AA Gas Simulation",
},
};
const bodyBytes = this.registry.encode(txBodyEncodeObject);

const tx = TxRaw.fromPartial({
bodyBytes,
authInfoBytes,
signatures: [new Uint8Array()],
});

const request = SimulateRequest.fromPartial({
txBytes: TxRaw.encode(tx).finish(),
});

const { gasInfo } = await queryService.Simulate(request);

if (!gasInfo) {
throw new Error("No gas info returned");
}

return Uint53.fromString(gasInfo.gasUsed.toString()).toNumber();
BurntVal marked this conversation as resolved.
Show resolved Hide resolved
}

public async getAccount(searchAddress: string): Promise<Account | null> {
const account =
await this.forceGetQueryClient().auth.account(searchAddress);
Expand Down
Loading
Loading