Skip to content
Closed
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
4 changes: 4 additions & 0 deletions packages/blue-sdk/src/vault/v2/VaultV2Adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@ import type { BigIntish } from "../../types";
import type { CapacityLimit } from "../../utils";

export interface IVaultV2Adapter {
type: string;
address: Address;
parentVault: Address;
adapterId: Hash;
skimRecipient: Address;
}

export abstract class VaultV2Adapter implements IVaultV2Adapter {
public readonly type: string;
public readonly address: Address;
public readonly parentVault: Address;
public readonly adapterId: Hash;
public skimRecipient: Address;

constructor({
type,
address,
parentVault,
adapterId,
skimRecipient,
}: IVaultV2Adapter) {
this.type = type;
this.address = address;
this.parentVault = parentVault;
this.adapterId = adapterId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ import { VaultV2Adapter } from "./VaultV2Adapter";
import type { IAccrualVaultV2Adapter, IVaultV2Adapter } from "./VaultV2Adapter";

export interface IVaultV2MorphoMarketV1Adapter
extends Omit<IVaultV2Adapter, "adapterId"> {
extends Omit<IVaultV2Adapter, "adapterId" | "type"> {
type?: "VaultV2MorphoMarketV1Adapter";
marketParamsList: IMarketParams[];
}

export class VaultV2MorphoMarketV1Adapter
extends VaultV2Adapter
implements IVaultV2MorphoMarketV1Adapter
{
public declare readonly type: "VaultV2MorphoMarketV1Adapter";

static adapterId(address: Address) {
return keccak256(
encodeAbiParameters(
Expand Down Expand Up @@ -54,6 +57,7 @@ export class VaultV2MorphoMarketV1Adapter
}: IVaultV2MorphoMarketV1Adapter) {
super({
...vaultV2Adapter,
type: "VaultV2MorphoMarketV1Adapter",
adapterId: VaultV2MorphoMarketV1Adapter.adapterId(vaultV2Adapter.address),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { VaultV2Adapter } from "./VaultV2Adapter";
import type { IAccrualVaultV2Adapter, IVaultV2Adapter } from "./VaultV2Adapter";

export interface IVaultV2MorphoMarketV1AdapterV2
extends Omit<IVaultV2Adapter, "adapterId"> {
extends Omit<IVaultV2Adapter, "adapterId" | "type"> {
type?: "VaultV2MorphoMarketV1AdapterV2";
marketIds: MarketId[];
adaptiveCurveIrm: Address;
supplyShares: Record<MarketId, bigint>;
Expand All @@ -16,6 +17,8 @@ export class VaultV2MorphoMarketV1AdapterV2
extends VaultV2Adapter
implements IVaultV2MorphoMarketV1AdapterV2
{
public declare readonly type: "VaultV2MorphoMarketV1AdapterV2";

static adapterId(address: Address) {
return keccak256(
encodeAbiParameters(
Expand Down Expand Up @@ -55,6 +58,7 @@ export class VaultV2MorphoMarketV1AdapterV2
}: IVaultV2MorphoMarketV1AdapterV2) {
super({
...vaultV2Adapter,
type: "VaultV2MorphoMarketV1AdapterV2",
adapterId: VaultV2MorphoMarketV1AdapterV2.adapterId(
vaultV2Adapter.address,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { type Address, type Hex, encodeAbiParameters, keccak256 } from "viem";
import { VaultV2Adapter } from "./VaultV2Adapter";

export interface IVaultV2MorphoVaultV1Adapter
extends Omit<IVaultV2Adapter, "adapterId"> {
extends Omit<IVaultV2Adapter, "adapterId" | "type"> {
type?: "VaultV2MorphoVaultV1Adapter";
morphoVaultV1: Address;
}

Expand All @@ -15,6 +16,8 @@ export class VaultV2MorphoVaultV1Adapter
extends VaultV2Adapter
implements IVaultV2MorphoVaultV1Adapter
{
public declare readonly type: "VaultV2MorphoVaultV1Adapter";

static adapterId(address: Address) {
return keccak256(
encodeAbiParameters(
Expand All @@ -32,6 +35,7 @@ export class VaultV2MorphoVaultV1Adapter
}: IVaultV2MorphoVaultV1Adapter) {
super({
...vaultV2Adapter,
type: "VaultV2MorphoVaultV1Adapter",
adapterId: VaultV2MorphoVaultV1Adapter.adapterId(vaultV2Adapter.address),
});

Expand Down
43 changes: 42 additions & 1 deletion packages/bundler-sdk-viem/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from "@morpho-org/blue-sdk";
import { Time, getValue } from "@morpho-org/morpho-ts";
import {
APPROVE_ONLY_ONCE_TOKENS,
MAX_TOKEN_APPROVALS,
type MaybeDraft,
type Operation,
Expand Down Expand Up @@ -52,6 +53,7 @@ const encodeErc20Approval = (
token: Address,
spender: Address,
amount: bigint,
sender: Address,
data: MaybeDraft<SimulationState>,
) => {
const { chainId } = data;
Expand All @@ -63,6 +65,37 @@ const encodeErc20Approval = (

const txRequirements: TransactionRequirement[] = [];

// Handle USDT-like tokens that require reset to 0 before changing allowance.
if (APPROVE_ONLY_ONCE_TOKENS[chainId]?.includes(token) && amount > 0n) {
const holding = data.tryGetHolding(sender, token);
const addresses = getChainAddresses(chainId);
// Check if there's an existing allowance to the spender.
// We need to check the raw allowance since this is a direct ERC20 approval.
const existingAllowance =
spender === addresses.bundler3.generalAdapter1
? holding?.erc20Allowances["bundler3.generalAdapter1"]
: spender === addresses.permit2
? holding?.erc20Allowances.permit2
: spender === addresses.morpho
? holding?.erc20Allowances.morpho
: undefined;

if (existingAllowance != null && existingAllowance > 0n) {
txRequirements.push({
type: "erc20Approve",
args: [token, spender, 0n],
tx: {
to: token,
data: encodeFunctionData({
abi: erc20Abi,
functionName: "approve",
args: [spender, 0n],
}),
},
});
}
}

txRequirements.push({
type: "erc20Approve",
args: [token, spender, amount],
Expand Down Expand Up @@ -218,7 +251,13 @@ export const encodeOperation = (
if (!supportsSignature && spender === permit2) break;

requirements.txs.push(
...encodeErc20Approval(operation.address, spender, amount, dataBefore),
...encodeErc20Approval(
operation.address,
spender,
amount,
sender,
dataBefore,
),
);

break;
Expand Down Expand Up @@ -337,6 +376,7 @@ export const encodeOperation = (
operation.address,
spender,
amount,
sender,
dataBefore,
),
);
Expand Down Expand Up @@ -421,6 +461,7 @@ export const encodeOperation = (
operation.address,
generalAdapter1,
amount,
sender,
dataBefore,
),
);
Expand Down
26 changes: 0 additions & 26 deletions packages/bundler-sdk-viem/src/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,6 @@ export const populateInputTransfer = (
},
});
else if (useSimpleTransfer) {
if (
APPROVE_ONLY_ONCE_TOKENS[data.chainId]?.includes(address) &&
erc20Allowances["bundler3.generalAdapter1"] > 0n
)
operations.push({
type: "Erc20_Approve",
sender: from,
address,
args: {
amount: 0n,
spender: generalAdapter1,
},
});
operations.push({
type: "Erc20_Approve",
sender: from,
Expand All @@ -172,19 +159,6 @@ export const populateInputTransfer = (
// Simple permit is not supported: fallback to Permit2.
else {
if (erc20Allowances.permit2 < amount) {
if (
APPROVE_ONLY_ONCE_TOKENS[data.chainId]?.includes(address) &&
erc20Allowances.permit2 > 0n
)
operations.push({
type: "Erc20_Approve",
sender: from,
address,
args: {
amount: 0n,
spender: permit2,
},
});
operations.push({
type: "Erc20_Approve",
sender: from,
Comment on lines 161 to 164
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore zero-reset before approve-only token Permit2 approvals

populateInputTransfer now emits a single positive Erc20_Approve whenever erc20Allowances.permit2 < amount, but for APPROVE_ONLY_ONCE_TOKENS (USDT/CRV) any non-zero existing allowance must be reset to zero first. finalizeBundle/encodeOperation simulate this operation via handleErc20ApproveOperation, which throws NonZeroAllowanceError when allowance is already > 0, so bundle construction fails before encodeErc20Approval can inject the extra zero-approval transaction requirement. This breaks users with partial existing Permit2 allowances (e.g., approved 60, now need 80), and the same pattern also affects the simple-transfer approval branch.

Useful? React with 👍 / 👎.

Expand Down
Loading