Skip to content

Commit

Permalink
Spacebean/UI/bs 3 UI fix v2 (#1153)
Browse files Browse the repository at this point in the history
  • Loading branch information
Space-Bean authored Oct 17, 2024
2 parents d131265 + 71a57ae commit 0ed2c24
Show file tree
Hide file tree
Showing 22 changed files with 505 additions and 296 deletions.
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

0 comments on commit 0ed2c24

Please sign in to comment.