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

Bean UI - Form and FarmStep fixes #714

Merged
merged 8 commits into from
Dec 20, 2023
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
14 changes: 11 additions & 3 deletions projects/ui/src/components/Common/Form/FormTxnProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { FarmToMode, TokenValue } from '@beanstalk/sdk';
import { FarmToMode, Token, TokenValue } from '@beanstalk/sdk';
import { FC, MayPromise } from '~/types';
import useSdk from '~/hooks/sdk';
import useAccount from '~/hooks/ledger/useAccount';
Expand Down Expand Up @@ -104,10 +104,17 @@ const useInitFormTxnContext = () => {
);
const claimable = farmerSilo.balances[sdk.tokens.BEAN.address]?.claimable;
const seasons = claimable?.crates.map((c) => c.season.toString());

const tokensWithStalk: Map<Token, TokenValue> = new Map();
farmerSilo.stalk.grownByToken.forEach((value, token) => {
if (value.gt(0)) {
tokensWithStalk.set(token, value);
};
});

const farmSteps = {
[FormTxn.MOW]: account
? new MowFarmStep(sdk, account).build()
[FormTxn.MOW]: account && tokensWithStalk.size > 0
? new MowFarmStep(sdk, account, tokensWithStalk).build()
: undefined,
[FormTxn.PLANT]: earnedBeans.gt(0)
? new PlantFarmStep(sdk).build()
Expand All @@ -134,6 +141,7 @@ const useInitFormTxnContext = () => {
farmerField.harvestablePlots,
farmerSilo.balances,
farmerSilo.beans.earned,
farmerSilo.stalk.grownByToken,
getBDV,
sdk,
destination
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export default function useFarmerFormTxnsSummary(mode?: 'plantToggle') {
[FormTxn.ENROOT]: {
title: 'Enroot',
tooltip: tooltips.enroot,
enabled: revitalizedSeeds.gt(0) && revitalizedStalk.gt(0),
enabled: revitalizedSeeds.gt(0) || revitalizedStalk.gt(0),
summary: [
{
description: 'Revitalized Seeds',
Expand Down
5 changes: 4 additions & 1 deletion projects/ui/src/lib/Txn/FarmSteps/silo/EnrootFarmStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ enum EnrootType {
}

export class EnrootFarmStep extends FarmStep implements EstimatesGas {

_crates: Record<string, LegacyDepositCrate[]>

constructor(
_sdk: BeanstalkSDK,
private _crates: Record<string, LegacyDepositCrate[]>
_crates: Record<string, LegacyDepositCrate[]>
) {
super(_sdk);
this._crates = _crates;
Expand Down
98 changes: 69 additions & 29 deletions projects/ui/src/lib/Txn/FarmSteps/silo/MowFarmStep.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
import { BeanstalkSDK } from '@beanstalk/sdk';
import { Token } from '@beanstalk/sdk-core';
import { Token, TokenValue } from '@beanstalk/sdk-core';
import { ethers } from 'ethers';
import { FarmStep, EstimatesGas } from '~/lib/Txn/Interface';

export class MowFarmStep extends FarmStep implements EstimatesGas {
private _token: Token;
_account: string;

private _account: string;
_tokensToMow: Map<Token, TokenValue>;

constructor(
_sdk: BeanstalkSDK,
_account: string,
// TODO(silo-v3): .update doesn't exist anymore.
// Rewrite this mow step to use `mow()` or `mowMultiple()` depending on
// the tokens requested to be mown. this will require ui changes or defaults
_token?: Token
_tokensToMow: Map<Token, TokenValue>
// This step now calls mow or mowMultiple depending on how many tokens
// are in _tokensToMow
) {
super(_sdk);
this._account = _account;
this._token = this._sdk.tokens.BEAN;
this._tokensToMow = _tokensToMow;
}

async estimateGas(): Promise<ethers.BigNumber> {
const { beanstalk } = this._sdk.contracts;

const gasAmount = await beanstalk.estimateGas.mow(
this._account,
this._token.address
);
const tokensToMow: string[] = [];
this._tokensToMow.forEach((grown, token) => {
if (grown.gt(0)) {
tokensToMow.push(token.address);
}
});
console.debug(`[MowFarmStep][estimateGas]: tokensToMow = `, tokensToMow);
let gasAmount;
if (tokensToMow.length === 1) {
gasAmount = await beanstalk.estimateGas.mow(
this._account,
tokensToMow[0]
);
} else {
gasAmount = await beanstalk.estimateGas.mowMultiple(
this._account,
tokensToMow
);
};
console.debug(`[MowFarmStep][estimateGas]: `, gasAmount.toString());

return gasAmount;
Expand All @@ -37,24 +50,51 @@ export class MowFarmStep extends FarmStep implements EstimatesGas {
this.clear();

const { beanstalk } = this._sdk.contracts;
const tokensToMow: string[] = [];
this._tokensToMow.forEach((grown, token) => {
if (grown.gt(0)) {
tokensToMow.push(token.address);
}
});
console.debug(`[MowFarmStep][build]: tokensToMow = `, tokensToMow);

this.pushInput({
input: async (_amountInStep) => ({
name: 'mow',
amountOut: _amountInStep,
prepare: () => ({
target: beanstalk.address,
callData: beanstalk.interface.encodeFunctionData('mow', [
this._account,
this._token.address,
]),
if (tokensToMow.length === 1) {
this.pushInput({
input: async (_amountInStep) => ({
name: 'mow',
amountOut: _amountInStep,
prepare: () => ({
target: beanstalk.address,
callData: beanstalk.interface.encodeFunctionData('mow', [
this._account,
tokensToMow[0],
]),
}),
decode: (data: string) =>
beanstalk.interface.decodeFunctionData('mow', data),
decodeResult: (result: string) =>
beanstalk.interface.decodeFunctionResult('mow', result),
}),
decode: (data: string) =>
beanstalk.interface.decodeFunctionData('mow', data),
decodeResult: (result: string) =>
beanstalk.interface.decodeFunctionResult('mow', result),
}),
});
});
} else {
this.pushInput({
input: async (_amountInStep) => ({
name: 'mowMultiple',
amountOut: _amountInStep,
prepare: () => ({
target: beanstalk.address,
callData: beanstalk.interface.encodeFunctionData('mowMultiple', [
this._account,
tokensToMow,
]),
}),
decode: (data: string) =>
beanstalk.interface.decodeFunctionData('mowMultiple', data),
decodeResult: (result: string) =>
beanstalk.interface.decodeFunctionResult('mowMultiple', result),
}),
});
};

console.debug('[MowFarmStep][build]: ', this.getFarmInput());

Expand Down
70 changes: 47 additions & 23 deletions projects/ui/src/lib/Txn/FormTxn/FormTxnBundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '~/lib/Txn/FormTxn/types';
import { FormTxnBundlerPresets as presets } from '~/lib/Txn/FormTxn/presets';
import { BigNumber } from 'ethers';
import { Token } from '@beanstalk/sdk-core';

type FormTxnFarmStep =
| MowFarmStep
Expand Down Expand Up @@ -44,7 +45,7 @@ type FormTxnFarmStep =
* - it is intended for this to be called in an 'onSubmit' function where the user
* has confirmed which FarmSteps they want to perform.
* - setFarmSteps() deduplicates any FarmSteps that are implied by other FarmSteps
* as welll as any FarmSteps that are excluded by the form.
* as well as any FarmSteps that are excluded by the form.
*
* 3. Call build() to perform the bundling of FarmSteps into a single, executable Workflow.
* - It is assumed every FarmStep added to have previous been 'built'.
Expand Down Expand Up @@ -152,13 +153,55 @@ export class FormTxnBundler {
farm.add(_input.input, _input.options);
});

Object.entries(this.after).forEach(([step, farmStep]) => {
// Here we build a map of tokens that will be mown taking into account
// internal mow calls inside Plant and Enroot
const allSteps = Object.entries(this.after);
let tokensToMow: Map<Token, TokenValue> = new Map();
let tokensToRemove: string[] = [];
allSteps.forEach((farmOp) => {
if (farmOp[1] instanceof MowFarmStep) {
// Create map of tokens to Mow
tokensToMow = farmOp[1]._tokensToMow;
} else if (farmOp[1] instanceof PlantFarmStep) {
// If there's a Plant step, add Bean to the list of tokens to remove
tokensToRemove.push(this._sdk.tokens.BEAN.address);
} else if (farmOp[1] instanceof EnrootFarmStep) {
// If there's an Enroot step, add token(s) to the list of tokens to remove
const tokensEnrooted = Object.keys(farmOp[1]._crates);
tokensToRemove = tokensToRemove.concat(tokensEnrooted);
};
});

// Remove tokens from map of tokens to Mow.
// We separate this logic from the forEach loop so
// we aren't forced into a strict input order
const tokenMap = this._sdk.tokens.getMap();
tokensToRemove.forEach((token) => {
const tokenToDelete = tokenMap.get(token);
if (tokenToDelete) tokensToMow.delete(tokenToDelete);
});

allSteps.forEach(([step, farmStep]) => {
const farmInput = farmStep.getFarmInput();
if (!farmInput.length) {
throw new Error(`Expected FarmStep ${step.toLowerCase()} to be built`);
}

farmInput.forEach(({ input, options }) => {
farm.add(input, options);
// If the current step is a Mow operation
if (farmStep instanceof MowFarmStep) {
// We check if there's any tokens in the map of tokens to Mow
// and build the Mow step if necessary
if (tokensToMow.size > 0) {
const newFarmStep = new MowFarmStep(this._sdk, farmStep._account, tokensToMow).build();
const newFarmInput = newFarmStep.getFarmInput();
const newInput = newFarmInput[0].input;
const newOptions = newFarmInput[0].options;
farm.add(newInput, newOptions);
};
} else {
farm.add(input, options);
};
});
});

Expand Down Expand Up @@ -189,14 +232,13 @@ export class FormTxnBundler {

/**
* @param data
* deduplicate farm steps & remove implicit actions.
* deduplicate farm steps.
*/
private static deduplicateFarmSteps(data: FormTxnBundlerInterface) {
const before = new Set(data.primary || []);
const after = new Set(data.secondary || []);

const allActions = new Set([...before, ...after]);

/// deduplicate
// if an action is in both primary and secondary, remove it from secondary
[...before].forEach((action) => {
Expand All @@ -205,24 +247,6 @@ export class FormTxnBundler {
}
});

/// deduplicate implied actions
[...allActions].forEach((action) => {
const implied = FormTxnBundler.implied[action];
if (implied) {
allActions.has(implied) && allActions.delete(implied);
before.has(implied) && before.delete(implied);
after.has(implied) && after.delete(implied);
}
});

const removeItems = [...(data.exclude || []), ...(data.implied || [])];

removeItems.forEach((toRemove) => {
allActions.has(toRemove) && allActions.delete(toRemove);
before.has(toRemove) && before.delete(toRemove);
after.has(toRemove) && after.delete(toRemove);
});

return {
before: [...before],
after: [...after],
Expand Down
4 changes: 2 additions & 2 deletions projects/ui/src/util/Actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,10 @@ export const parseActionMessage = (a: Action) => {
a.bean.gt(1) ? 's' : ''
}, ${displayFullBN(a.stalk, 2)} Stalk, and ${displayFullBN(a.seeds, 2)} Seeds.`;
case ActionType.ENROOT:
return `Enroot revitalized ${displayFullBN(
return `Enroot ${displayFullBN(
a.stalk,
2
)} Stalk and ${displayFullBN(a.seeds, 2)} Seeds.`;
)} Revitalized Stalk and ${displayFullBN(a.seeds, 2)} Revitalized Seeds.`;

/// FIELD
case ActionType.BUY_BEANS:
Expand Down