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

feat: evm gas refund #1211

Merged
merged 1 commit into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
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
);
};