Skip to content

Commit

Permalink
Simulation (#208)
Browse files Browse the repository at this point in the history
* reintroduce simulation code & fix build errors

* build fix

* fix build type error

* changeset

* extrapolate logic to function to reduce redundancy

* Explicitly state the turbo version

---------

Co-authored-by: Justin <[email protected]>
  • Loading branch information
BurntVal and justinbarry authored Aug 5, 2024
1 parent 3f3aa37 commit d5780ce
Show file tree
Hide file tree
Showing 8 changed files with 383 additions and 4,070 deletions.
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
},
"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",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.3",
"turbo": "latest"
"turbo": "^2.0.11"
},
"pnpm": {
"overrides": {
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
159 changes: 153 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 @@ -85,6 +99,49 @@ export class AAClient extends SigningCosmWasmClient {
return this.signAndBroadcast(sender, [createMsg], "auto");
}

/**
* Simulates a transaction to estimate the gas and calculates the default fee.
*
* @param {string} sender - The address of the sender.
* @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<StdFee>} - The calculated default fee for the transaction.
*/
private async simulateDefaultFee(
sender: string,
messages: readonly EncodeObject[],
memo: string | undefined,
): Promise<StdFee> {
const {
gasPrice: gasPriceString,
gasAdjustment,
gasAdjustmentMargin,
} = xionGasValues;

const simmedGas = await this.simulate(sender, messages, 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 };
}

return defaultFee;
}

/**
* Create and a cosmwasm add authenticator msg to the abstract account
* @param msg the message to be sent
Expand All @@ -93,7 +150,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 +165,10 @@ export class AAClient extends SigningCosmWasmClient {
funds: [],
}),
};
const tx = await this.sign(sender, [addMsg], fee, memo);

const defaultFee = await this.simulateDefaultFee(sender, [addMsg], memo);

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

Expand All @@ -120,7 +180,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 +195,97 @@ export class AAClient extends SigningCosmWasmClient {
funds: [],
}),
};
const tx = await this.sign(sender, [addMsg], fee, memo);

const defaultFee = await this.simulateDefaultFee(sender, [addMsg], memo);

const tx = await this.sign(sender, [addMsg], fee || defaultFee, memo);
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();
}

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

0 comments on commit d5780ce

Please sign in to comment.