Skip to content

Commit

Permalink
feat: evm gas refund
Browse files Browse the repository at this point in the history
  • Loading branch information
songwongtp committed Jan 29, 2025
1 parent 944d128 commit 904c8f7
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 42 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- [#1211](https://github.com/alleslabs/celatone-frontend/pull/1211) Implement evm gas refund logic
- [#1209](https://github.com/alleslabs/celatone-frontend/pull/1209) Implement evm contract details interaction
- [#1208](https://github.com/alleslabs/celatone-frontend/pull/1208) Implement evm interaction section
- [#1207](https://github.com/alleslabs/celatone-frontend/pull/1207) Add EVM contract details compiler settings
Expand Down
34 changes: 23 additions & 11 deletions src/lib/pages/evm-tx-details/components/EvmTxGasReceipt.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Box, Button, Divider, Flex } from "@chakra-ui/react";
import { Box, Button, Divider, Flex, SimpleGrid } from "@chakra-ui/react";
import { useState } from "react";

import type { GasInfo } from "../data";
import { CustomIcon } from "lib/components/icon";
import { LabelText } from "lib/components/LabelText";
import { MotionBox } from "lib/components/MotionBox";
import { TokenImageWithAmount } from "lib/components/token";
import { formatInteger, formatTokenWithValue } from "lib/utils";
import {
formatInteger,
formatPrettyPercent,
formatTokenWithValue,
} from "lib/utils";

interface EvmTxGasReceiptProps {
gasInfo: GasInfo;
Expand All @@ -18,15 +22,23 @@ export const EvmTxGasReceipt = ({ gasInfo }: EvmTxGasReceiptProps) => {
return (
<>
<Divider />
<LabelText flex={1} label="Transaction Fee">
<TokenImageWithAmount token={gasInfo.txFee} hasTrailingZeros={false} />
</LabelText>
<LabelText label="Gas Price">
{formatTokenWithValue(gasInfo.gasPrice, undefined, false)}
</LabelText>
<LabelText label="Usage by Tx & Gas Limit">
{`${formatInteger(gasInfo.gasUsed)}/${formatInteger(gasInfo.gasLimit)}`}
</LabelText>
<SimpleGrid columns={{ base: 2, md: 1 }} gap={4}>
<LabelText flex={1} label="Transaction Fee">
<TokenImageWithAmount
token={gasInfo.txFee}
hasTrailingZeros={false}
/>
</LabelText>
<LabelText label="Gas Price">
{formatTokenWithValue(gasInfo.gasPrice, undefined, false)}
</LabelText>
<LabelText label="Usage by Tx & Gas Limit">
{`${formatInteger(gasInfo.gasUsed)}/${formatInteger(gasInfo.gasLimit)}`}
</LabelText>
<LabelText label="Gas Refund Percentage">
{`${formatPrettyPercent(gasInfo.gasRefundRatio, 2, true)}`}
</LabelText>
</SimpleGrid>
{gasInfo.isEIP1559 && (
<Box>
<MotionBox
Expand Down
16 changes: 11 additions & 5 deletions src/lib/pages/evm-tx-details/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
useTxDataJsonRpc,
} from "lib/services/tx";
import type { TxData, TxDataJsonRpc } from "lib/services/types";
import type { Option, TokenWithValue } from "lib/types";
import { type Option, type Ratio, type TokenWithValue } from "lib/types";
import { coinToTokenWithValue } from "lib/utils";

export interface GasInfo {
Expand All @@ -19,6 +19,7 @@ export interface GasInfo {
gasPrice: TokenWithValue;
gasUsed: Big;
gasLimit: Big;
gasRefundRatio: Ratio<number>;
// eip-1559
baseFee: TokenWithValue;
maxFee: TokenWithValue;
Expand Down Expand Up @@ -50,6 +51,8 @@ export const useEvmTxDetailsData = (evmTxHash: string): EvmTxDetailsData => {
useBlockDataJsonRpc(evmTxData?.tx.blockNumber.toNumber());

const evmDenom = evmParams?.params.feeDenom;
const gasRefundRatio =
evmParams?.params.gasRefundRatio ?? (0 as Ratio<number>);

const evmTxValue = useMemo<Option<TokenWithValue>>(() => {
if (!evmTxData) return undefined;
Expand All @@ -62,11 +65,13 @@ export const useEvmTxDetailsData = (evmTxHash: string): EvmTxDetailsData => {

const gasInfo = useMemo<Option<GasInfo>>(() => {
if (!evmTxData || !blockData) return undefined;
const gasRefund = evmTxData.tx.gas
.minus(evmTxData.txReceipt.gasUsed)
.mul(gasRefundRatio);
const actualGasAmount = evmTxData.tx.gas.minus(gasRefund);
const txFee = coinToTokenWithValue(
evmDenom ?? "",
evmTxData.txReceipt.gasUsed
.mul(evmTxData.txReceipt.effectiveGasPrice)
.toString(),
actualGasAmount.mul(evmTxData.txReceipt.effectiveGasPrice).toString(),
assetInfos
);

Expand Down Expand Up @@ -100,11 +105,12 @@ export const useEvmTxDetailsData = (evmTxHash: string): EvmTxDetailsData => {
gasPrice,
gasUsed: evmTxData.txReceipt.gasUsed,
gasLimit: evmTxData.tx.gas,
gasRefundRatio,
baseFee,
maxFee,
maxPriorityFee,
};
}, [assetInfos, blockData, evmDenom, evmTxData]);
}, [assetInfos, blockData, evmDenom, evmTxData, gasRefundRatio]);

return {
isLoading:
Expand Down
32 changes: 22 additions & 10 deletions src/lib/pages/tx-details/components/TxInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import { UserDocsLink } from "lib/components/UserDocsLink";
import { useAssetInfos } from "lib/services/assetService";
import { useMovePoolInfos } from "lib/services/move/poolService";
import type { TxData } from "lib/services/types";
import { Option, Ratio } from "lib/types";
import {
coinToTokenWithValue,
computeCosmosFee,
formatInteger,
formatPrettyPercent,
formatTokenWithValue,
} from "lib/utils";

interface TxInfoProps extends FlexProps {
txData: TxData;
gasRefundRatio: Option<Ratio<number>>;
}

const Container = chakra(Flex, {
Expand All @@ -26,7 +29,11 @@ const Container = chakra(Flex, {
},
});

export const TxInfo = ({ txData, ...flexProps }: TxInfoProps) => {
export const TxInfo = ({
txData,
gasRefundRatio,
...flexProps
}: TxInfoProps) => {
const { data: assetInfos } = useAssetInfos({
withPrices: true,
});
Expand All @@ -35,14 +42,14 @@ export const TxInfo = ({ txData, ...flexProps }: TxInfoProps) => {
});

const feeCoin = txData.tx.authInfo?.fee?.amount[0];
const feeToken = feeCoin
? coinToTokenWithValue(
feeCoin.denom,
feeCoin.amount,
assetInfos,
movePoolInfos
)
: undefined;
const feeToken = computeCosmosFee(
feeCoin,
txData.gasUsed,
txData.gasWanted,
gasRefundRatio,
assetInfos,
movePoolInfos
);
return (
<Container {...flexProps}>
<LabelText label="Network">{txData.chainId}</LabelText>
Expand All @@ -66,6 +73,11 @@ export const TxInfo = ({ txData, ...flexProps }: TxInfoProps) => {
<LabelText label="Gas Used/Wanted">
{`${formatInteger(txData.gasUsed)}/${formatInteger(txData.gasWanted)}`}
</LabelText>
{gasRefundRatio && (
<LabelText label="Gas Refund Percentage">
{`${formatPrettyPercent(gasRefundRatio, 2, true)}`}
</LabelText>
)}
<LabelText label="Memo">
{txData.tx.body.memo || (
<Text variant="body2" color="text.dark">
Expand Down
32 changes: 22 additions & 10 deletions src/lib/pages/tx-details/components/TxInfoMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import { LabelText } from "lib/components/LabelText";
import { useAssetInfos } from "lib/services/assetService";
import { useMovePoolInfos } from "lib/services/move/poolService";
import type { TxData } from "lib/services/types";
import { Option, Ratio } from "lib/types";
import {
coinToTokenWithValue,
computeCosmosFee,
formatInteger,
formatPrettyPercent,
formatTokenWithValue,
} from "lib/utils";

interface TxInfoMobileProps extends FlexProps {
txData: TxData;
gasRefundRatio: Option<Ratio<number>>;
}

const Container = chakra(Flex, {
Expand All @@ -27,7 +30,11 @@ const Container = chakra(Flex, {
},
});

export const TxInfoMobile = ({ txData, ...flexProps }: TxInfoMobileProps) => {
export const TxInfoMobile = ({
txData,
gasRefundRatio,
...flexProps
}: TxInfoMobileProps) => {
const { data: assetInfos } = useAssetInfos({
withPrices: true,
});
Expand All @@ -36,14 +43,14 @@ export const TxInfoMobile = ({ txData, ...flexProps }: TxInfoMobileProps) => {
});

const feeCoin = txData.tx.authInfo.fee?.amount[0];
const feeToken = feeCoin
? coinToTokenWithValue(
feeCoin.denom,
feeCoin.amount,
assetInfos,
movePoolInfos
)
: undefined;
const feeToken = computeCosmosFee(
feeCoin,
txData.gasUsed,
txData.gasWanted,
gasRefundRatio,
assetInfos,
movePoolInfos
);
return (
<Container {...flexProps}>
<Flex>
Expand Down Expand Up @@ -75,6 +82,11 @@ export const TxInfoMobile = ({ txData, ...flexProps }: TxInfoMobileProps) => {
)}`}
</LabelText>
</Flex>
{gasRefundRatio && (
<LabelText flex={1} label="Gas Refund Percentage">
{`${formatPrettyPercent(gasRefundRatio, 2, true)}`}
</LabelText>
)}
<LabelText label="Memo">
{txData.tx.body.memo || (
<Text variant="body2" color="text.dark">
Expand Down
21 changes: 18 additions & 3 deletions src/lib/pages/tx-details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { TxHeader, TxInfo, TxInfoMobile } from "./components";
import { MessageSection } from "./components/MessageSection";
import { useTxRedirect } from "./hooks";
import { zTxDetailsQueryParams } from "./types";
import { useEvmParams } from "lib/services/evm";

const mapTxisFailed = (isFailed: Option<boolean>) => {
switch (isFailed) {
Expand All @@ -37,8 +38,13 @@ const TxDetailsBody = ({ txHash }: { txHash: string }) => {

const isCheckingRedirect = useTxRedirect(txHash);
const { data, isLoading } = useTxData(txHash);

const { data: evmParams, isFetching: isEvmParamsFetching } = useEvmParams();
const { data: relatedEvmTxHash, isFetching: isRelatedEvmTxFetching } =
useEvmTxHashByCosmosTxHash(txHash);
const gasRefundRatio = relatedEvmTxHash
? evmParams?.params.gasRefundRatio
: undefined;

useEffect(() => {
if (router.isReady && !isLoading)
Expand All @@ -47,7 +53,12 @@ const TxDetailsBody = ({ txHash }: { txHash: string }) => {
});
}, [router.isReady, data, isLoading]);

if (isCheckingRedirect || isLoading || isRelatedEvmTxFetching)
if (
isCheckingRedirect ||
isLoading ||
isRelatedEvmTxFetching ||
isEvmParamsFetching
)
return <Loading withBorder />;
if (!data) return <InvalidTx />;

Expand All @@ -63,9 +74,13 @@ const TxDetailsBody = ({ txHash }: { txHash: string }) => {
{data ? (
<>
<TxHeader mt={2} txData={data} />
{isMobile && <TxInfoMobile txData={data} />}
{isMobile && (
<TxInfoMobile txData={data} gasRefundRatio={gasRefundRatio} />
)}
<Flex my={{ base: 0, md: 12 }} gap={4} justify="space-between">
{!isMobile && <TxInfo txData={data} />}
{!isMobile && (
<TxInfo txData={data} gasRefundRatio={gasRefundRatio} />
)}
<MessageSection txData={data} relatedEvmTxHash={relatedEvmTxHash} />
</Flex>
</>
Expand Down
11 changes: 9 additions & 2 deletions src/lib/services/evm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ import { ProxyResult } from "./json-rpc/proxy/types";

export const useEvmParams = () => {
const {
chainConfig: { lcd: lcdEndpoint },
chainConfig: {
lcd: lcdEndpoint,
features: { evm },
},
} = useCelatoneApp();

return useQuery(
[CELATONE_QUERY_KEYS.EVM_PARAMS_LCD, lcdEndpoint],
async () => getEvmParams(lcdEndpoint),
async () => {
if (!evm.enabled) throw new Error("EVM is not enabled (useEvmParams)");
return getEvmParams(lcdEndpoint);
},
{
enabled: evm.enabled,
refetchOnWindowFocus: false,
retry: false,
staleTime: Infinity,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/services/types/evm/params.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { zRatio } from "lib/types";
import { snakeToCamel } from "lib/utils";
import { z } from "zod";

Expand All @@ -9,6 +10,7 @@ export const zEvmParams = z
allow_custom_erc20: z.boolean(),
allowed_custom_erc20s: z.string().array(),
fee_denom: z.string(),
gas_refund_ratio: zRatio(z.coerce.number()),
}),
})
.transform(snakeToCamel);
40 changes: 39 additions & 1 deletion src/lib/utils/fee.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import type { StdFee } from "@cosmjs/stargate";

import type { Option } from "lib/types";
import {
type AssetInfos,
big,
type Coin,
type MovePoolInfos,
type Option,
type Ratio,
} from "lib/types";

import { coinsFromStr } from "./funds";
import { coinToTokenWithValue } from "./assetValue";

export const feeFromStr = (uFee: Option<string>): Option<StdFee> => {
if (!uFee) return undefined;
Expand All @@ -14,3 +22,33 @@ export const feeFromStr = (uFee: Option<string>): Option<StdFee> => {

return fee;
};

export const computeCosmosFee = (
feeCoin: Option<Coin>,
gasUsed: string,
gasLimit: string,
gasRefundRatio: Option<Ratio<number>>,
assetInfos: Option<AssetInfos>,
movePoolInfos: Option<MovePoolInfos>
) => {
if (!feeCoin) return undefined;

if (gasRefundRatio) {
const gasPrice = big(feeCoin.amount).div(gasLimit);
const gasRefund = big(gasLimit).minus(gasUsed).mul(gasRefundRatio);
const actualFeeAmount = gasPrice.mul(big(gasLimit).minus(gasRefund));
return coinToTokenWithValue(
feeCoin.denom,
actualFeeAmount.toFixed(0),
assetInfos,
movePoolInfos
);
}

return coinToTokenWithValue(
feeCoin.denom,
feeCoin.amount,
assetInfos,
movePoolInfos
);
};

0 comments on commit 904c8f7

Please sign in to comment.