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

Spacebean/UI/bs 3 UI fix v2 #1153

Merged
merged 9 commits into from
Oct 17, 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
8 changes: 4 additions & 4 deletions projects/sdk/src/lib/swapV2/BeanSwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface BeanSwapNodeQuote {
export class BeanSwapOperation {
static sdk: BeanstalkSDK;

readonly quoter: BeanSwapQuoter;
#quoter: BeanSwapQuoter;

#builder: BeanSwapBuilder;

Expand Down Expand Up @@ -81,7 +81,7 @@ export class BeanSwapOperation {
toMode?: FarmToMode
) {
BeanSwapOperation.sdk = sdk;
this.quoter = quoter;
this.#quoter = quoter;
this.#builder = new BeanSwapBuilder(BeanSwapOperation.sdk);

this.inputToken = inputToken;
Expand Down Expand Up @@ -117,7 +117,7 @@ export class BeanSwapOperation {
* @param force - If true, the reserves and prices will be refreshed regardless of the time since the last refresh.
*/
async refresh(force?: boolean) {
await this.quoter.refresh(force);
await this.#quoter.refresh(force);
}

/**
Expand All @@ -131,7 +131,7 @@ export class BeanSwapOperation {
if (amount.lte(0)) return;

if (this.#shouldFetchQuote(amount, slippage) || force === true) {
this.#quoteData = await this.quoter.route(this.inputToken, this.targetToken, amount, slippage);
this.#quoteData = await this.#quoter.route(this.inputToken, this.targetToken, amount, slippage);
this.#buildQuoteData();
await this.estimate();
}
Expand Down
65 changes: 48 additions & 17 deletions projects/sdk/src/lib/swapV2/BeanSwapBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,23 @@ class Builder {
if (i === maxIndex) {
fromMode = FarmFromMode.EXTERNAL;
toMode = finalToMode;

this.#offloadPipeline(node, recipient, fromMode, finalToMode, i);
/**
* If WETH -> ETH is the last action,
* - do everything first & send the WETH to the recipient's internal balance.
* - then unwrap the WETH to ETH.
*
* Otherwise, send the output token to the recipient.
*/
if (isUnwrapEthNode(node)) {
toMode = FarmToMode.INTERNAL;
}

this.#offloadPipeline(node, recipient, fromMode, toMode, i);
this.#advFarm.add(this.#advPipe);

// // Add UnwrapETH if last action

if (isUnwrapEthNode(node)) {
fromMode = FarmFromMode.INTERNAL;
this.#advFarm.add(this.#getUnwrapETH(node, fromMode, i), { tag: node.tag });
}
}
Expand Down Expand Up @@ -184,38 +195,48 @@ class Builder {
recipient.toLowerCase() === Builder.sdk.contracts.pipeline.address.toLowerCase();
if (recipientIsPipeline) return;

const outputToken = node.buyToken;
let copySlot: number | undefined;

let approveToken: ERC20Token;

let tag: string;
let outToken: ERC20Token;

/**
* If going from WETH -> ETH
* - Send to internal
* - use previous node info.
*/
if (isUnwrapEthNode(node)) {
approveToken = node.sellToken;
copySlot = this.#getPrevNodeCopySlot(i);
tag = node.returnIndexTag; // get-WETH
outToken = node.sellToken; // WETH
copySlot = this.#getPrevNodeCopySlot(i); // WETH amountOutCopySlot
} else if (isERC20Node(node)) {
approveToken = node.buyToken;
tag = node.tag;
outToken = node.buyToken;
copySlot = node.amountOutCopySlot;
} else {
throw new Error("Error building swap: Cannot determine approval token for transfer.");
throw new Error(
"Error building swap offloading pipeline: Cannot determine approval token for transfer."
);
}

if (copySlot === undefined) {
throw new Error("Error building swap: Cannot determine copySlot from previous node.");
throw new Error(
"Error building swap offloading pipeline: Cannot determine copySlot from previous node."
);
}

const prevActionClipboard = {
tag: node.tag,
tag: tag,
copySlot: copySlot
};

const approve = new Builder.sdk.farm.actions.ApproveERC20(
approveToken,
outToken,
Builder.sdk.contracts.beanstalk.address,
{ ...prevActionClipboard, pasteSlot: 1 }
);

const transfer = new Builder.sdk.farm.actions.TransferToken(
outputToken.address,
outToken.address,
recipient,
fromMode,
toMode,
Expand All @@ -230,6 +251,12 @@ class Builder {
*
*/
#getApproveERC20MaxAllowance(node: SwapNode) {
if (isUnwrapEthNode(node)) {
const approve = new Builder.sdk.farm.actions.ApproveERC20(node.sellToken, node.allowanceTarget);
approve.setAmount(TokenValue.MAX_UINT256);
return approve;
}

if (!isERC20Node(node)) {
throw new Error("Misconfigured Swap Route. Cannot approve non-ERC20 token.");
}
Expand Down Expand Up @@ -260,7 +287,11 @@ class Builder {
);
}

const copySlot = this.#getPrevNodeCopySlot(i);
let copySlot;
if (!isLast) {
copySlot = this.#getPrevNodeCopySlot(i);
}


return node.buildStep({ fromMode, copySlot });
}
Expand Down
55 changes: 34 additions & 21 deletions projects/sdk/src/lib/swapV2/BeanSwapQuoter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
ERC20SwapNode,
WellSyncSwapNode
} from "./nodes";
import { isERC20Token } from "src/utils/token";
import { isERC20Token, isNativeToken } from "src/utils/token";
import { BeanSwapNodeQuote } from "./BeanSwap";
import { TransferTokenNode } from "./nodes/TransferTokenNode";

Expand Down Expand Up @@ -65,10 +65,10 @@ class WellsRouter {
throw new Error("Invalid token types. Cannot quote well routes for non-erc20 tokens");
}

const routes: {
swap: QuoteSourceFragment | undefined;
sync: SyncQuoteSourceFragment | undefined;
} = { swap: undefined, sync: undefined };
const routes: { swap: QuoteSourceFragment | undefined; sync: SyncQuoteSourceFragment | undefined; } = {
swap: undefined,
sync: undefined
};

const inputSwap: QuoteSourceFragment = {
sellToken,
Expand Down Expand Up @@ -234,9 +234,9 @@ class WellsRouter {
routes: routes
};

WellsRouter.sdk.debug("[WellsRouter/makeSummary] bestRoute: ", bestRoute);
WellsRouter.sdk.debug("[WellsRouter/makeSummary] directRoute: ", directRoute);
WellsRouter.sdk.debug("[WellsRouter/makeSummary] routes: ", routes);
WellsRouter.sdk.debug("[BeanSwap/WellsRouter/makeSummary] bestRoute: ", bestRoute);
WellsRouter.sdk.debug("[BeanSwap/WellsRouter/makeSummary] directRoute: ", directRoute);
WellsRouter.sdk.debug("[BeanSwap/WellsRouter/makeSummary] routes: ", routes);

return summary;
}
Expand Down Expand Up @@ -277,7 +277,7 @@ class WellsRouter {
}

WellsRouter.sdk.debug(
"[WellsRouter/constructWellGetSwapOutPipeCalls] Swap Approximations: ",
"[BeanSwap/WellsRouter/constructWellGetSwapOutPipeCalls] Swap Approximations: ",
swapApproximations
);

Expand Down Expand Up @@ -308,7 +308,7 @@ class WellsRouter {
}

/**
*
* Fetches the result of well.getSwapOut for all wells in one advancedPipe call
*/
private async fetchWellGetSwapOutPipeCalls(calls: AdvancedPipeCallStruct[]) {
return await WellsRouter.sdk.contracts.beanstalk.callStatic.advancedPipe(calls, "0");
Expand Down Expand Up @@ -441,9 +441,6 @@ class Quoter {
const ETH = Quoter.sdk.tokens.ETH;
const nodes: SwapNode[] = [];

const unwrapEthNode = new UnwrapEthSwapNode(Quoter.sdk);
const wrapEthNode = new WrapEthSwapNode(Quoter.sdk);

const sellingWETH = WETH.equals(sellToken);
const buyingWETH = WETH.equals(buyToken);

Expand All @@ -458,24 +455,34 @@ class Quoter {
transferNode.setFields({ sellAmount: amount });
return this.makeQuote([transferNode], sellToken, buyToken, amount, slippage);
}
} else if (
isNativeToken(sellToken) &&
isNativeToken(buyToken) &&
sellToken.equals(buyToken)
) {
throw new Error("Error building swap route. Cannot determine path two native tokens");
}

// ONLY WETH -> ETH
if (sellingWETH && unwrappingETH) {
const unwrapEthNode = new UnwrapEthSwapNode(Quoter.sdk);
unwrapEthNode.setFields({ sellAmount: amount });
return this.makeQuote([unwrapEthNode], sellToken, buyToken, amount, slippage);
}

// ETH -> X
if (wrappingETH) {
const wrapEthNode = new WrapEthSwapNode(Quoter.sdk);
wrapEthNode.setFields({ sellAmount: amount });

// ONLY ETH -> WETH
if (buyingWETH) {
return this.makeQuote([wrapEthNode], sellToken, buyToken, amount, slippage);
}
nodes.push(wrapEthNode);
}

// X -> X (ERC20)
const swapPath = await this.handleERC20OnlyQuote(
sellToken,
buyToken as ERC20Token,
Expand All @@ -486,7 +493,13 @@ class Quoter {

if (unwrappingETH) {
const lastSwapNode = nodes[nodes.length - 1];
unwrapEthNode.setFields({ sellAmount: lastSwapNode.buyAmount });
const unwrapEthNode = new UnwrapEthSwapNode(Quoter.sdk);

if (lastSwapNode instanceof ERC20SwapNode) {
unwrapEthNode.setFields({ sellAmount: lastSwapNode.minBuyAmount });
} else {
unwrapEthNode.setFields({ sellAmount: lastSwapNode.buyAmount });
}
nodes.push(unwrapEthNode);
}

Expand Down Expand Up @@ -558,7 +571,7 @@ class Quoter {
slippage: number
): Promise<SwapNode[]> {
if (sellToken.equals(buyToken)) {
throw new Error("Invalid swap path. SellToken and BuyToken cannot be the same");
throw new Error("Invalid NonBean swap path. SellToken and BuyToken cannot be the same");
}
const zeroX = new ZeroXSwapNode(Quoter.sdk, sellToken, buyToken);
await zeroX.quoteForward(amount, slippage);
Expand All @@ -575,7 +588,7 @@ class Quoter {
const buyingBEAN = this.isTokenBEAN(buyToken);

if (sellToken.equals(buyToken)) {
throw new Error("Invalid swap path. Expected SellToken and BuyToken to be different.");
throw new Error("Invalid Bean swap path. Expected SellToken and BuyToken to be different.");
}
if (!sellingBEAN && !buyingBEAN) {
throw new Error(
Expand All @@ -588,15 +601,15 @@ class Quoter {
return [] as SwapNode[];
}
if (routesSummary.routes.length === 1) {
Quoter.sdk.debug("[BeanSwapQuoter/finalizeSellBeansRoute] Only 1 route found. Using route: ", routesSummary.bestRoute);
Quoter.sdk.debug("[BeanSwap/Quoter/finalizeSellBeansRoute] Only 1 route found. Using route: ", routesSummary.bestRoute);
return routesSummary.routes;
}

const nodes = sellingBEAN
? await this.finalizeSellBeansRoute(routesSummary, buyToken, slippage)
: await this.finalizeBuyBeansRoute(routesSummary, sellToken, amount, slippage);

Quoter.sdk.debug("[BeanSwapQuoter/processSellBeansSwap] Selected route: ", nodes);
Quoter.sdk.debug("[BeanSwap/Quoter/processSellBeansSwap] Selected route: ", nodes);
return nodes;
}

Expand All @@ -614,7 +627,7 @@ class Quoter {
const { directRoute, bestRoute } = summary;

if (directRoute && directRoute === bestRoute) {
Quoter.sdk.debug("[BeanSwapQuoter/finalizeSellBeansRoute] Using direct route: ", directRoute);
Quoter.sdk.debug("[BeanSwap/Quoter/finalizeSellBeansRoute] Using direct route: ", directRoute);
return [directRoute];
}

Expand Down Expand Up @@ -642,7 +655,7 @@ class Quoter {
const { directRoute, bestRoute } = summary;

if (directRoute && directRoute === bestRoute) {
Quoter.sdk.debug("[BeanSwapQuoter/finalizeBuyBeansRoute] Using direct route: ", directRoute);
Quoter.sdk.debug("[BeanSwap/Quoter/finalizeBuyBeansRoute] Using direct route: ", directRoute);
return [directRoute];
}

Expand Down Expand Up @@ -740,7 +753,7 @@ class Quoter {
sellToken,
buyToken,
sellAmount,
buyAmount: last?.buyAmount ?? sellToken.fromHuman(0),
buyAmount: last?.buyAmount ?? buyToken.fromHuman(0),
minBuyAmount: last?.buyAmount ?? buyToken.fromHuman(0),
slippage,
nodes: nodes as ReadonlyArray<SwapNode>
Expand Down
4 changes: 4 additions & 0 deletions projects/sdk/src/lib/swapV2/nodes/NativeSwapNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export abstract class NativeSwapNode extends SwapNode {

abstract readonly buyToken: NativeToken | ERC20Token;

/**
* @param args sell amount or buy amount
* setting sellAmount sets buyAmount and vice versa
*/
override setFields<T extends ISwapNodeSettable>(args: Partial<T>) {
const amount = args.sellAmount ?? args.buyAmount;
if (amount) {
Expand Down
20 changes: 15 additions & 5 deletions projects/sdk/src/lib/swapV2/nodes/TransferTokenNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { StepClass } from "src/classes/Workflow";
import { AdvancedPipePreparedResult } from "src/lib/depot/pipe";
import { FarmFromMode, FarmToMode } from "src/lib/farm";
import { ClipboardSettings } from "src/types";
import { isNativeToken } from "src/utils/token";

interface TransferTokenBuildParams {
fromMode: FarmFromMode;
Expand Down Expand Up @@ -40,8 +41,8 @@ export class TransferTokenNode extends SwapNode {
override buildStep({
fromMode,
toMode,
recipient,
copySlot,
recipient,
copySlot
}: TransferTokenBuildParams): StepClass<AdvancedPipePreparedResult> {
let clipboard: ClipboardSettings | undefined;

Expand All @@ -55,12 +56,21 @@ export class TransferTokenNode extends SwapNode {

const transfer = new TransferTokenNode.sdk.farm.actions.TransferToken(
this.sellToken.address,
recipient,
fromMode,
recipient,
fromMode,
toMode,
clipboard
)
);

return transfer;
}

override validateTokens() {
super.validateTokens();
if (isNativeToken(this.sellToken)) {
throw new Error("Invalid token transfer configuration. Sell Token cannot be a native token");
} else if (isNativeToken(this.buyToken)) {
throw new Error("Invalid token transfer configuration. Buy Token cannot be a native token");
}
}
}
2 changes: 1 addition & 1 deletion projects/sdk/src/utils/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export function isERC20Token(token: Token): token is ERC20Token {
}

export function isNativeToken(token: Token): token is NativeToken {
return isNativeToken(token);
return token instanceof NativeToken;
}
Loading
Loading