Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { ProtocolAction } from '@aave/contract-helpers';
import { TransactionResponse } from '@ethersproject/providers';
import { Trans } from '@lingui/macro';
import { useQueryClient } from '@tanstack/react-query';
import { Contract } from 'ethers';
import { Contract, PopulatedTransaction } from 'ethers';
import React from 'react';
import { useTransactionHandler } from 'src/helpers/useTransactionHandler';
import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider';
import { usePoolSupportsMulticall } from 'src/hooks/pool/usePoolSupportsMulticall';
import { useModalContext } from 'src/hooks/useModal';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
Expand Down Expand Up @@ -39,16 +40,19 @@ export const CollateralChangeActions = ({
symbol,
selectedEmodeId,
}: CollateralChangeActionsProps) => {
const [setUsageAsCollateral, estimateGasLimit, currentMarketData] = useRootStore(
const [setUsageAsCollateral, setUserEMode, estimateGasLimit, currentMarketData] = useRootStore(
useShallow((state) => [
state.setUsageAsCollateral,
state.setUserEMode,
state.estimateGasLimit,
state.currentMarketData,
])
);
const { sendTx } = useWeb3Context();
const queryClient = useQueryClient();
const { mainTxState, setMainTxState, setTxError } = useModalContext();
// Resolved on modal open so it is cached well before the user picks an e-mode.
const { data: poolSupportsMulticall } = usePoolSupportsMulticall(currentMarketData);

const needsEmodeSwitch = selectedEmodeId !== undefined;

Expand Down Expand Up @@ -76,29 +80,52 @@ export const CollateralChangeActions = ({
skip: blocked || needsEmodeSwitch,
});

// Multicall action: setUserEMode + setUserUseReserveAsCollateral
const multicallAction = async () => {
// setUserEMode + setUserUseReserveAsCollateral, bundled via Pool.multicall
// where the pool supports it and sent sequentially where it does not.
const emodeAction = async () => {
try {
setMainTxState({ ...mainTxState, loading: true });

const poolContractAddress = currentMarketData.addresses.LENDING_POOL;
const poolInterface = new Contract(poolContractAddress, POOL_MULTICALL_ABI).interface;
const currentAccount = useRootStore.getState().account;

const setEModeCalldata = poolInterface.encodeFunctionData('setUserEMode', [selectedEmodeId]);

const setCollateralCalldata = poolInterface.encodeFunctionData(
'setUserUseReserveAsCollateral',
[poolReserve.underlyingAsset, usageAsCollateral]
);

let multicallTxData = await new Contract(
poolContractAddress,
POOL_MULTICALL_ABI
).populateTransaction.multicall([setEModeCalldata, setCollateralCalldata]);
multicallTxData = { ...multicallTxData, from: currentAccount };
multicallTxData = await estimateGasLimit(multicallTxData);
const response: TransactionResponse = await sendTx(multicallTxData);
let response: TransactionResponse;

if (poolSupportsMulticall) {
const poolContractAddress = currentMarketData.addresses.LENDING_POOL;
const poolInterface = new Contract(poolContractAddress, POOL_MULTICALL_ABI).interface;
const currentAccount = useRootStore.getState().account;

const setEModeCalldata = poolInterface.encodeFunctionData('setUserEMode', [
selectedEmodeId,
]);

const setCollateralCalldata = poolInterface.encodeFunctionData(
'setUserUseReserveAsCollateral',
[poolReserve.underlyingAsset, usageAsCollateral]
);

let multicallTxData = await new Contract(
poolContractAddress,
POOL_MULTICALL_ABI
).populateTransaction.multicall([setEModeCalldata, setCollateralCalldata]);
multicallTxData = { ...multicallTxData, from: currentAccount };
multicallTxData = await estimateGasLimit(multicallTxData);
response = await sendTx(multicallTxData);
} else {
// Pools below POOL_REVISION 8 have no multicall — send the two calls
// one after the other instead.
const eModeTxs = await setUserEMode(selectedEmodeId as number);
const eModeTx = await eModeTxs[0].tx();
const eModeTxWithGas = await estimateGasLimit(eModeTx as PopulatedTransaction);
const eModeResponse = await sendTx(eModeTxWithGas);
await eModeResponse.wait(1);

const collateralTxs = await setUsageAsCollateral({
reserve: poolReserve.underlyingAsset,
usageAsCollateral,
});
const collateralTx = await collateralTxs[0].tx();
const collateralTxWithGas = await estimateGasLimit(collateralTx as PopulatedTransaction);
response = await sendTx(collateralTxWithGas);
}

await response.wait(1);

Expand Down Expand Up @@ -136,7 +163,7 @@ export const CollateralChangeActions = ({
)
}
actionInProgressText={<Trans>Pending...</Trans>}
handleAction={needsEmodeSwitch ? multicallAction : action}
handleAction={needsEmodeSwitch ? emodeAction : action}
/>
);
};
36 changes: 26 additions & 10 deletions src/components/transactions/Supply/SupplyActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Contract, PopulatedTransaction } from 'ethers';
import { parseUnits, splitSignature } from 'ethers/lib/utils';
import React, { useEffect, useState } from 'react';
import { useAppDataContext } from 'src/hooks/app-data-provider/useAppDataProvider';
import { usePoolSupportsMulticall } from 'src/hooks/pool/usePoolSupportsMulticall';
import { SignedParams, useApprovalTx } from 'src/hooks/useApprovalTx';
import { usePoolApprovedAmount } from 'src/hooks/useApprovedAmount';
import { useModalContext } from 'src/hooks/useModal';
Expand Down Expand Up @@ -96,6 +97,8 @@ export const SupplyActions = React.memo(
} = useModalContext();
const permitAvailable = tryPermit({ reserveAddress: poolAddress, isWrappedBaseAsset });
const { sendTx } = useWeb3Context();
// Resolved on modal open so it is cached well before the user picks an e-mode.
const { data: poolSupportsMulticall } = usePoolSupportsMulticall(currentMarketData);
const queryClient = useQueryClient();

const [signatureParams, setSignatureParams] = useState<SignedParams | undefined>();
Expand Down Expand Up @@ -176,22 +179,35 @@ export const SupplyActions = React.memo(
const isNativeAsset = poolAddress.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase();

if (needsEmodeSwitch) {
if (isNativeAsset) {
// Native ETH goes through WETH Gateway which is a separate contract —
// can't bundle with Pool.multicall. Send setUserEMode first, then supply.
if (isNativeAsset || !poolSupportsMulticall) {
// Native ETH goes through WETH Gateway which is a separate contract,
// and pools below POOL_REVISION 8 have no multicall at all — neither
// can be bundled. Send setUserEMode first, then supply.
const eModeTxs = await setUserEMode(selectedEmodeId);
const eModeTx = await eModeTxs[0].tx();
const eModeTxWithGas = await estimateGasLimit(eModeTx as PopulatedTransaction);
const eModeResponse = await sendTx(eModeTxWithGas);
await eModeResponse.wait(1);

action = ProtocolAction.supply;
let supplyTxData = supply({
amount: parseUnits(amountToSupply, decimals).toString(),
reserve: poolAddress,
});
supplyTxData = await estimateGasLimit(supplyTxData);
response = await sendTx(supplyTxData);
if (usePermit && signatureParams) {
action = ProtocolAction.supplyWithPermit;
let signedSupplyWithPermitTxData = supplyWithPermit({
signature: signatureParams.signature,
amount: parseUnits(amountToSupply, decimals).toString(),
reserve: poolAddress,
deadline: signatureParams.deadline,
});
signedSupplyWithPermitTxData = await estimateGasLimit(signedSupplyWithPermitTxData);
response = await sendTx(signedSupplyWithPermitTxData);
} else {
action = ProtocolAction.supply;
let supplyTxData = supply({
amount: parseUnits(amountToSupply, decimals).toString(),
reserve: poolAddress,
});
supplyTxData = await estimateGasLimit(supplyTxData);
response = await sendTx(supplyTxData);
}
await response.wait(1);
} else {
// ERC20: Bundle setUserEMode + supply via Pool multicall
Expand Down
40 changes: 40 additions & 0 deletions src/hooks/pool/usePoolSupportsMulticall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useQuery } from '@tanstack/react-query';
import { Contract } from 'ethers';
import { MarketDataType } from 'src/ui-config/marketsConfig';
import { getProvider } from 'src/utils/marketsAndNetworksConfig';

/**
* Pool.multicall was introduced in Aave v3.4, which bumped POOL_REVISION to 8.
* Pools below that revision revert on the call, so anything that bundles pool
* actions has to fall back to sending them sequentially.
*/
export const MULTICALL_POOL_REVISION = 8;

const POOL_REVISION_ABI = ['function POOL_REVISION() view returns (uint256)'];

export const usePoolSupportsMulticall = (marketData: MarketDataType) => {
const poolAddress = marketData.addresses.LENDING_POOL;
const chainId = marketData.chainId;

return useQuery({
queryFn: async () => {
try {
const provider = getProvider(chainId);
const revision = await new Contract(
poolAddress,
POOL_REVISION_ABI,
provider
).POOL_REVISION();
return revision.gte(MULTICALL_POOL_REVISION);
} catch (error) {
// Pools that predate the getter, and any RPC failure, fall back to
// sequential transactions — those work on every revision.
console.error('Error reading POOL_REVISION:', error);
return false;
}
},
queryKey: ['poolSupportsMulticall', poolAddress, chainId],
enabled: !!poolAddress,
staleTime: Infinity,
});
};
Loading