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

Withdraw gas limit fix #49

Merged
merged 3 commits into from
Oct 25, 2024
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
18 changes: 3 additions & 15 deletions apps/create-vesting/src/app/signing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,10 @@ import type { Address } from "viem";
export default function Page() {
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [permitTxs, setPermitTxs] = useState<BaseTransaction[]>([]);
const {
actions,
hookInfo,
cowShed,
signer,
context,
publicClient,
cowShedProxy,
} = useIFrameContext();
const { hookInfo, cowShed, signer, context, cowShedProxy } =
useIFrameContext();

const submitHook = useSubmitHook({
actions,
context,
publicClient,
recipientOverride: hookInfo?.recipientOverride,
});
const submitHook = useSubmitHook(hookInfo?.recipientOverride);
const cowShedSignature = useCowShedSignature({
cowShed,
signer,
Expand Down
17 changes: 3 additions & 14 deletions apps/deposit-pool/src/app/signing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,11 @@ import type { Address } from "viem";
export default function Page() {
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [permitTxs, setPermitTxs] = useState<BaseTransaction[]>([]);
const {
actions,
hookInfo,
cowShed,
signer,
context,
publicClient,
cowShedProxy,
} = useIFrameContext();
const { hookInfo, cowShed, signer, context, cowShedProxy } =
useIFrameContext();
const [account, setAccount] = useState<string>();
const router = useRouter();
const submitHook = useSubmitHook({
actions,
context,
publicClient,
});
const submitHook = useSubmitHook();
const cowShedSignature = useCowShedSignature({
cowShed,
signer,
Expand Down
3 changes: 2 additions & 1 deletion apps/withdraw-pool/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default function Page() {
if (!pools || pools.length === 0) {
return (
<span className="mt-10 text-center">
You don't have liquidity in a CoW AMM pool
You don't have unstaked liquidity in a CoW AMM pool
</span>
);
}
Expand All @@ -87,6 +87,7 @@ export default function Page() {
pools={pools || []}
PoolItemInfo={PoolItemInfo}
selectedPool={selectedPool}
tooltipText="Withdraw of staked liquidity or pool with low user balance are not supported"
/>
<PoolForm selectedPool={selectedPool} />
</div>
Expand Down
23 changes: 7 additions & 16 deletions apps/withdraw-pool/src/app/signing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,12 @@ import type { WithdrawSchemaType } from "#/utils/schema";
export default function Page() {
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [permitTxs, setPermitTxs] = useState<BaseTransaction[]>([]);
const {
actions,
hookInfo,
cowShed,
signer,
context,
publicClient,
cowShedProxy,
} = useIFrameContext();
const { hookInfo, cowShed, signer, context, cowShedProxy } =
useIFrameContext();
const { getValues } = useFormContext<WithdrawSchemaType>();
const [account, setAccount] = useState<string>();
const router = useRouter();
const submitHook = useSubmitHook({
actions,
context,
publicClient,
});
const submitHook = useSubmitHook();
const cowShedSignature = useCowShedSignature({
cowShed,
signer,
Expand Down Expand Up @@ -68,8 +57,10 @@ export default function Page() {
const cowShedCall = await cowShedSignature(txs);
if (!cowShedCall) throw new Error("Error signing hooks");
const withdrawPct = getValues("withdrawPct");
const cowShedCallDataWithWithdrawPct =
cowShedCall + Number(Number(withdrawPct).toFixed()).toString(16); // adding to facilitate editing the hook
const withdrawPctHex = Number(Number(withdrawPct).toFixed())
.toString(16)
.padStart(4, "0");
const cowShedCallDataWithWithdrawPct = cowShedCall + withdrawPctHex; // adding to facilitate editing the hook
await submitHook({
target: cowShed.getFactoryAddress(),
callData: cowShedCallDataWithWithdrawPct,
Expand Down
6 changes: 3 additions & 3 deletions apps/withdraw-pool/src/components/PoolBalancePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function PoolBalancesPreview({

const { withdrawPct } = useWatch({ control });

const _withdrawBalance = useMemo(() => {
const withdrawBalance = useMemo(() => {
if (!poolBalances || !withdrawPct) return [];
return poolBalances.map((poolBalance) => ({
...poolBalance,
Expand Down Expand Up @@ -55,7 +55,7 @@ export function PoolBalancesPreview({
</TableRow>
</TableHeader>
<TableBody>
{poolBalances.map((balance, _index) => {
{poolBalances.map((balance, index) => {
return (
<TableRow
className="hover:bg-transparent border-none"
Expand All @@ -81,7 +81,7 @@ export function PoolBalancesPreview({
token={balance.token}
balance={Number(
formatUnits(
BigInt(balance.balance.toString()),
BigInt(withdrawBalance[index].balance.toString()),
balance.token.decimals,
),
)}
Expand Down
7 changes: 6 additions & 1 deletion apps/withdraw-pool/src/hooks/useUserPools.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { SupportedChainId } from "@cowprotocol/cow-sdk";

import { usePools } from "@bleu/cow-hooks-ui";
import { BigNumber } from "ethers";
import type { Address } from "viem";

export function useUserPools(chainId?: SupportedChainId, user?: Address) {
return usePools(
const useSwrData = usePools(
{ poolTypeIn: ["COW_AMM"], userAddress: user },
chainId,
"userbalanceUsd",
);
const data = useSwrData.data?.filter((pool) =>
BigNumber.from(pool.userBalance.walletBalance).gt(BigNumber.from("10")),
);
return { ...useSwrData, data };
}
26 changes: 16 additions & 10 deletions packages/cow-hooks-ui/src/PoolsDropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,20 @@ import {
CommandItem,
CommandList,
} from "./ui/Command";
import { InfoTooltip } from "./ui/TooltipBase";

export function PoolsDropdownMenu({
onSelect,
pools,
PoolItemInfo,
selectedPool,
tooltipText,
}: {
onSelect: (pool: IPool) => void;
pools: IPool[];
PoolItemInfo: React.ComponentType<{ pool: IPool }>;
selectedPool?: IPool;
tooltipText?: string;
}) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
Expand All @@ -49,22 +52,25 @@ export function PoolsDropdownMenu({
return (
<div className="flex w-full flex-col items-center gap-2">
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger
className={
"w-full flex p-2 justify-between rounded-xl space-x-1 items-center text-sm bg-muted shadow-sm text-foreground group hover:bg-primary hover:text-primary-foreground"
}
onClick={() => setOpen(true)}
>
{selectedPool ? <PoolLogo pool={selectedPool} /> : "Select a pool"}
<ChevronDownIcon className="size-4" />
</Dialog.Trigger>
<div className="flex flex-row gap-1 w-full justify-between">
<Dialog.Trigger
className={
"w-full flex p-2 justify-between rounded-xl space-x-1 items-center text-sm bg-muted shadow-sm text-foreground group hover:bg-primary hover:text-primary-foreground"
}
onClick={() => setOpen(true)}
>
{selectedPool ? <PoolLogo pool={selectedPool} /> : "Select a pool"}
<ChevronDownIcon className="size-4" />
</Dialog.Trigger>
</div>
<Dialog.Portal>
<Dialog.Content className="fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%] border bg-background p-[15px] w-screen h-screen bg-background border-none flex flex-col gap-2">
<div className="flex items-center gap-2">
<Dialog.Close className="cursor-pointer hover:opacity-50">
<ArrowLeftIcon className="size-5" />
</Dialog.Close>
<span>Select a pool</span>
<Dialog.Title>Select a pool</Dialog.Title>
{tooltipText && <InfoTooltip text={tooltipText} />}
</div>
<Command
filter={(value: string, search: string) => {
Expand Down
28 changes: 28 additions & 0 deletions packages/cow-hooks-ui/src/hooks/useEstimateGas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS } from "@cowprotocol/cow-sdk";
import type { CowHook } from "@cowprotocol/hook-dapp-lib";
import { useCallback } from "react";
import type { Address } from "viem";
import { useIFrameContext } from "../context/iframe";
import { retryAsync } from "../utils/retry";

export function useEstimateGas() {
const { context, actions, publicClient } = useIFrameContext();
return useCallback(
async (hook: Omit<CowHook, "gasLimit" | "dappId">) => {
if (!context || !actions || !publicClient)
throw new Error("Missing context");

return await retryAsync<bigint>(() => {
return publicClient.estimateGas({
account: COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS[
context.chainId
] as `0x${string}`,
to: hook.target as Address,
value: BigInt("0"),
data: hook.callData as `0x${string}`,
});
});
},
[context, actions, publicClient],
);
}
49 changes: 15 additions & 34 deletions packages/cow-hooks-ui/src/hooks/useSubmitHook.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,24 @@
import { COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS } from "@cowprotocol/cow-sdk";
import type { CoWHookDappActions, CowHook } from "@cowprotocol/hook-dapp-lib";
import type { CowHook } from "@cowprotocol/hook-dapp-lib";
import { BigNumber } from "ethers";
import { useCallback } from "react";
import type { Address, PublicClient } from "viem";
import type { HookDappContextAdjusted } from "../types";
import { useIFrameContext } from "../context/iframe";
import { useEstimateGas } from "./useEstimateGas";

export function useSubmitHook({
actions,
context,
publicClient,
recipientOverride,
}: {
actions: CoWHookDappActions | undefined;
context: HookDappContextAdjusted | undefined;
publicClient: PublicClient | undefined;
recipientOverride?: string;
}) {
export function useSubmitHook(recipientOverride?: string) {
const { context, actions } = useIFrameContext();
const estimateGas = useEstimateGas();
return useCallback(
async (hook: Omit<CowHook, "gasLimit" | "dappId">) => {
if (!context || !actions || !publicClient)
throw new Error("Missing context");
if (!context || !actions) throw new Error("Missing context");

const estimatedGas = await publicClient
.estimateGas({
account: COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS[
context.chainId
] as `0x${string}`,
to: hook.target as Address,
value: BigInt(0),
data: hook.callData as `0x${string}`,
})
.catch(() => {
console.error("Failed to estimated hook gas", {
chainId: context.chainId,
calldata: hook.callData,
target: hook.target,
});
throw new Error("Failed to estimate hook gas");
const estimatedGas = await estimateGas(hook).catch(() => {
console.error("Failed to estimated hook gas", {
chainId: context.chainId,
calldata: hook.callData,
target: hook.target,
});
throw new Error("Failed to estimated hook gas");
});

const gasLimit = BigNumber.from(estimatedGas)
.mul(120)
Expand All @@ -60,6 +41,6 @@ export function useSubmitHook({

actions.addHook({ hook: hookWithGasLimit, recipientOverride });
},
[actions, context, recipientOverride, publicClient],
[actions, context, recipientOverride, estimateGas],
);
}
40 changes: 40 additions & 0 deletions packages/cow-hooks-ui/src/utils/retry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
type RetryOptions = {
maxAttempts?: number;
delayMs?: number;
onRetry?: (error: Error, attempt: number) => void;
};

export async function retryAsync<T>(
fn: () => Promise<T>,
options: RetryOptions = {},
): Promise<T> {
const {
maxAttempts = 5,
delayMs = 1000,
onRetry = (error, attempt) =>
console.warn(
`Attempt ${attempt} failed with error: ${error.message}. Retrying...`,
),
} = options;

let lastError: Error | null = null;

for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));

if (attempt === maxAttempts) {
break;
}

onRetry(lastError, attempt);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}

throw new Error(
`Failed after ${maxAttempts} attempts. Last error: ${lastError?.message}`,
);
}
Loading