Skip to content

Commit

Permalink
prioritize lower spent fee amounts
Browse files Browse the repository at this point in the history
  • Loading branch information
jonator committed May 31, 2024
1 parent 8273e87 commit 99c43da
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 21 deletions.
87 changes: 87 additions & 0 deletions packages/tx/src/__tests__/gas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,93 @@ describe("getGasFeeAmount", () => {
expect(gasAmount.isNeededForTx).toBe(true);
});

it("should return the correct gas amount with an alternative fee token when the last available fee token is not fully spent", async () => {
const gasLimit = 1000;
const chainId = "osmosis-1";
const address = "osmo1...";
const baseFee = 0.04655;
const spotPrice = 8;

(queryBalances as jest.Mock).mockResolvedValue({
balances: [
{
denom: "uosmo",
amount: "1",
},
{
denom: "uion",
amount: "1000000",
},
],
} as Awaited<ReturnType<typeof queryBalances>>);
(queryFeesBaseGasPrice as jest.Mock).mockResolvedValue({
base_fee: baseFee.toString(),
} as Awaited<ReturnType<typeof queryFeesBaseGasPrice>>);
(queryFeeTokens as jest.Mock).mockResolvedValue({
fee_tokens: [
{
denom: "uion",
poolID: 2,
},
],
} as Awaited<ReturnType<typeof queryFeeTokens>>);
(queryFeesBaseDenom as jest.Mock).mockResolvedValue({
base_denom: "uosmo",
} as Awaited<ReturnType<typeof queryFeesBaseDenom>>);
(queryFeeTokenSpotPrice as jest.Mock).mockResolvedValue({
pool_id: "2",
spot_price: spotPrice.toString(),
} as Awaited<ReturnType<typeof queryFeeTokenSpotPrice>>);

const gasMultiplier = 1.5;
const coinsSpent = [{ denom: "uion", amount: "1000" }];

const gasAmount = (
await getGasFeeAmount({
chainId,
chainList: MockChains,
gasLimit: gasLimit.toString(),
bech32Address: address,
gasMultiplier,
coinsSpent,
})
)[0];

const expectedGasAmount = new Dec(baseFee * gasMultiplier)
.quo(new Dec(spotPrice))
.mul(new Dec(1.01))
.mul(new Dec(gasLimit))
.truncate()
.toString();

expect(queryBalances).toBeCalledWith({
chainId,
bech32Address: address,
chainList: MockChains,
});
expect(queryFeesBaseGasPrice).toBeCalledWith({
chainId,
chainList: MockChains,
});
expect(queryFeeTokens).toBeCalledWith({
chainId,
chainList: MockChains,
});
expect(queryFeesBaseDenom).toBeCalledWith({
chainId,
chainList: MockChains,
});
expect(queryFeeTokenSpotPrice).toBeCalledWith({
chainId,
chainList: MockChains,
denom: "uion",
});

expect(gasAmount.denom).toBe("uion");
expect(gasAmount.amount).toBe(expectedGasAmount);
expect(gasAmount.isNeededForTx).toBe(false);
});

it("should throw InsufficientFeeError when balance is insufficient without Osmosis fee module — no balances", async () => {
const gasLimit = 1000;
const chainId = "cosmoshub-4";
Expand Down
49 changes: 28 additions & 21 deletions packages/tx/src/gas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Dec } from "@keplr-wallet/unit";
import { Dec, Int } from "@keplr-wallet/unit";
import {
queryBalances,
queryBaseAccount,
Expand Down Expand Up @@ -345,34 +345,41 @@ export async function getGasFeeAmount({
coinsSpent.some(({ denom }) => denom === balance.denom)
);

for (const spentFeeBalance of spentFeeBalances) {
const { gasPrice: feeDenomGasPrice } = await getGasPriceByFeeDenom({
chainId,
chainList,
feeDenom: spentFeeBalance.denom,
gasMultiplier,
});
const feeAmount = feeDenomGasPrice
.mul(new Dec(gasLimit))
.truncate()
.toString();
// get all fee amounts for spent balances so we can prioritize the smallest amounts
const spentFeesWithAmounts = await Promise.all(
spentFeeBalances.map((spentFeeBalance) =>
getGasPriceByFeeDenom({
chainId,
chainList,
feeDenom: spentFeeBalance.denom,
gasMultiplier,
}).then(({ gasPrice }) => ({
...spentFeeBalance,
feeAmount: gasPrice.mul(new Dec(gasLimit)).truncate().toString(),
}))
)
);

// Check if this balance is not enough to pay the fee, if so skip.
if (new Dec(feeAmount).gt(new Dec(spentFeeBalance.amount))) continue;
// filter spent fees by those that are not enough to pay the fee and sort by smallest amounts
const spentFees = spentFeesWithAmounts
.filter((spentFee) =>
new Dec(spentFee.feeAmount).lte(new Dec(spentFee.amount))
)
.sort((a, b) => (new Int(a.feeAmount).lt(new Int(b.feeAmount)) ? -1 : 1));

for (const spentFeeAmount of spentFees) {
const spentAmount =
coinsSpent.find(({ denom }) => denom === spentFeeBalance.denom)?.amount ||
coinsSpent.find(({ denom }) => denom === spentFeeAmount.denom)?.amount ||
"0";
const totalSpent = new Dec(spentAmount).add(new Dec(feeAmount));
const isBalanceNeededForTx = totalSpent.gte(
new Dec(spentFeeBalance.amount)
const totalSpent = new Dec(spentAmount).add(
new Dec(spentFeeAmount.feeAmount)
);
const isBalanceNeededForTx = totalSpent.gte(new Dec(spentFeeAmount.amount));

// the coins spent in this transaction exceeds the amount needed for fee
return [
{
amount: feeAmount,
denom: spentFeeBalance.denom,
amount: spentFeeAmount.feeAmount,
denom: spentFeeAmount.denom,
isNeededForTx: isBalanceNeededForTx,
},
];
Expand Down

0 comments on commit 99c43da

Please sign in to comment.