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

refactor: useSolver #163

Merged
merged 10 commits into from
May 17, 2023
217 changes: 76 additions & 141 deletions apps/vaults/contexts/useSolver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import type {MaybeString} from '@yearn-finance/web-lib/types';
import type {TNormalizedBN} from '@common/types/types';
import type {TInitSolverArgs, TSolverContext, TWithSolver} from '@vaults/types/solvers';

export enum Solver {
export enum Solver {
VANILLA = 'Vanilla',
PARTNER_CONTRACT = 'PartnerContract',
CHAIN_COIN = 'ChainCoin',
INTERNAL_MIGRATION = 'InternalMigration',
COWSWAP = 'Cowswap',
WIDO = 'Wido',
PORTALS = 'Portals'
PORTALS = 'Portals',
NONE = 'None'
}

export const isSolverDisabled = {
Expand All @@ -35,10 +36,18 @@ export const isSolverDisabled = {
[Solver.INTERNAL_MIGRATION]: false,
[Solver.COWSWAP]: false,
[Solver.WIDO]: false,
[Solver.PORTALS]: false
[Solver.PORTALS]: false,
[Solver.NONE]: false
};

const DefaultWithSolverContext: TWithSolver = {
type TUpdateSolverHandler = {
request: TInitSolverArgs;
quote: PromiseSettledResult<TNormalizedBN>;
solver: Solver;
ctx: TSolverContext;
}

const DefaultWithSolverContext: TWithSolver = {
currentSolver: Solver.VANILLA,
effectiveSolver: Solver.VANILLA,
expectedOut: toNormalizedBN(0),
Expand All @@ -51,8 +60,8 @@ const DefaultWithSolverContext: TWithSolver = {
onExecuteWithdraw: async (): Promise<void> => Promise.resolve()
};

const WithSolverContext = createContext<TWithSolver>(DefaultWithSolverContext);
function WithSolverContextApp({children}: {children: React.ReactElement}): React.ReactElement {
const WithSolverContext = createContext<TWithSolver>(DefaultWithSolverContext);
function WithSolverContextApp({children}: { children: React.ReactElement }): React.ReactElement {
const {address} = useWeb3();
const {currentVault, actionParams, currentSolver, isDepositing} = useActionFlow();
const cowswap = useSolverCowswap();
Expand All @@ -62,189 +71,114 @@ function WithSolverContextApp({children}: {children: React.ReactElement}): React
const chainCoin = useSolverChainCoin();
const partnerContract = useSolverPartnerContract();
const internalMigration = useSolverInternalMigration();
const [currentSolverState, set_currentSolverState] = useState<TSolverContext & {hash: MaybeString}>({...vanilla, hash: undefined});
const [currentSolverState, set_currentSolverState] = useState<TSolverContext & { hash: MaybeString }>({...vanilla, hash: undefined});
const [isLoading, set_isLoading] = useState(false);

async function handleUpdateSolver({request, quote, solver, ctx}: TUpdateSolverHandler): Promise<void> {
if (quote.status !== 'fulfilled') {
return;
}

const requestHash = await hash(JSON.stringify({...request, solver, expectedOut: quote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...ctx, quote: quote.value, hash: requestHash});
set_isLoading(false);
});
}

/* 🔵 - Yearn Finance **************************************************************************
** Based on the currentSolver, we initialize the solver with the required parameters.
**********************************************************************************************/
const onUpdateSolver = useCallback(async (): Promise<void> => {
if (!actionParams?.selectedOptionFrom || !actionParams?.selectedOptionTo || !actionParams?.amount) {
const onUpdateSolver = useCallback(async (): Promise<void> => {
if (!actionParams?.selectedOptionFrom || !actionParams?.selectedOptionTo || !actionParams?.amount.raw) {
return;
}
set_isLoading(true);

const request: TInitSolverArgs = {
from: toAddress(address || ''),
inputToken: actionParams?.selectedOptionFrom,
outputToken: actionParams?.selectedOptionTo,
inputAmount: actionParams?.amount.raw,
inputToken: actionParams.selectedOptionFrom,
outputToken: actionParams.selectedOptionTo,
inputAmount: actionParams.amount.raw,
isDepositing: isDepositing
};

const isValidSolver = ({quote, solver}: { quote: PromiseSettledResult<TNormalizedBN>; solver: Solver }): boolean => {
return quote.status === 'fulfilled' && quote?.value.raw?.gt(0) && !isSolverDisabled[solver];
};

switch (currentSolver) {
case Solver.WIDO:
case Solver.PORTALS:
case Solver.COWSWAP: {
const promises = [
const [widoQuote, cowswapQuote, portalsQuote] = await Promise.allSettled([
wido.init(request, currentSolver === Solver.WIDO),
cowswap.init(request, currentSolver === Solver.COWSWAP),
portals.init(request, currentSolver === Solver.PORTALS)
];
const [widoQuote, cowswapQuote, portalsQuote] = await Promise.allSettled(promises);

/**************************************************************
** Logic is to use the primary solver (Wido) and check if a
** quote is available. If not, we fallback to the secondary
** solver (Cowswap). If neither are available, we set the
** quote to 0.
**************************************************************/
if (currentSolver === Solver.WIDO && !isSolverDisabled[Solver.WIDO]) {
if (widoQuote.status === 'fulfilled' && widoQuote?.value.raw?.gt(0)) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.WIDO, expectedOut: widoQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...wido, quote: widoQuote.value, hash: requestHash});
set_isLoading(false);
});
} else if (cowswapQuote.status === 'fulfilled' && cowswapQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.COWSWAP]) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.COWSWAP, expectedOut: cowswapQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...cowswap, quote: cowswapQuote.value, hash: requestHash});
set_isLoading(false);
});
} else if (portalsQuote.status === 'fulfilled' && portalsQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.PORTALS]) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.PORTALS, expectedOut: portalsQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...portals, quote: portalsQuote.value, hash: requestHash});
set_isLoading(false);
});
} else {
const requestHash = await hash(JSON.stringify({...request, solver: 'NONE', expectedOut: '0'}));
performBatchedUpdates((): void => {
set_currentSolverState({...cowswap, quote: toNormalizedBN(0), hash: requestHash});
set_isLoading(false);
});
]);

const solvers: {
[key in Solver]?: { quote: PromiseSettledResult<TNormalizedBN>; ctx: TSolverContext };
} = {};

[
{solver: Solver.WIDO, quote: widoQuote, ctx: wido},
{solver: Solver.COWSWAP, quote: cowswapQuote, ctx: cowswap},
{solver: Solver.PORTALS, quote: portalsQuote, ctx: portals}
].forEach(({solver, quote, ctx}): void => {
if (isValidSolver({quote, solver})) {
solvers[solver] = {quote, ctx};
}
return;
}
});

/**************************************************************
** Logic is to use the primary solver (Cowswap) and check if a
** quote is available. If not, we fallback to the secondary
** solver (Wido). If neither are available, we set the
** quote to 0.
**************************************************************/
if (currentSolver === Solver.COWSWAP && !isSolverDisabled[Solver.COWSWAP]) {
if (cowswapQuote.status === 'fulfilled' && cowswapQuote.value.raw?.gt(0)) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.COWSWAP, expectedOut: cowswapQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...cowswap, quote: cowswapQuote.value, hash: requestHash});
set_isLoading(false);
});
} else if (widoQuote.status === 'fulfilled' && widoQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.WIDO]) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.WIDO, expectedOut: widoQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...wido, quote: widoQuote.value, hash: requestHash});
set_isLoading(false);
});
} else if (portalsQuote.status === 'fulfilled' && portalsQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.PORTALS]) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.PORTALS, expectedOut: portalsQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...portals, quote: portalsQuote.value, hash: requestHash});
set_isLoading(false);
});
} else {
const requestHash = await hash(JSON.stringify({...request, solver: 'NONE', expectedOut: '0'}));
performBatchedUpdates((): void => {
set_currentSolverState({...wido, quote: toNormalizedBN(0), hash: requestHash});
set_isLoading(false);
});
}
}
solvers[Solver.NONE] = {quote: {status: 'fulfilled', value: toNormalizedBN(0)}, ctx: vanilla};

const solverPriority = [Solver.WIDO, Solver.COWSWAP, Solver.PORTALS, Solver.NONE];
Majorfi marked this conversation as resolved.
Show resolved Hide resolved

const newSolverPriority = [currentSolver, ...solverPriority.filter((solver): boolean => solver !== currentSolver)];

/**************************************************************
** Logic is to use the primary solver (Portals) and check if a
** quote is available. If not, we fallback to the secondary
** solver (Wido). If neither are available, we set the
** quote to 0.
**************************************************************/
if (currentSolver === Solver.PORTALS && !isSolverDisabled[Solver.PORTALS]) {
if (portalsQuote.status === 'fulfilled' && portalsQuote.value.raw?.gt(0)) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.PORTALS, expectedOut: portalsQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...portals, quote: portalsQuote.value, hash: requestHash});
set_isLoading(false);
});
} else if (widoQuote.status === 'fulfilled' && widoQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.WIDO]) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.WIDO, expectedOut: widoQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...wido, quote: widoQuote.value, hash: requestHash});
set_isLoading(false);
});
} else if (cowswapQuote.status === 'fulfilled' && cowswapQuote.value.raw?.gt(0) && !isSolverDisabled[Solver.COWSWAP]) {
const requestHash = await hash(JSON.stringify({...request, solver: Solver.COWSWAP, expectedOut: cowswapQuote.value.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...cowswap, quote: cowswapQuote.value, hash: requestHash});
set_isLoading(false);
});
} else {
const requestHash = await hash(JSON.stringify({...request, solver: 'NONE', expectedOut: '0'}));
performBatchedUpdates((): void => {
set_currentSolverState({...wido, quote: toNormalizedBN(0), hash: requestHash});
set_isLoading(false);
});
for (const solver of newSolverPriority) {
if (!solvers[solver]) {
continue;
}
}

set_isLoading(false);
const {quote, ctx} = solvers[solver] ?? solvers[Solver.NONE];
await handleUpdateSolver({request, quote, solver, ctx});
return;
}

break;
}
case Solver.CHAIN_COIN: {
const quote = await chainCoin.init(request);
const requestHash = await hash(JSON.stringify({...request, solver: Solver.CHAIN_COIN, expectedOut: quote.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...chainCoin, quote, hash: requestHash});
set_isLoading(false);
});
const [quote] = await Promise.allSettled([chainCoin.init(request)]);
await handleUpdateSolver({request, quote, solver: Solver.CHAIN_COIN, ctx: chainCoin});
break;
}
case Solver.PARTNER_CONTRACT: {
const quote = await partnerContract.init(request);
const requestHash = await hash(JSON.stringify({...request, solver: Solver.PARTNER_CONTRACT, expectedOut: quote.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...partnerContract, quote, hash: requestHash});
set_isLoading(false);
});
const [quote] = await Promise.allSettled([partnerContract.init(request)]);
await handleUpdateSolver({request, quote, solver: Solver.PARTNER_CONTRACT, ctx: partnerContract});
break;
}
case Solver.INTERNAL_MIGRATION: {
request.migrator = currentVault.migration.contract;
const quote = await internalMigration.init(request);
const requestHash = await hash(JSON.stringify({...request, solver: Solver.INTERNAL_MIGRATION, expectedOut: quote.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...internalMigration, quote, hash: requestHash});
set_isLoading(false);
});
const [quote] = await Promise.allSettled([internalMigration.init(request)]);
await handleUpdateSolver({request, quote, solver: Solver.INTERNAL_MIGRATION, ctx: internalMigration});
break;
}
default: {
const quote = await vanilla.init(request);
const requestHash = await hash(JSON.stringify({...request, solver: Solver.VANILLA, expectedOut: quote.raw.toString()}));
performBatchedUpdates((): void => {
set_currentSolverState({...vanilla, quote, hash: requestHash});
set_isLoading(false);
});
const [quote] = await Promise.allSettled([vanilla.init(request)]);
await handleUpdateSolver({request, quote, solver: Solver.VANILLA, ctx: vanilla});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [address, actionParams, currentSolver, cowswap.init, vanilla.init, wido.init, internalMigration.init, isDepositing, currentVault.migration.contract]); //Ignore the warning, it's a false positive

useDebouncedEffect((): void => {
onUpdateSolver();
}, [onUpdateSolver], 0);

const contextValue = useDeepCompareMemo((): TWithSolver => ({
const contextValue = useDeepCompareMemo((): TWithSolver => ({
currentSolver: currentSolver,
effectiveSolver: currentSolverState?.type,
expectedOut: currentSolverState?.quote || toNormalizedBN(0),
Expand All @@ -264,5 +198,6 @@ function WithSolverContextApp({children}: {children: React.ReactElement}): React
);
}


export {WithSolverContextApp};
export const useSolver = (): TWithSolver => useContext(WithSolverContext);