From e987f206b3f823812cf36dc8d8cc577ee91d8499 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Mon, 14 Mar 2022 23:16:38 +0100 Subject: [PATCH 01/36] Update earn redux state to persist more data --- background/redux-slices/earn.ts | 223 +++++++++++++++++++++++--------- 1 file changed, 164 insertions(+), 59 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index b8ac9c4af9..3286f471c3 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -3,23 +3,42 @@ import { createSlice, createSelector } from "@reduxjs/toolkit" import { BigNumber, ethers } from "ethers" import { HOUR } from "../constants" import { ERC20_ABI } from "../lib/erc20" +import { fromFixedPointNumber } from "../lib/fixed-point" +import { normalizeEVMAddress } from "../lib/utils" import VAULT_ABI from "../lib/vault" import { EIP712TypedData, HexString } from "../types" import { createBackgroundAsyncThunk } from "./utils" import { getContract, + getCurrentTimestamp, + getNonce, getProvider, getSignerAddress, } from "./utils/contract-utils" export type ApprovalTargetAllowance = { contractAddress: HexString - allowance: string + allowance: number +} + +export type PendingReward = { + vault: HexString + pendingAmount: BigInt +} + +export type DepositedVault = { + vault: HexString + depositedAmount: BigInt } export type EarnState = { signature: Signature approvalTargetAllowances: ApprovalTargetAllowance[] + depositedVaults: DepositedVault[] + pendingRewards: PendingReward[] + currentlyDepositing: boolean + currentlyApproving: boolean + depositError: boolean } export type Signature = { @@ -35,6 +54,11 @@ export const initialState: EarnState = { v: 0, }, approvalTargetAllowances: [], + depositedVaults: [], + pendingRewards: [], + currentlyDepositing: false, + currentlyApproving: false, + depositError: false, } export type EIP712DomainType = { @@ -58,47 +82,10 @@ export type SignTypedDataRequest = { typedData: EIP712TypedData } -// once testnet contracts are deployed we should replace this const APPROVAL_TARGET_CONTRACT_ADDRESS = - "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" // currently: swap router - -export const vaultDeposit = createBackgroundAsyncThunk( - "signing/vaultAndDeposit", - async ( - { - vaultContractAddress, - amount, - }: { - tokenContractAddress: HexString - vaultContractAddress: HexString - amount: BigInt - }, - { getState } - ) => { - const provider = getProvider() - const signer = provider.getSigner() - const signerAddress = await getSignerAddress() + "0x9a7E392264500e46AAe5277C99d3CD381269cb9B" - const state = getState() - const { earn } = state as { earn: EarnState } - - const vaultContract = await getContract(vaultContractAddress, VAULT_ABI) - - const depositTransactionData = - await vaultContract.populateTransaction.depositWithApprovalTarget( - amount, - signerAddress, - signerAddress, - amount, - (await provider.getBlock(provider.getBlockNumber())).timestamp + - 3 * HOUR, - earn.signature.r, - earn.signature.s, - earn.signature.v - ) - signer.sendTransaction(depositTransactionData) - } -) +const DOGGO_TOKEN_CONTRACT = "0x2eD9D339899CD5f1E4a3B131F467E76549E8Eab0" export const vaultWithdraw = createBackgroundAsyncThunk( "earn/vaultWithdraw", @@ -112,11 +99,8 @@ export const vaultWithdraw = createBackgroundAsyncThunk( const provider = getProvider() const signer = provider.getSigner() - const vaultContract = new ethers.Contract( - vaultContractAddress, - VAULT_ABI, - signer - ) + const vaultContract = await getContract(vaultContractAddress, VAULT_ABI) + const signedWithdrawTransaction = await signer.signTransaction( await vaultContract.functions["withdraw(uint256)"](amount) ) @@ -125,8 +109,8 @@ export const vaultWithdraw = createBackgroundAsyncThunk( } ) -export const getRewards = createBackgroundAsyncThunk( - "earn/getRewards", +export const claimRewards = createBackgroundAsyncThunk( + "earn/clamRewards", async (vaultContractAddress: HexString) => { const provider = getProvider() const signer = provider.getSigner() @@ -155,11 +139,39 @@ const earnSlice = createSlice({ ...state, signature: { r, s, v }, }), + currentlyDepositing: (immerState, { payload }: { payload: boolean }) => { + immerState.currentlyDepositing = payload + }, + currentlyApproving: (immerState, { payload }: { payload: boolean }) => { + immerState.currentlyApproving = payload + }, + deposited: ( + immerState, + { payload }: { payload: { vault: HexString; depositedAmount: BigInt } } + ) => { + immerState.depositedVaults.push(payload) + }, + withdrawn: ( + state, + { payload }: { payload: { vault: HexString; depositedAmount: BigInt } } + ) => { + return { + ...state, + depositedVaults: state.depositedVaults.map((vault) => + vault.vault === payload.vault + ? { ...vault, depositedAmount: payload.depositedAmount } + : vault + ), + } + }, + depositError: (immerState, { payload }: { payload: boolean }) => { + immerState.depositError = payload + }, saveAllowance: ( state, { payload, - }: { payload: { contractAddress: HexString; allowance: string } } + }: { payload: { contractAddress: HexString; allowance: number } } ) => { const { contractAddress, allowance } = payload return { @@ -173,16 +185,87 @@ const earnSlice = createSlice({ }, }) -export const { saveSignature, saveAllowance } = earnSlice.actions +export const { + saveSignature, + saveAllowance, + currentlyDepositing, + currentlyApproving, + deposited, + withdrawn, + depositError, +} = earnSlice.actions export default earnSlice.reducer +export const vaultDeposit = createBackgroundAsyncThunk( + "signing/vaultDeposit", + async ( + { + vaultContractAddress, + amount, + }: { + tokenContractAddress: HexString + vaultContractAddress: HexString + amount: BigInt + }, + { getState, dispatch } + ) => { + const provider = getProvider() + const signer = provider.getSigner() + const signerAddress = await getSignerAddress() + + const state = getState() + const { earn } = state as { earn: EarnState } + + const vaultContract = await getContract(vaultContractAddress, VAULT_ABI) + const doggoTokenContract = await getContract( + DOGGO_TOKEN_CONTRACT, + ERC20_ABI + ) + + const timestamp = await getCurrentTimestamp() + const signatureDeadline = timestamp + 12 * HOUR + + const amountPermitted = doggoTokenContract.allowance( + signerAddress, + APPROVAL_TARGET_CONTRACT_ADDRESS + ) + + const depositTransactionData = + await vaultContract.populateTransaction.depositWithApprovalTarget( + amount, + signerAddress, + signerAddress, + amountPermitted, + signatureDeadline, + earn.signature.r, + earn.signature.s, + earn.signature.v + ) + const response = signer.sendTransaction(depositTransactionData) + const result = await response + const receipt = await result.wait() + if (receipt.status === 1) { + dispatch(currentlyDepositing(false)) + dispatch( + deposited({ + vault: normalizeEVMAddress(vaultContractAddress), + depositedAmount: amount, + }) + ) + } + dispatch(currentlyDepositing(false)) + dispatch(dispatch(depositError(true))) + } +) + export const approveApprovalTarget = createBackgroundAsyncThunk( "earn/approveApprovalTarget", async ( tokenContractAddress: HexString, { dispatch } ): Promise => { + dispatch(currentlyApproving(true)) const provider = getProvider() const signer = provider.getSigner() @@ -196,7 +279,6 @@ export const approveApprovalTarget = createBackgroundAsyncThunk( try { const tx = await signer.sendTransaction(approvalTransactionData) await tx.wait() - const { r, s, v } = tx if ( typeof r !== "undefined" && @@ -204,9 +286,11 @@ export const approveApprovalTarget = createBackgroundAsyncThunk( typeof s !== "undefined" ) { dispatch(earnSlice.actions.saveSignature({ r, s, v })) + dispatch(currentlyApproving(false)) } return tx } catch (error) { + dispatch(currentlyApproving(false)) return undefined } } @@ -226,21 +310,31 @@ export const checkApprovalTargetApproval = createBackgroundAsyncThunk( ) if (knownAllowanceIndex === -1) { try { - const allowance: BigNumber = await assetContract.functions.allowance( + const allowance: BigNumber = await assetContract.allowance( signerAddress, APPROVAL_TARGET_CONTRACT_ADDRESS ) + const amount = fromFixedPointNumber( + { amount: allowance.toBigInt(), decimals: 18 }, + 2 + ) dispatch( earnSlice.actions.saveAllowance({ contractAddress: tokenContractAddress, - allowance: allowance.toString(), + allowance: amount, }) ) + return { + contractAddress: tokenContractAddress, + allowance: amount, + } as ApprovalTargetAllowance } catch (err) { return undefined } } - return earn.approvalTargetAllowances[knownAllowanceIndex] + return earn.approvalTargetAllowances.find( + (_, i) => i === knownAllowanceIndex + ) } ) @@ -252,7 +346,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( amount, }: { vaultContractAddress: HexString - amount: BigInt + amount: string }, { dispatch } ) => { @@ -261,6 +355,9 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const signerAddress = await getSignerAddress() const chainID = await signer.getChainId() + const timestamp = await getCurrentTimestamp() + const signatureDeadline = timestamp + 12 * HOUR + const types = { Message: [ { @@ -294,11 +391,9 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const message = { owner: signerAddress, spender: vaultContractAddress, - value: amount.toString(), - nonce: 0, - deadline: - (await provider.getBlock(provider.getBlockNumber())).timestamp + - 3 * HOUR, + value: amount, + nonce: getNonce(), + deadline: signatureDeadline, } // _signTypedData is the ethers function name, once the official release will be ready _ will be dropped @@ -320,3 +415,13 @@ export const selectApprovalTargetApprovals = createSelector( }, (approvals) => approvals ) + +export const selectCurrentlyApproving = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState?.currentlyApproving +) + +export const selectCurrentlyDepositing = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.currentlyDepositing +) From a064453c646570e9dcee992c754e542aeacdf958 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Mon, 14 Mar 2022 23:18:12 +0100 Subject: [PATCH 02/36] Add nonce and timestamp utils and unhide earn --- background/redux-slices/index.ts | 2 +- background/redux-slices/utils/contract-utils.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/background/redux-slices/index.ts b/background/redux-slices/index.ts index 0e9111ec5b..f2fd1a851d 100644 --- a/background/redux-slices/index.ts +++ b/background/redux-slices/index.ts @@ -29,7 +29,7 @@ const mainReducer = combineReducers({ claim: claimReducer, signing: signingReducer, ...(HIDE_IMPORT_LEDGER ? {} : { ledger: ledgerReducer }), - ...(HIDE_EARN_PAGE ? {} : { earn: earnReducer }), + earn: earnReducer, }) export default mainReducer diff --git a/background/redux-slices/utils/contract-utils.ts b/background/redux-slices/utils/contract-utils.ts index 1a9040f556..a5fd679f04 100644 --- a/background/redux-slices/utils/contract-utils.ts +++ b/background/redux-slices/utils/contract-utils.ts @@ -52,3 +52,17 @@ export const getSignerAddress = async (): Promise => { const signerAddress = await signer.getAddress() return signerAddress } + +export const getNonce = async (): Promise => { + const provider = getProvider() + const signer = provider.getSigner() + const signerAddress = await signer.getAddress() + const nonce = provider.getTransactionCount(signerAddress) + return nonce +} + +export const getCurrentTimestamp = async (): Promise => { + const provider = getProvider() + const { timestamp } = await provider.getBlock(provider.getBlockNumber()) + return timestamp +} From da1c323496f1ed6ff070e2db9cfde15c95e2e5d2 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Mon, 14 Mar 2022 23:19:03 +0100 Subject: [PATCH 03/36] Integrating earn and approving into the ui --- ui/pages/EarnDeposit.tsx | 95 ++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index eeaa6c867a..dbde4b9c89 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -1,15 +1,16 @@ -import React, { ReactElement, useEffect, useState } from "react" +import React, { ReactElement, useEffect, useMemo, useState } from "react" import { selectAccountAndTimestampedActivities } from "@tallyho/tally-background/redux-slices/selectors" import { + ApprovalTargetAllowance, approveApprovalTarget, checkApprovalTargetApproval, permitVaultDeposit, - selectApprovalTargetApprovals, + selectCurrentlyApproving, } from "@tallyho/tally-background/redux-slices/earn" import { AnyAsset } from "@tallyho/tally-background/assets" import { HexString } from "@tallyho/tally-background/types" -import { useLocation } from "react-router-dom" +import { useHistory, useLocation } from "react-router-dom" import BackButton from "../components/Shared/SharedBackButton" import SharedAssetIcon from "../components/Shared/SharedAssetIcon" @@ -26,49 +27,52 @@ export default function EarnDeposit(): ReactElement { const [withdrawSlideupVisible, setWithdrawalSlideupVisible] = useState(false) const [isApproved, setIsApproved] = useState(false) const [deposited, setDeposited] = useState(false) + const [allowance, setAllowance] = useState(0) const [availableRewards, setAvailableRewards] = useState("21,832") const dispatch = useBackgroundDispatch() + const history = useHistory() + const { asset } = useLocation().state as { asset: AnyAsset & { contractAddress: HexString } } + const isCurrentlyApproving = useBackgroundSelector(selectCurrentlyApproving) + const showWithdrawalModal = () => { setWithdrawalSlideupVisible(true) } + useEffect(() => { + const getApprovalAmount = async () => { + const approvedAmount = (await dispatch( + checkApprovalTargetApproval(asset.contractAddress) + )) as unknown as ApprovalTargetAllowance + setAllowance(approvedAmount.allowance) + } + const allowanceGreaterThanAmount = allowance >= Number(amount) + setIsApproved(allowanceGreaterThanAmount) + getApprovalAmount() + }, [asset.contractAddress, dispatch, allowance, amount]) + const { combinedData } = useBackgroundSelector( selectAccountAndTimestampedActivities ) - // We currently assume that the approval held by ApprovalTarget is infinite and users wont decrease it themselves. - const approvals = useBackgroundSelector(selectApprovalTargetApprovals) - - const isTokenApproved = () => { - if (!isApproved) { - const allowanceIndex = approvals?.findIndex( - (approval) => approval.contractAddress === asset.contractAddress - ) - if (allowanceIndex !== -1) { - setIsApproved(true) - } - } - } - - isTokenApproved() - const approve = () => { dispatch(approveApprovalTarget(asset.contractAddress)) + history.push("/sign-transaction") } const enable = () => { dispatch( permitVaultDeposit({ vaultContractAddress: asset.contractAddress, - amount: 2000n, + amount: "2000", }) ) + history.push("/sign-data") } const deposit = () => { @@ -84,9 +88,32 @@ export default function EarnDeposit(): ReactElement { setAvailableRewards("0") } - useEffect(() => { - dispatch(checkApprovalTargetApproval(asset.contractAddress)) - }, [dispatch, asset.contractAddress]) + const handleAmountChange = ( + value: string, + errorMessage: string | undefined + ) => { + setAmount(value) + const allowanceGreaterThanAmount = allowance >= Number(value) + setIsApproved(allowanceGreaterThanAmount) + if (errorMessage) { + setHasError(true) + } else { + setHasError(false) + } + } + + const depositButtonText = useMemo(() => { + if (isApproved) { + return "Enable" + } + if (isCurrentlyApproving) { + return "Approving..." + } + if (!isApproved) { + return "Approve" + } + return "Deposit" + }, [isApproved, isCurrentlyApproving]) return ( <> @@ -160,14 +187,9 @@ export default function EarnDeposit(): ReactElement { { - setAmount(value) - if (errorMessage) { - setHasError(true) - } else { - setHasError(false) - } - }} + onAmountChange={(value, errorMessage) => + handleAmountChange(value, errorMessage) + } selectedAsset={asset} amount={amount} disableDropdown @@ -177,14 +199,19 @@ export default function EarnDeposit(): ReactElement { - {!isApproved ? "Approve" : "Enable"} + {depositButtonText} ) : ( - + {!deposited ? "Deposit" : "Deposit more"} )} From 95037d1cdcbc3b29b8e4ca5ff2032a643452b35a Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 16 Mar 2022 17:28:23 +0100 Subject: [PATCH 04/36] Move ComingSoon earn page behind a feature flag --- .env.defaults | 1 + ui/routes/routes.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.env.defaults b/.env.defaults index 461acf3505..6b8ed8beb6 100644 --- a/.env.defaults +++ b/.env.defaults @@ -13,6 +13,7 @@ HIDE_IMPORT_LEDGER=false GAS_PRICE_POOLING_FREQUENCY=120 ETHEREUM_NETWORK=mainnet PERSIST_UI_LOCATION=false +EARN_COMING_SOON=true USE_MAINNET_FORK=false MAINNET_FORK_URL="http://127.0.0.1:8545" MAINNET_FORK_CHAIN_ID=1337 \ No newline at end of file diff --git a/ui/routes/routes.tsx b/ui/routes/routes.tsx index 393555d66a..f12d4c5566 100644 --- a/ui/routes/routes.tsx +++ b/ui/routes/routes.tsx @@ -1,4 +1,5 @@ import React, { ReactElement } from "react" +import { EARN_COMING_SOON } from "@tallyho/tally-background/features/features" import Wallet from "../pages/Wallet" import SignTransaction from "../pages/SignTransaction" import SignData from "../pages/SignData" @@ -121,7 +122,7 @@ const pageList: PageList[] = [ }, { path: "/earn", - Component: ComingSoon ?? Earn, + Component: EARN_COMING_SOON ? ComingSoon : Earn, hasTabBar: true, hasTopBar: true, persistOnClose: true, From 35b45cf40a306698e04cd3f42abe625008183c00 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 16 Mar 2022 17:29:55 +0100 Subject: [PATCH 05/36] Save doggo decimals in constants --- background/constants/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/background/constants/index.ts b/background/constants/index.ts index c878dbfaae..f5a3697aaf 100644 --- a/background/constants/index.ts +++ b/background/constants/index.ts @@ -26,5 +26,7 @@ export const DAY = 24 * HOUR export const COMMUNITY_MULTISIG_ADDRESS = "0x99b36fDbC582D113aF36A21EBa06BFEAb7b9bE12" +export const doggoTokenDecimalDigits = 18 + export * from "./currencies" export * from "./networks" From 2a30b383c6e71d40cbeaa0fbd3115344a0f91b11 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 16 Mar 2022 17:30:26 +0100 Subject: [PATCH 06/36] The coming soon flag in features --- background/features/features.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/background/features/features.ts b/background/features/features.ts index bdc0692fdf..0f2febd430 100644 --- a/background/features/features.ts +++ b/background/features/features.ts @@ -8,3 +8,4 @@ export const HIDE_CREATE_PHRASE = process.env.HIDE_CREATE_PHRASE === "true" export const HIDE_IMPORT_LEDGER = process.env.HIDE_IMPORT_LEDGER === "true" export const PERSIST_UI_LOCATION = process.env.PERSIST_UI_LOCATION === "true" export const USE_MAINNET_FORK = process.env.USE_MAINNET_FORK === "true" +export const EARN_COMING_SOON = process.env.EARN_COMING_SOON === "true" From 48d01e6472c02074c63c0487daa1ada6bfaf5037 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 16 Mar 2022 17:30:53 +0100 Subject: [PATCH 07/36] Store more data in redux We need to store more earn data in redux that now includes available vault, deposited vaults, vaults with pending rewards. Redux will also now hold the input amount so its not lost when we navigate to the signing page. Added few selectors to read the data in the UI. --- background/redux-slices/earn.ts | 211 +++++++++++++++++++++++++++++--- 1 file changed, 195 insertions(+), 16 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 3286f471c3..c69edfe76f 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -1,7 +1,9 @@ import { TransactionResponse } from "@ethersproject/abstract-provider" import { createSlice, createSelector } from "@reduxjs/toolkit" import { BigNumber, ethers } from "ethers" -import { HOUR } from "../constants" +import { AnyAsset } from "../assets" +import { HOUR, doggoTokenDecimalDigits } from "../constants" +import { USE_MAINNET_FORK } from "../features/features" import { ERC20_ABI } from "../lib/erc20" import { fromFixedPointNumber } from "../lib/fixed-point" import { normalizeEVMAddress } from "../lib/utils" @@ -22,43 +24,87 @@ export type ApprovalTargetAllowance = { } export type PendingReward = { - vault: HexString - pendingAmount: BigInt + vaultAddress: HexString + pendingAmount: number } export type DepositedVault = { - vault: HexString + vaultAddress: HexString depositedAmount: BigInt } +export type LockedValue = { + vaultAddress: HexString + lockedValue: bigint + vaultTokenSymbol: string + asset: AnyAsset +} + +export type AvailableVault = { + name: string + symbol: string + contractAddress: HexString + wantToken: HexString + active: boolean +} + export type EarnState = { signature: Signature approvalTargetAllowances: ApprovalTargetAllowance[] depositedVaults: DepositedVault[] pendingRewards: PendingReward[] + lockedAmounts: LockedValue[] + availableVaults: AvailableVault[] currentlyDepositing: boolean currentlyApproving: boolean depositError: boolean + inputAmount: string } export type Signature = { - r: string - s: string - v: number + r: string | undefined + s: string | undefined + v: number | undefined } export const initialState: EarnState = { signature: { - r: "", - s: "", - v: 0, + r: undefined, + s: undefined, + v: undefined, }, approvalTargetAllowances: [], depositedVaults: [], pendingRewards: [], + lockedAmounts: [], + availableVaults: [ + { + name: "WBTC", + symbol: "WBTC", + contractAddress: "0x5D1aB585B7d05d81F07E8Ff33998f2f11647B46e", + wantToken: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + active: true, + } as AnyAsset & { + contractAddress: HexString + active: boolean + wantToken: HexString + }, + { + name: "USDT", + symbol: "USDT", + contractAddress: "0x362Db3b1F85154a537CEf5e2a3D87D56d71DF823", + active: true, + wantToken: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + } as AnyAsset & { + contractAddress: HexString + active: boolean + wantToken: HexString + }, + ], currentlyDepositing: false, currentlyApproving: false, depositError: false, + inputAmount: "", } export type EIP712DomainType = { @@ -109,7 +155,7 @@ export const vaultWithdraw = createBackgroundAsyncThunk( } ) -export const claimRewards = createBackgroundAsyncThunk( +export const claimVaultRewards = createBackgroundAsyncThunk( "earn/clamRewards", async (vaultContractAddress: HexString) => { const provider = getProvider() @@ -139,18 +185,42 @@ const earnSlice = createSlice({ ...state, signature: { r, s, v }, }), + clearSignature: (state) => ({ + ...state, + signature: { r: undefined, s: undefined, v: undefined }, + }), currentlyDepositing: (immerState, { payload }: { payload: boolean }) => { immerState.currentlyDepositing = payload }, currentlyApproving: (immerState, { payload }: { payload: boolean }) => { immerState.currentlyApproving = payload }, + inputAmount: (state, { payload }: { payload: string }) => { + return { + ...state, + inputAmount: payload, + } + }, deposited: ( immerState, - { payload }: { payload: { vault: HexString; depositedAmount: BigInt } } + { + payload, + }: { payload: { vaultAddress: HexString; depositedAmount: BigInt } } ) => { immerState.depositedVaults.push(payload) }, + earnedAmounts: (state, { payload }: { payload: PendingReward[] }) => { + return { + ...state, + pendingRewards: payload, + } + }, + lockedAmounts: (state, { payload }: { payload: LockedValue[] }) => { + return { + ...state, + lockedAmounts: payload, + } + }, withdrawn: ( state, { payload }: { payload: { vault: HexString; depositedAmount: BigInt } } @@ -158,7 +228,7 @@ const earnSlice = createSlice({ return { ...state, depositedVaults: state.depositedVaults.map((vault) => - vault.vault === payload.vault + vault.vaultAddress === payload.vault ? { ...vault, depositedAmount: payload.depositedAmount } : vault ), @@ -193,6 +263,10 @@ export const { deposited, withdrawn, depositError, + earnedAmounts, + lockedAmounts, + inputAmount, + clearSignature, } = earnSlice.actions export default earnSlice.reducer @@ -242,6 +316,9 @@ export const vaultDeposit = createBackgroundAsyncThunk( earn.signature.s, earn.signature.v ) + if (USE_MAINNET_FORK) { + depositTransactionData.gasLimit = BigNumber.from(350000) // for mainnet fork only + } const response = signer.sendTransaction(depositTransactionData) const result = await response const receipt = await result.wait() @@ -249,7 +326,7 @@ export const vaultDeposit = createBackgroundAsyncThunk( dispatch(currentlyDepositing(false)) dispatch( deposited({ - vault: normalizeEVMAddress(vaultContractAddress), + vaultAddress: normalizeEVMAddress(vaultContractAddress), depositedAmount: amount, }) ) @@ -259,6 +336,64 @@ export const vaultDeposit = createBackgroundAsyncThunk( } ) +export const updateEarnedOnDepositedPools = createBackgroundAsyncThunk( + "earn/updateEarnedOnDepositedPools", + async (_, { getState, dispatch }) => { + const currentState = getState() + const { earn } = currentState as { earn: EarnState } + const { depositedVaults } = earn + const provider = getProvider() + const signer = provider.getSigner() + const account = signer.getAddress() + + const pendingAmounts = depositedVaults.map(async (vault) => { + const vaultContract = await getContract(vault.vaultAddress, VAULT_ABI) + const earned: BigNumber = await vaultContract.earned(account) + return { + vaultAddress: vault.vaultAddress, + pendingAmount: fromFixedPointNumber( + { amount: earned.toBigInt(), decimals: doggoTokenDecimalDigits }, + 0 + ), + } + }) + + const amounts = await Promise.all(pendingAmounts) + dispatch(earnedAmounts(amounts)) + return amounts + } +) + +export const updateLockedValues = createBackgroundAsyncThunk( + "earn/updateLockedValues", + async (_, { getState, dispatch }) => { + const currentState = getState() + const { earn } = currentState as { earn: EarnState } + const { availableVaults } = earn + + const locked = availableVaults.map(async (vault) => { + const wantTokenContract = await getContract(vault.wantToken, ERC20_ABI) + const lockedValue: BigNumber = await wantTokenContract.balanceOf( + vault.contractAddress + ) + return { + vaultAddress: vault.contractAddress, + vaultTokenSymbol: vault.symbol, + lockedValue: lockedValue.toBigInt(), + asset: { + contractAddress: vault.wantToken, + name: vault.name, + symbol: vault.symbol, + } as AnyAsset, + } + }) + // TODO Convert below to be showing $ value rather than BigNumber + const amounts = await Promise.all(locked) + dispatch(lockedAmounts(amounts)) + return amounts + } +) + export const approveApprovalTarget = createBackgroundAsyncThunk( "earn/approveApprovalTarget", async ( @@ -277,6 +412,9 @@ export const approveApprovalTarget = createBackgroundAsyncThunk( ethers.constants.MaxUint256 ) try { + if (USE_MAINNET_FORK) { + approvalTransactionData.gasLimit = BigNumber.from(350000) // for mainnet fork only + } const tx = await signer.sendTransaction(approvalTransactionData) await tx.wait() const { r, s, v } = tx @@ -358,6 +496,8 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const timestamp = await getCurrentTimestamp() const signatureDeadline = timestamp + 12 * HOUR + const nonceValue = await getNonce() + const types = { Message: [ { @@ -370,7 +510,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( }, { name: "value", - type: "string", + type: "uint256", }, { name: "nonce", @@ -392,7 +532,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( owner: signerAddress, spender: vaultContractAddress, value: amount, - nonce: getNonce(), + nonce: nonceValue, deadline: signatureDeadline, } @@ -425,3 +565,42 @@ export const selectCurrentlyDepositing = createSelector( (state: { earn: EarnState }): EarnState => state.earn, (earnState: EarnState) => earnState.currentlyDepositing ) + +export const selectAvailableVaults = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.availableVaults +) + +// TODO This should include a maincurrency value for each element in the array +export const selectLockedValues = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.lockedAmounts +) + +// TODO This should return a maincurrency value +export const selectTotalLockedValue = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => + earnState.lockedAmounts.reduce((total, vault) => { + return total + Number(vault.lockedValue) + }, 0) +) + +export const selectIsSignatureAvailable = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => { + if ( + typeof earnState.signature.r !== "undefined" && + typeof earnState.signature.v !== "undefined" && + typeof earnState.signature.s !== "undefined" + ) { + return true + } + return false + } +) + +export const selectEarnInputAmount = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.inputAmount +) From 4d7c1d5c98b1d06607c9220b2d849f87c7d9e41a Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 16 Mar 2022 17:32:32 +0100 Subject: [PATCH 08/36] Mainnet fork gasEstimate override --- background/services/chain/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 709b12f4f5..59539384de 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -50,6 +50,7 @@ import type { } from "../enrichment" import { HOUR } from "../../constants" import SerialFallbackProvider from "./serial-fallback-provider" +import { USE_MAINNET_FORK } from "../../features/features" // We can't use destructuring because webpack has to replace all instances of // `process.env` variables in the bundled output @@ -592,6 +593,9 @@ export default class ChainService extends BaseService { network: EVMNetwork, transactionRequest: EIP1559TransactionRequest ): Promise { + if (USE_MAINNET_FORK) { + return 350000n + } const estimate = await this.providers.ethereum.estimateGas( ethersTransactionRequestFromEIP1559TransactionRequest(transactionRequest) ) From fcab7d6ddda63f66239b5b8426d08e2ad1d0579f Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 16 Mar 2022 17:33:29 +0100 Subject: [PATCH 09/36] Replace hardcoded data with redux data --- ui/pages/Earn.tsx | 57 ++++++++++++++++------------ ui/pages/EarnDeposit.tsx | 80 +++++++++++++++++++++++++--------------- 2 files changed, 83 insertions(+), 54 deletions(-) diff --git a/ui/pages/Earn.tsx b/ui/pages/Earn.tsx index 97cb0da1e5..ca6cc3181f 100644 --- a/ui/pages/Earn.tsx +++ b/ui/pages/Earn.tsx @@ -1,37 +1,49 @@ -import { AnyAsset } from "@tallyho/tally-background/assets" -import { HexString } from "@tallyho/tally-background/types" +import { + AvailableVault, + selectAvailableVaults, + selectLockedValues, + selectTotalLockedValue, + updateEarnedOnDepositedPools, + updateLockedValues, +} from "@tallyho/tally-background/redux-slices/earn" -import React, { ReactElement, useState } from "react" +import React, { ReactElement, useEffect, useState } from "react" import { Link } from "react-router-dom" import SharedAssetIcon from "../components/Shared/SharedAssetIcon" import SharedPanelSwitcher from "../components/Shared/SharedPanelSwitcher" +import { useBackgroundDispatch, useBackgroundSelector } from "../hooks" type EarnCardProps = { - asset: (AnyAsset & { contractAddress: HexString }) | undefined + vault: AvailableVault } -function EarnCard({ asset }: EarnCardProps) { +function EarnCard({ vault }: EarnCardProps) { + const lockedValues = useBackgroundSelector(selectLockedValues) + const currentVault = lockedValues.find( + (lockedValue) => lockedValue.vaultAddress === vault?.contractAddress + ) + return (
- +
- {asset?.symbol} + {vault?.symbol} Estimated APR 250%
TVL
-
$22.800.322
+
${Number(currentVault?.lockedValue)}
@@ -135,18 +147,14 @@ function EarnCard({ asset }: EarnCardProps) { export default function Earn(): ReactElement { const [panelNumber, setPanelNumber] = useState(0) - const assets = [ - { - name: "Dai Token", - symbol: "DAI", - contractAddress: "0x6b175474e89094c44da98b954eedeac495271d0f", - } as AnyAsset & { contractAddress: HexString }, - { - name: "Keep", - symbol: "KEEP", - contractAddress: "0x85eee30c52b0b379b046fb0f85f4f3dc3009afec", - } as AnyAsset & { contractAddress: HexString }, - ] + const dispatch = useBackgroundDispatch() + const availableVaults = useBackgroundSelector(selectAvailableVaults) + const totalTVL = useBackgroundSelector(selectTotalLockedValue) + + useEffect(() => { + dispatch(updateEarnedOnDepositedPools()) + dispatch(updateLockedValues()) + }, [dispatch]) return ( <> @@ -154,7 +162,8 @@ export default function Earn(): ReactElement {
Total value locked
- $23,928,292 + $ + {totalTVL || "0"}
@@ -167,9 +176,9 @@ export default function Earn(): ReactElement { {panelNumber === 0 ? (
    - {assets.map((asset) => ( + {availableVaults?.map((vault) => (
  • - +
  • ))}
diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index dbde4b9c89..848f78f7b8 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -1,15 +1,18 @@ -import React, { ReactElement, useEffect, useMemo, useState } from "react" +import React, { ReactElement, useEffect, useState } from "react" import { selectAccountAndTimestampedActivities } from "@tallyho/tally-background/redux-slices/selectors" import { ApprovalTargetAllowance, approveApprovalTarget, + AvailableVault, checkApprovalTargetApproval, + inputAmount, permitVaultDeposit, selectCurrentlyApproving, + selectEarnInputAmount, + selectIsSignatureAvailable, + selectLockedValues, } from "@tallyho/tally-background/redux-slices/earn" -import { AnyAsset } from "@tallyho/tally-background/assets" -import { HexString } from "@tallyho/tally-background/types" import { useHistory, useLocation } from "react-router-dom" import BackButton from "../components/Shared/SharedBackButton" import SharedAssetIcon from "../components/Shared/SharedAssetIcon" @@ -21,11 +24,13 @@ import SharedSlideUpMenu from "../components/Shared/SharedSlideUpMenu" import { useBackgroundDispatch, useBackgroundSelector } from "../hooks" export default function EarnDeposit(): ReactElement { + const storedInput = useBackgroundSelector(selectEarnInputAmount) const [panelNumber, setPanelNumber] = useState(0) - const [amount, setAmount] = useState("") + const [amount, setAmount] = useState(storedInput) const [hasError, setHasError] = useState(false) const [withdrawSlideupVisible, setWithdrawalSlideupVisible] = useState(false) const [isApproved, setIsApproved] = useState(false) + const [isEnabled, setIsEnabled] = useState(false) const [deposited, setDeposited] = useState(false) const [allowance, setAllowance] = useState(0) const [availableRewards, setAvailableRewards] = useState("21,832") @@ -34,11 +39,16 @@ export default function EarnDeposit(): ReactElement { const history = useHistory() - const { asset } = useLocation().state as { - asset: AnyAsset & { contractAddress: HexString } + const { vault } = useLocation().state as { + vault: AvailableVault } + const lockedValues = useBackgroundSelector(selectLockedValues) + const lockedValue = lockedValues.find( + (locked) => locked.vaultAddress === vault?.contractAddress + ) const isCurrentlyApproving = useBackgroundSelector(selectCurrentlyApproving) + const signatureAvailable = useBackgroundSelector(selectIsSignatureAvailable) const showWithdrawalModal = () => { setWithdrawalSlideupVisible(true) @@ -47,29 +57,30 @@ export default function EarnDeposit(): ReactElement { useEffect(() => { const getApprovalAmount = async () => { const approvedAmount = (await dispatch( - checkApprovalTargetApproval(asset.contractAddress) + checkApprovalTargetApproval(vault.wantToken) )) as unknown as ApprovalTargetAllowance setAllowance(approvedAmount.allowance) } const allowanceGreaterThanAmount = allowance >= Number(amount) setIsApproved(allowanceGreaterThanAmount) getApprovalAmount() - }, [asset.contractAddress, dispatch, allowance, amount]) + }, [vault.wantToken, dispatch, allowance, amount]) const { combinedData } = useBackgroundSelector( selectAccountAndTimestampedActivities ) const approve = () => { - dispatch(approveApprovalTarget(asset.contractAddress)) + dispatch(approveApprovalTarget(vault.wantToken)) history.push("/sign-transaction") } const enable = () => { + setIsEnabled(true) dispatch( permitVaultDeposit({ - vaultContractAddress: asset.contractAddress, - amount: "2000", + vaultContractAddress: vault.contractAddress, + amount, }) ) history.push("/sign-data") @@ -93,6 +104,7 @@ export default function EarnDeposit(): ReactElement { errorMessage: string | undefined ) => { setAmount(value) + dispatch(inputAmount(value)) const allowanceGreaterThanAmount = allowance >= Number(value) setIsApproved(allowanceGreaterThanAmount) if (errorMessage) { @@ -102,18 +114,22 @@ export default function EarnDeposit(): ReactElement { } } - const depositButtonText = useMemo(() => { - if (isApproved) { + const depositButtonText = () => { + if (!isEnabled && !signatureAvailable) { return "Enable" } - if (isCurrentlyApproving) { - return "Approving..." - } - if (!isApproved) { - return "Approve" + if (deposited) { + return "Deposit more" } return "Deposit" - }, [isApproved, isCurrentlyApproving]) + } + + const approveButtonText = () => { + if (isCurrentlyApproving === true) { + return "Approving..." + } + return "Approve" + } return ( <> @@ -123,8 +139,8 @@ export default function EarnDeposit(): ReactElement {
  • VAULT
    - -

    {asset.symbol}

    + +

    {vault.symbol}

  • Total value locked
    -
    $20,283,219
    +
    ${Number(lockedValue?.lockedValue)}
  • Rewards
    @@ -156,7 +172,7 @@ export default function EarnDeposit(): ReactElement {
  • Deposited amount
    - 27,834 {asset.symbol} + 27,834 {vault.symbol}
  • @@ -190,29 +206,33 @@ export default function EarnDeposit(): ReactElement { onAmountChange={(value, errorMessage) => handleAmountChange(value, errorMessage) } - selectedAsset={asset} + selectedAsset={{ + name: vault.name, + symbol: vault.symbol, + contractAddress: vault.wantToken, + }} amount={amount} disableDropdown />
    - {!isApproved ? ( + {!isApproved || isCurrentlyApproving ? ( - {depositButtonText} + {approveButtonText()} ) : ( - {!deposited ? "Deposit" : "Deposit more"} + {depositButtonText()} )}
    From af261b58e675d328ea6c54d04863df48afc2e6b0 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 16 Mar 2022 17:34:07 +0100 Subject: [PATCH 10/36] Sign typed data must send back on success --- ui/pages/SignData.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/pages/SignData.tsx b/ui/pages/SignData.tsx index e0c6f08de5..babefae232 100644 --- a/ui/pages/SignData.tsx +++ b/ui/pages/SignData.tsx @@ -76,6 +76,7 @@ export default function SignData({ dispatch(signTypedData(typedDataRequest)) } } + history.goBack() } const handleReject = async () => { From 5b28d0983564372471f355affeb452c9e3002b08 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Fri, 18 Mar 2022 16:59:43 +0100 Subject: [PATCH 11/36] Update the deposit function --- background/redux-slices/earn.ts | 29 +++++++++++++++-------------- ui/pages/EarnDeposit.tsx | 20 +++++++++++++++++--- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index c69edfe76f..d8f62dc46f 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -5,7 +5,7 @@ import { AnyAsset } from "../assets" import { HOUR, doggoTokenDecimalDigits } from "../constants" import { USE_MAINNET_FORK } from "../features/features" import { ERC20_ABI } from "../lib/erc20" -import { fromFixedPointNumber } from "../lib/fixed-point" +import { fromFixedPointNumber, toFixedPoint } from "../lib/fixed-point" import { normalizeEVMAddress } from "../lib/utils" import VAULT_ABI from "../lib/vault" import { EIP712TypedData, HexString } from "../types" @@ -277,10 +277,11 @@ export const vaultDeposit = createBackgroundAsyncThunk( { vaultContractAddress, amount, + tokenAddress, }: { - tokenContractAddress: HexString vaultContractAddress: HexString - amount: BigInt + amount: string + tokenAddress: HexString }, { getState, dispatch } ) => { @@ -292,29 +293,28 @@ export const vaultDeposit = createBackgroundAsyncThunk( const { earn } = state as { earn: EarnState } const vaultContract = await getContract(vaultContractAddress, VAULT_ABI) - const doggoTokenContract = await getContract( - DOGGO_TOKEN_CONTRACT, - ERC20_ABI - ) + const tokenContract = await getContract(tokenAddress, ERC20_ABI) const timestamp = await getCurrentTimestamp() - const signatureDeadline = timestamp + 12 * HOUR + // ! cleanup + const signatureDeadline = BigInt(timestamp) + BigInt(12) * BigInt(HOUR) - const amountPermitted = doggoTokenContract.allowance( + const amountPermitted = await tokenContract.allowance( signerAddress, APPROVAL_TARGET_CONTRACT_ADDRESS ) const depositTransactionData = await vaultContract.populateTransaction.depositWithApprovalTarget( - amount, + // ! remove hardcoded decimal + toFixedPoint(Number(amount), 6), signerAddress, signerAddress, amountPermitted, signatureDeadline, + earn.signature.v, earn.signature.r, - earn.signature.s, - earn.signature.v + earn.signature.s ) if (USE_MAINNET_FORK) { depositTransactionData.gasLimit = BigNumber.from(350000) // for mainnet fork only @@ -327,7 +327,8 @@ export const vaultDeposit = createBackgroundAsyncThunk( dispatch( deposited({ vaultAddress: normalizeEVMAddress(vaultContractAddress), - depositedAmount: amount, + // ! remove hardcoded decimal + depositedAmount: toFixedPoint(Number(amount), 6), }) ) } @@ -531,7 +532,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const message = { owner: signerAddress, spender: vaultContractAddress, - value: amount, + value: toFixedPoint(Number(amount), 6), // ! REMOVE HARDCODED DECIMAL nonce: nonceValue, deadline: signatureDeadline, } diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index 848f78f7b8..52ba155f48 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -11,7 +11,12 @@ import { selectEarnInputAmount, selectIsSignatureAvailable, selectLockedValues, + vaultDeposit, } from "@tallyho/tally-background/redux-slices/earn" +import { + clearTransactionState, + TransactionConstructionStatus, +} from "@tallyho/tally-background/redux-slices/transaction-construction" import { useHistory, useLocation } from "react-router-dom" import BackButton from "../components/Shared/SharedBackButton" @@ -70,7 +75,8 @@ export default function EarnDeposit(): ReactElement { selectAccountAndTimestampedActivities ) - const approve = () => { + const approve = async () => { + await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) dispatch(approveApprovalTarget(vault.wantToken)) history.push("/sign-transaction") } @@ -86,8 +92,16 @@ export default function EarnDeposit(): ReactElement { history.push("/sign-data") } - const deposit = () => { - setDeposited(true) + const deposit = async () => { + await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) + dispatch( + vaultDeposit({ + vaultContractAddress: vault.contractAddress, + amount, + tokenAddress: vault.wantToken, + }) + ) + history.push("/sign-transaction") } const withdraw = () => { From e4194267529909588f0bf56cc023e734b1e4e240 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Fri, 18 Mar 2022 17:00:06 +0100 Subject: [PATCH 12/36] Hardcode assets to be tracked on mainnet fork --- .../services/chain/asset-data-helper.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/background/services/chain/asset-data-helper.ts b/background/services/chain/asset-data-helper.ts index 17bb04b10b..9b4d9adec0 100644 --- a/background/services/chain/asset-data-helper.ts +++ b/background/services/chain/asset-data-helper.ts @@ -17,7 +17,9 @@ import { AddressOnNetwork } from "../../accounts" import { HexString } from "../../types" import logger from "../../lib/logger" import { EVMNetwork, SmartContract } from "../../networks" -import { getMetadata as getERC20Metadata } from "../../lib/erc20" +import { getBalance, getMetadata as getERC20Metadata } from "../../lib/erc20" +import { USE_MAINNET_FORK } from "../../features/features" +import { FORK } from "../../constants" interface ProviderManager { providerForNetwork(network: EVMNetwork): SerialFallbackProvider | undefined @@ -65,6 +67,29 @@ export default class AssetDataHelper { ) } + // Load balances of tokens on the mainnet fork + if (USE_MAINNET_FORK) { + const signer = provider.getSigner() + const signerAddress = await signer.getAddress() + const tokens = [ + "0x2eD9D339899CD5f1E4a3B131F467E76549E8Eab0", + "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + "0x514910771AF9Ca656af840dff83E8264EcF986CA", + ] + const balances = tokens.map(async (token) => { + const balance = await getBalance(provider, token, signerAddress) + return { + smartContract: { + contractAddress: token, + homeNetwork: FORK, + }, + amount: BigInt(balance.toString()), + } + }) + const resolvedBalances = Promise.all(balances) + return resolvedBalances + } return [] } From 81071e0fef63782226e57f71f60d7bd7a08c589e Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Mon, 21 Mar 2022 12:09:46 +0100 Subject: [PATCH 13/36] Fetch nonces from the contract using EIP2612 --- background/redux-slices/earn.ts | 24 ++++++++++++------- .../services/chain/asset-data-helper.ts | 1 - ui/pages/EarnDeposit.tsx | 1 + 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index d8f62dc46f..3792d9ef16 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -4,7 +4,7 @@ import { BigNumber, ethers } from "ethers" import { AnyAsset } from "../assets" import { HOUR, doggoTokenDecimalDigits } from "../constants" import { USE_MAINNET_FORK } from "../features/features" -import { ERC20_ABI } from "../lib/erc20" +import { ERC20_ABI, ERC2612_INTERFACE } from "../lib/erc20" import { fromFixedPointNumber, toFixedPoint } from "../lib/fixed-point" import { normalizeEVMAddress } from "../lib/utils" import VAULT_ABI from "../lib/vault" @@ -293,21 +293,23 @@ export const vaultDeposit = createBackgroundAsyncThunk( const { earn } = state as { earn: EarnState } const vaultContract = await getContract(vaultContractAddress, VAULT_ABI) - const tokenContract = await getContract(tokenAddress, ERC20_ABI) const timestamp = await getCurrentTimestamp() + + const TokenContract = await getContract(tokenAddress, ERC2612_INTERFACE) + + const decimals: BigNumber = await TokenContract.decimals() // ! cleanup const signatureDeadline = BigInt(timestamp) + BigInt(12) * BigInt(HOUR) - const amountPermitted = await tokenContract.allowance( + const amountPermitted = await TokenContract.allowance( signerAddress, APPROVAL_TARGET_CONTRACT_ADDRESS ) const depositTransactionData = await vaultContract.populateTransaction.depositWithApprovalTarget( - // ! remove hardcoded decimal - toFixedPoint(Number(amount), 6), + toFixedPoint(Number(amount), decimals.toNumber()), signerAddress, signerAddress, amountPermitted, @@ -327,8 +329,7 @@ export const vaultDeposit = createBackgroundAsyncThunk( dispatch( deposited({ vaultAddress: normalizeEVMAddress(vaultContractAddress), - // ! remove hardcoded decimal - depositedAmount: toFixedPoint(Number(amount), 6), + depositedAmount: toFixedPoint(Number(amount), decimals.toNumber()), }) ) } @@ -483,9 +484,11 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( { vaultContractAddress, amount, + tokenAddress, }: { vaultContractAddress: HexString amount: string + tokenAddress: HexString }, { dispatch } ) => { @@ -497,7 +500,10 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const timestamp = await getCurrentTimestamp() const signatureDeadline = timestamp + 12 * HOUR - const nonceValue = await getNonce() + const TokenContract = await getContract(tokenAddress, ERC2612_INTERFACE) + const nonceValue = await TokenContract.nonces(signerAddress) + + const decimals: BigNumber = await TokenContract.decimals() const types = { Message: [ @@ -532,7 +538,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const message = { owner: signerAddress, spender: vaultContractAddress, - value: toFixedPoint(Number(amount), 6), // ! REMOVE HARDCODED DECIMAL + value: toFixedPoint(Number(amount), decimals.toNumber()), nonce: nonceValue, deadline: signatureDeadline, } diff --git a/background/services/chain/asset-data-helper.ts b/background/services/chain/asset-data-helper.ts index 9b4d9adec0..39820424d7 100644 --- a/background/services/chain/asset-data-helper.ts +++ b/background/services/chain/asset-data-helper.ts @@ -72,7 +72,6 @@ export default class AssetDataHelper { const signer = provider.getSigner() const signerAddress = await signer.getAddress() const tokens = [ - "0x2eD9D339899CD5f1E4a3B131F467E76549E8Eab0", "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", "0x514910771AF9Ca656af840dff83E8264EcF986CA", diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index 52ba155f48..0cc097cb13 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -86,6 +86,7 @@ export default function EarnDeposit(): ReactElement { dispatch( permitVaultDeposit({ vaultContractAddress: vault.contractAddress, + tokenAddress: vault.wantToken, amount, }) ) From 2eda9da468d29ba9346120340356db90a0e1df3f Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Thu, 24 Mar 2022 14:12:52 +0100 Subject: [PATCH 14/36] Add additional ABIs used in Earn --- background/lib/approvalTarget.ts | 162 ++++++ background/lib/yearnVault.ts | 882 +++++++++++++++++++++++++++++++ 2 files changed, 1044 insertions(+) create mode 100644 background/lib/approvalTarget.ts create mode 100644 background/lib/yearnVault.ts diff --git a/background/lib/approvalTarget.ts b/background/lib/approvalTarget.ts new file mode 100644 index 0000000000..887b8370af --- /dev/null +++ b/background/lib/approvalTarget.ts @@ -0,0 +1,162 @@ +const APPROVAL_TARGET_ABI = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "DOMAIN_SEPARATOR", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "PERMIT_AND_TRANSFER_FROM_TYPEHASH", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "cachedChainId", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "cachedDomainSeparator", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "nonces", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "erc20", + type: "address", + }, + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "recipient", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "bytes32", + name: "r", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "permitAndTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "typeHashDigest", + type: "bytes32", + }, + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "bytes32", + name: "r", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "recoverFromTypeHashSignature", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +] + +export default APPROVAL_TARGET_ABI diff --git a/background/lib/yearnVault.ts b/background/lib/yearnVault.ts new file mode 100644 index 0000000000..a8d66b66c6 --- /dev/null +++ b/background/lib/yearnVault.ts @@ -0,0 +1,882 @@ +const YEARN_VAULT_ABI = [ + { + name: "Transfer", + inputs: [ + { name: "sender", type: "address", indexed: true }, + { name: "receiver", type: "address", indexed: true }, + { name: "value", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "Approval", + inputs: [ + { name: "owner", type: "address", indexed: true }, + { name: "spender", type: "address", indexed: true }, + { name: "value", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyAdded", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "debtRatio", type: "uint256", indexed: false }, + { name: "minDebtPerHarvest", type: "uint256", indexed: false }, + { name: "maxDebtPerHarvest", type: "uint256", indexed: false }, + { name: "performanceFee", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyReported", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "gain", type: "uint256", indexed: false }, + { name: "loss", type: "uint256", indexed: false }, + { name: "debtPaid", type: "uint256", indexed: false }, + { name: "totalGain", type: "uint256", indexed: false }, + { name: "totalLoss", type: "uint256", indexed: false }, + { name: "totalDebt", type: "uint256", indexed: false }, + { name: "debtAdded", type: "uint256", indexed: false }, + { name: "debtRatio", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "UpdateGovernance", + inputs: [{ name: "governance", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateManagement", + inputs: [{ name: "management", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateGuestList", + inputs: [{ name: "guestList", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateRewards", + inputs: [{ name: "rewards", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateDepositLimit", + inputs: [{ name: "depositLimit", type: "uint256", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdatePerformanceFee", + inputs: [{ name: "performanceFee", type: "uint256", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateManagementFee", + inputs: [{ name: "managementFee", type: "uint256", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateGuardian", + inputs: [{ name: "guardian", type: "address", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "EmergencyShutdown", + inputs: [{ name: "active", type: "bool", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "UpdateWithdrawalQueue", + inputs: [{ name: "queue", type: "address[20]", indexed: false }], + anonymous: false, + type: "event", + }, + { + name: "StrategyUpdateDebtRatio", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "debtRatio", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyUpdateMinDebtPerHarvest", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "minDebtPerHarvest", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyUpdateMaxDebtPerHarvest", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "maxDebtPerHarvest", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyUpdatePerformanceFee", + inputs: [ + { name: "strategy", type: "address", indexed: true }, + { name: "performanceFee", type: "uint256", indexed: false }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyMigrated", + inputs: [ + { name: "oldVersion", type: "address", indexed: true }, + { name: "newVersion", type: "address", indexed: true }, + ], + anonymous: false, + type: "event", + }, + { + name: "StrategyRevoked", + inputs: [{ name: "strategy", type: "address", indexed: true }], + anonymous: false, + type: "event", + }, + { + name: "StrategyRemovedFromQueue", + inputs: [{ name: "strategy", type: "address", indexed: true }], + anonymous: false, + type: "event", + }, + { + name: "StrategyAddedToQueue", + inputs: [{ name: "strategy", type: "address", indexed: true }], + anonymous: false, + type: "event", + }, + { + stateMutability: "nonpayable", + type: "function", + name: "initialize", + inputs: [ + { name: "token", type: "address" }, + { name: "governance", type: "address" }, + { name: "rewards", type: "address" }, + { name: "nameOverride", type: "string" }, + { name: "symbolOverride", type: "string" }, + ], + outputs: [], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "initialize", + inputs: [ + { name: "token", type: "address" }, + { name: "governance", type: "address" }, + { name: "rewards", type: "address" }, + { name: "nameOverride", type: "string" }, + { name: "symbolOverride", type: "string" }, + { name: "guardian", type: "address" }, + ], + outputs: [], + }, + { + stateMutability: "pure", + type: "function", + name: "apiVersion", + inputs: [], + outputs: [{ name: "", type: "string" }], + gas: 4546, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setName", + inputs: [{ name: "name", type: "string" }], + outputs: [], + gas: 107044, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setSymbol", + inputs: [{ name: "symbol", type: "string" }], + outputs: [], + gas: 71894, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setGovernance", + inputs: [{ name: "governance", type: "address" }], + outputs: [], + gas: 36365, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "acceptGovernance", + inputs: [], + outputs: [], + gas: 37637, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setManagement", + inputs: [{ name: "management", type: "address" }], + outputs: [], + gas: 37775, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setGuestList", + inputs: [{ name: "guestList", type: "address" }], + outputs: [], + gas: 37805, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setRewards", + inputs: [{ name: "rewards", type: "address" }], + outputs: [], + gas: 37835, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setLockedProfitDegration", + inputs: [{ name: "degration", type: "uint256" }], + outputs: [], + gas: 36519, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setDepositLimit", + inputs: [{ name: "limit", type: "uint256" }], + outputs: [], + gas: 37795, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setPerformanceFee", + inputs: [{ name: "fee", type: "uint256" }], + outputs: [], + gas: 37929, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setManagementFee", + inputs: [{ name: "fee", type: "uint256" }], + outputs: [], + gas: 37959, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setGuardian", + inputs: [{ name: "guardian", type: "address" }], + outputs: [], + gas: 39203, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setEmergencyShutdown", + inputs: [{ name: "active", type: "bool" }], + outputs: [], + gas: 39274, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "setWithdrawalQueue", + inputs: [{ name: "queue", type: "address[20]" }], + outputs: [], + gas: 763950, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "transfer", + inputs: [ + { name: "receiver", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 76768, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "transferFrom", + inputs: [ + { name: "sender", type: "address" }, + { name: "receiver", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 116531, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "approve", + inputs: [ + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 38271, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "increaseAllowance", + inputs: [ + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 40312, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "decreaseAllowance", + inputs: [ + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 40336, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "permit", + inputs: [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" }, + { name: "expiry", type: "uint256" }, + { name: "signature", type: "bytes" }, + ], + outputs: [{ name: "", type: "bool" }], + gas: 81264, + }, + { + stateMutability: "view", + type: "function", + name: "totalAssets", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 4098, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "deposit", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "deposit", + inputs: [{ name: "_amount", type: "uint256" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "deposit", + inputs: [ + { name: "_amount", type: "uint256" }, + { name: "recipient", type: "address" }, + ], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "maxAvailableShares", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 383839, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "withdraw", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "withdraw", + inputs: [{ name: "maxShares", type: "uint256" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "withdraw", + inputs: [ + { name: "maxShares", type: "uint256" }, + { name: "recipient", type: "address" }, + ], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "withdraw", + inputs: [ + { name: "maxShares", type: "uint256" }, + { name: "recipient", type: "address" }, + { name: "maxLoss", type: "uint256" }, + ], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "pricePerShare", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 18195, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "addStrategy", + inputs: [ + { name: "strategy", type: "address" }, + { name: "debtRatio", type: "uint256" }, + { name: "minDebtPerHarvest", type: "uint256" }, + { name: "maxDebtPerHarvest", type: "uint256" }, + { name: "performanceFee", type: "uint256" }, + ], + outputs: [], + gas: 1485796, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "updateStrategyDebtRatio", + inputs: [ + { name: "strategy", type: "address" }, + { name: "debtRatio", type: "uint256" }, + ], + outputs: [], + gas: 115193, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "updateStrategyMinDebtPerHarvest", + inputs: [ + { name: "strategy", type: "address" }, + { name: "minDebtPerHarvest", type: "uint256" }, + ], + outputs: [], + gas: 42441, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "updateStrategyMaxDebtPerHarvest", + inputs: [ + { name: "strategy", type: "address" }, + { name: "maxDebtPerHarvest", type: "uint256" }, + ], + outputs: [], + gas: 42471, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "updateStrategyPerformanceFee", + inputs: [ + { name: "strategy", type: "address" }, + { name: "performanceFee", type: "uint256" }, + ], + outputs: [], + gas: 41251, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "migrateStrategy", + inputs: [ + { name: "oldVersion", type: "address" }, + { name: "newVersion", type: "address" }, + ], + outputs: [], + gas: 1141468, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "revokeStrategy", + inputs: [], + outputs: [], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "revokeStrategy", + inputs: [{ name: "strategy", type: "address" }], + outputs: [], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "addStrategyToQueue", + inputs: [{ name: "strategy", type: "address" }], + outputs: [], + gas: 1199804, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "removeStrategyFromQueue", + inputs: [{ name: "strategy", type: "address" }], + outputs: [], + gas: 23088703, + }, + { + stateMutability: "view", + type: "function", + name: "debtOutstanding", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "debtOutstanding", + inputs: [{ name: "strategy", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "creditAvailable", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "creditAvailable", + inputs: [{ name: "strategy", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "availableDepositLimit", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 9551, + }, + { + stateMutability: "view", + type: "function", + name: "expectedReturn", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "view", + type: "function", + name: "expectedReturn", + inputs: [{ name: "strategy", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "report", + inputs: [ + { name: "gain", type: "uint256" }, + { name: "loss", type: "uint256" }, + { name: "_debtPayment", type: "uint256" }, + ], + outputs: [{ name: "", type: "uint256" }], + gas: 1015170, + }, + { + stateMutability: "nonpayable", + type: "function", + name: "sweep", + inputs: [{ name: "token", type: "address" }], + outputs: [], + }, + { + stateMutability: "nonpayable", + type: "function", + name: "sweep", + inputs: [ + { name: "token", type: "address" }, + { name: "amount", type: "uint256" }, + ], + outputs: [], + }, + { + stateMutability: "view", + type: "function", + name: "name", + inputs: [], + outputs: [{ name: "", type: "string" }], + gas: 8750, + }, + { + stateMutability: "view", + type: "function", + name: "symbol", + inputs: [], + outputs: [{ name: "", type: "string" }], + gas: 7803, + }, + { + stateMutability: "view", + type: "function", + name: "decimals", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2408, + }, + { + stateMutability: "view", + type: "function", + name: "precisionFactor", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2438, + }, + { + stateMutability: "view", + type: "function", + name: "balanceOf", + inputs: [{ name: "arg0", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + gas: 2683, + }, + { + stateMutability: "view", + type: "function", + name: "allowance", + inputs: [ + { name: "arg0", type: "address" }, + { name: "arg1", type: "address" }, + ], + outputs: [{ name: "", type: "uint256" }], + gas: 2928, + }, + { + stateMutability: "view", + type: "function", + name: "totalSupply", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2528, + }, + { + stateMutability: "view", + type: "function", + name: "token", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2558, + }, + { + stateMutability: "view", + type: "function", + name: "governance", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2588, + }, + { + stateMutability: "view", + type: "function", + name: "management", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2618, + }, + { + stateMutability: "view", + type: "function", + name: "guardian", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2648, + }, + { + stateMutability: "view", + type: "function", + name: "guestList", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 2678, + }, + { + stateMutability: "view", + type: "function", + name: "strategies", + inputs: [{ name: "arg0", type: "address" }], + outputs: [ + { name: "performanceFee", type: "uint256" }, + { name: "activation", type: "uint256" }, + { name: "debtRatio", type: "uint256" }, + { name: "minDebtPerHarvest", type: "uint256" }, + { name: "maxDebtPerHarvest", type: "uint256" }, + { name: "lastReport", type: "uint256" }, + { name: "totalDebt", type: "uint256" }, + { name: "totalGain", type: "uint256" }, + { name: "totalLoss", type: "uint256" }, + ], + gas: 11031, + }, + { + stateMutability: "view", + type: "function", + name: "withdrawalQueue", + inputs: [{ name: "arg0", type: "uint256" }], + outputs: [{ name: "", type: "address" }], + gas: 2847, + }, + { + stateMutability: "view", + type: "function", + name: "emergencyShutdown", + inputs: [], + outputs: [{ name: "", type: "bool" }], + gas: 2768, + }, + { + stateMutability: "view", + type: "function", + name: "depositLimit", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2798, + }, + { + stateMutability: "view", + type: "function", + name: "debtRatio", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2828, + }, + { + stateMutability: "view", + type: "function", + name: "totalDebt", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2858, + }, + { + stateMutability: "view", + type: "function", + name: "lastReport", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2888, + }, + { + stateMutability: "view", + type: "function", + name: "activation", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2918, + }, + { + stateMutability: "view", + type: "function", + name: "lockedProfit", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2948, + }, + { + stateMutability: "view", + type: "function", + name: "lockedProfitDegration", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 2978, + }, + { + stateMutability: "view", + type: "function", + name: "rewards", + inputs: [], + outputs: [{ name: "", type: "address" }], + gas: 3008, + }, + { + stateMutability: "view", + type: "function", + name: "managementFee", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 3038, + }, + { + stateMutability: "view", + type: "function", + name: "performanceFee", + inputs: [], + outputs: [{ name: "", type: "uint256" }], + gas: 3068, + }, + { + stateMutability: "view", + type: "function", + name: "nonces", + inputs: [{ name: "arg0", type: "address" }], + outputs: [{ name: "", type: "uint256" }], + gas: 3313, + }, + { + stateMutability: "view", + type: "function", + name: "DOMAIN_SEPARATOR", + inputs: [], + outputs: [{ name: "", type: "bytes32" }], + gas: 3128, + }, +] + +export default YEARN_VAULT_ABI From a9e18ae607e00e15d07fbd37eb1a667d7a3439b3 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Thu, 24 Mar 2022 14:13:28 +0100 Subject: [PATCH 15/36] Fetch hardcoded token values on mainnet fork --- background/services/chain/asset-data-helper.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/background/services/chain/asset-data-helper.ts b/background/services/chain/asset-data-helper.ts index 39820424d7..b6289e3874 100644 --- a/background/services/chain/asset-data-helper.ts +++ b/background/services/chain/asset-data-helper.ts @@ -69,15 +69,16 @@ export default class AssetDataHelper { // Load balances of tokens on the mainnet fork if (USE_MAINNET_FORK) { - const signer = provider.getSigner() - const signerAddress = await signer.getAddress() const tokens = [ "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT - "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", - "0x514910771AF9Ca656af840dff83E8264EcF986CA", + "0x7EE7c993FBAD3AAFf795973ee14ff2034311a966", // DOGGO ] const balances = tokens.map(async (token) => { - const balance = await getBalance(provider, token, signerAddress) + const balance = await getBalance( + provider, + token, + addressOnNetwork.address + ) return { smartContract: { contractAddress: token, From 84e903c3ef2391da62c41af688cdf3cd96e29680 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Thu, 24 Mar 2022 14:17:38 +0100 Subject: [PATCH 16/36] Fetch data from redux and contracts and display --- ui/pages/Earn.tsx | 75 +++++++++++++++++++------- ui/pages/EarnDeposit.tsx | 110 ++++++++++++++++++++++++--------------- 2 files changed, 125 insertions(+), 60 deletions(-) diff --git a/ui/pages/Earn.tsx b/ui/pages/Earn.tsx index 4d8a630482..1d78747c27 100644 --- a/ui/pages/Earn.tsx +++ b/ui/pages/Earn.tsx @@ -1,11 +1,15 @@ import { AvailableVault, selectAvailableVaults, - selectLockedValues, - selectTotalLockedValue, - updateEarnedOnDepositedPools, + updateEarnedValues, updateLockedValues, } from "@tallyho/tally-background/redux-slices/earn" +import { + getAssetsState, + selectMainCurrencySymbol, +} from "@tallyho/tally-background/redux-slices/selectors" +import { selectAssetPricePoint } from "@tallyho/tally-background/redux-slices/assets" +import { enrichAssetAmountWithMainCurrencyValues } from "@tallyho/tally-background/redux-slices/utils/asset-utils" import React, { ReactElement, useEffect, useState } from "react" import { Link } from "react-router-dom" @@ -16,16 +20,14 @@ import EarnDepositedCard from "../components/Earn/EarnDepositedCard" import { useBackgroundDispatch, useBackgroundSelector } from "../hooks" type EarnCardProps = { - vault: AvailableVault + vault: AvailableVault & { + localValueTotalDeposited: string | undefined + localValueUserDeposited: string | undefined + } isComingSoon: boolean } function EarnCard({ vault, isComingSoon }: EarnCardProps) { - const lockedValues = useBackgroundSelector(selectLockedValues) - const currentVault = lockedValues.find( - (lockedValue) => lockedValue.vaultAddress === vault?.contractAddress - ) - return (
    - +
    - {vault?.symbol} + {vault?.asset?.symbol} Estimated APR 250%
    TVL
    -
    ${Number(currentVault?.lockedValue)}
    +
    ${vault.localValueTotalDeposited ?? 0}
    @@ -164,15 +166,52 @@ export default function Earn(): ReactElement { const [panelNumber, setPanelNumber] = useState(0) const dispatch = useBackgroundDispatch() + const availableVaults = useBackgroundSelector(selectAvailableVaults) - const totalTVL = useBackgroundSelector(selectTotalLockedValue) + + const isComingSoon = false useEffect(() => { - dispatch(updateEarnedOnDepositedPools()) dispatch(updateLockedValues()) + dispatch(updateEarnedValues()) }, [dispatch]) - const isComingSoon = true + const assets = useBackgroundSelector(getAssetsState) + const mainCurrencySymbol = useBackgroundSelector(selectMainCurrencySymbol) + + const vaultsWithMainCurrencyValues = availableVaults.map((vault) => { + const assetPricePoint = selectAssetPricePoint( + assets, + vault.asset.symbol, + mainCurrencySymbol + ) + const userTVL = enrichAssetAmountWithMainCurrencyValues( + { amount: vault.userDeposited, asset: vault.asset }, + assetPricePoint, + 2 + ) + const totalTVL = enrichAssetAmountWithMainCurrencyValues( + { amount: vault.totalDeposited, asset: vault.asset }, + assetPricePoint, + 2 + ) + + return { + ...vault, + localValueUserDeposited: userTVL.localizedMainCurrencyAmount, + localValueTotalDeposited: totalTVL.localizedMainCurrencyAmount, + numberValueUserDeposited: userTVL.mainCurrencyAmount, + numberValueTotalDeposited: totalTVL.mainCurrencyAmount, + } + }) + + const totalTVL = vaultsWithMainCurrencyValues + .map((item) => { + return typeof item.numberValueTotalDeposited !== "undefined" + ? item.numberValueTotalDeposited + : 0 + }) + .reduce((prev, curr) => prev + curr, 0) return ( <> @@ -198,7 +237,7 @@ export default function Earn(): ReactElement {
    Total value locked
    $ - {totalTVL || "0"} + {totalTVL}
    @@ -212,7 +251,7 @@ export default function Earn(): ReactElement { {panelNumber === 0 ? (
    - {deposited ? ( + {deposited || pendingRewards > 0 ? (
  • Deposited amount
    - 27,834 {vault.symbol} + {vault.localValueUserDeposited} + {vault?.asset.symbol}
  • Available rewards
    - {availableRewards} TALLY + {pendingRewards} + TALLY
  • @@ -222,9 +246,9 @@ export default function EarnDeposit(): ReactElement { handleAmountChange(value, errorMessage) } selectedAsset={{ - name: vault.name, - symbol: vault.symbol, - contractAddress: vault.wantToken, + name: vault.asset.name, + symbol: vault.asset.symbol, + contractAddress: vault.asset.contractAddress, }} amount={amount} disableDropdown @@ -296,14 +320,15 @@ export default function EarnDeposit(): ReactElement {
  • Deposited amount
    - 27,834 Curve ibGBP + {vault.localValueUserDeposited} + {vault.asset.symbol}
  • Available rewards
    - {availableRewards} TALLY + {pendingRewards} TALLY
  • @@ -390,6 +415,7 @@ export default function EarnDeposit(): ReactElement { background-color: var(--trophy-gold); } .token { + margin-left: 8px; font-size: 14px; } .divider { From a0c7a2cc8847f0fe9bc53f87430e2ccb25d3e185 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Thu, 24 Mar 2022 14:18:06 +0100 Subject: [PATCH 17/36] Earn contract interactions This is an early approach to fetching and updating data in the contracts, it may or rather should be improved in the near future. --- background/redux-slices/earn.ts | 440 +++++++++++++------------------- 1 file changed, 176 insertions(+), 264 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 3792d9ef16..9b0dac87ce 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -1,19 +1,17 @@ import { TransactionResponse } from "@ethersproject/abstract-provider" import { createSlice, createSelector } from "@reduxjs/toolkit" import { BigNumber, ethers } from "ethers" +import { parseUnits } from "ethers/lib/utils" import { AnyAsset } from "../assets" -import { HOUR, doggoTokenDecimalDigits } from "../constants" import { USE_MAINNET_FORK } from "../features/features" -import { ERC20_ABI, ERC2612_INTERFACE } from "../lib/erc20" -import { fromFixedPointNumber, toFixedPoint } from "../lib/fixed-point" -import { normalizeEVMAddress } from "../lib/utils" +import { ERC20_ABI } from "../lib/erc20" +import { fromFixedPointNumber } from "../lib/fixed-point" import VAULT_ABI from "../lib/vault" +import APPROVAL_TARGET_ABI from "../lib/approvalTarget" import { EIP712TypedData, HexString } from "../types" import { createBackgroundAsyncThunk } from "./utils" import { getContract, - getCurrentTimestamp, - getNonce, getProvider, getSignerAddress, } from "./utils/contract-utils" @@ -23,36 +21,26 @@ export type ApprovalTargetAllowance = { allowance: number } -export type PendingReward = { - vaultAddress: HexString - pendingAmount: number -} - -export type DepositedVault = { - vaultAddress: HexString - depositedAmount: BigInt -} - export type LockedValue = { vaultAddress: HexString lockedValue: bigint vaultTokenSymbol: string - asset: AnyAsset + asset: AnyAsset & { contractAddress: string } } export type AvailableVault = { - name: string - symbol: string - contractAddress: HexString - wantToken: HexString + vaultAddress: HexString active: boolean + userDeposited: bigint + totalDeposited: bigint + yearnVault: HexString + asset: AnyAsset & { contractAddress: string; decimals: number } + pendingRewards: bigint } export type EarnState = { signature: Signature approvalTargetAllowances: ApprovalTargetAllowance[] - depositedVaults: DepositedVault[] - pendingRewards: PendingReward[] lockedAmounts: LockedValue[] availableVaults: AvailableVault[] currentlyDepositing: boolean @@ -74,31 +62,21 @@ export const initialState: EarnState = { v: undefined, }, approvalTargetAllowances: [], - depositedVaults: [], - pendingRewards: [], lockedAmounts: [], availableVaults: [ { - name: "WBTC", - symbol: "WBTC", - contractAddress: "0x5D1aB585B7d05d81F07E8Ff33998f2f11647B46e", - wantToken: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + asset: { + name: "USDT", + symbol: "USDT", + contractAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + decimals: 6, + }, + vaultAddress: "0xBfAF0E71db32EdE62BB4D43b362a9d3E5F19a9a2", + yearnVault: "0x7Da96a3891Add058AdA2E826306D812C638D87a7", + userDeposited: 0n, + totalDeposited: 0n, + pendingRewards: 0n, active: true, - } as AnyAsset & { - contractAddress: HexString - active: boolean - wantToken: HexString - }, - { - name: "USDT", - symbol: "USDT", - contractAddress: "0x362Db3b1F85154a537CEf5e2a3D87D56d71DF823", - active: true, - wantToken: "0xdAC17F958D2ee523a2206206994597C13D831ec7", - } as AnyAsset & { - contractAddress: HexString - active: boolean - wantToken: HexString }, ], currentlyDepositing: false, @@ -129,50 +107,7 @@ export type SignTypedDataRequest = { } const APPROVAL_TARGET_CONTRACT_ADDRESS = - "0x9a7E392264500e46AAe5277C99d3CD381269cb9B" - -const DOGGO_TOKEN_CONTRACT = "0x2eD9D339899CD5f1E4a3B131F467E76549E8Eab0" - -export const vaultWithdraw = createBackgroundAsyncThunk( - "earn/vaultWithdraw", - async ({ - vaultContractAddress, - amount, - }: { - vaultContractAddress: HexString - amount: BigNumber - }) => { - const provider = getProvider() - const signer = provider.getSigner() - - const vaultContract = await getContract(vaultContractAddress, VAULT_ABI) - - const signedWithdrawTransaction = await signer.signTransaction( - await vaultContract.functions["withdraw(uint256)"](amount) - ) - - provider.sendTransaction(signedWithdrawTransaction) - } -) - -export const claimVaultRewards = createBackgroundAsyncThunk( - "earn/clamRewards", - async (vaultContractAddress: HexString) => { - const provider = getProvider() - const signer = provider.getSigner() - - const vaultContract = new ethers.Contract( - vaultContractAddress, - VAULT_ABI, - signer - ) - const signedGetRewardsTx = await signer.signTransaction( - await vaultContract.functions.getReward() - ) - - provider.sendTransaction(signedGetRewardsTx) - } -) + "0x35613d0bF1a18e8E95181322cd5C60B9dD7Af2A8" const earnSlice = createSlice({ name: "earn", @@ -201,36 +136,41 @@ const earnSlice = createSlice({ inputAmount: payload, } }, - deposited: ( - immerState, - { - payload, - }: { payload: { vaultAddress: HexString; depositedAmount: BigInt } } + earnedOnVault: ( + state, + { payload }: { payload: { vault: HexString; amount: bigint } } ) => { - immerState.depositedVaults.push(payload) - }, - earnedAmounts: (state, { payload }: { payload: PendingReward[] }) => { return { ...state, - pendingRewards: payload, - } - }, - lockedAmounts: (state, { payload }: { payload: LockedValue[] }) => { - return { - ...state, - lockedAmounts: payload, + availableVaults: state.availableVaults.map((availableVault) => + availableVault.vaultAddress === payload.vault + ? { ...availableVault, pendingRewards: payload.amount } + : availableVault + ), } }, - withdrawn: ( + lockedAmounts: ( state, - { payload }: { payload: { vault: HexString; depositedAmount: BigInt } } + { + payload, + }: { + payload: { + vault: AvailableVault + userLockedValue: bigint + totalTVL: bigint + } + } ) => { return { ...state, - depositedVaults: state.depositedVaults.map((vault) => - vault.vaultAddress === payload.vault - ? { ...vault, depositedAmount: payload.depositedAmount } - : vault + availableVaults: state.availableVaults.map((availableVault) => + availableVault.vaultAddress === payload.vault.vaultAddress + ? { + ...availableVault, + userDeposited: payload.userLockedValue, + totalDeposited: payload.totalTVL, + } + : availableVault ), } }, @@ -260,10 +200,8 @@ export const { saveAllowance, currentlyDepositing, currentlyApproving, - deposited, - withdrawn, + earnedOnVault, depositError, - earnedAmounts, lockedAmounts, inputAmount, clearSignature, @@ -271,15 +209,63 @@ export const { export default earnSlice.reducer +export const updateLockedValues = createBackgroundAsyncThunk( + "earn/updateLockedValues", + async (_, { getState, dispatch }) => { + const currentState = getState() + const { earn } = currentState as { earn: EarnState } + const { availableVaults } = earn + const provider = getProvider() + const signer = provider.getSigner() + const account = signer.getAddress() + + availableVaults.map(async (vault) => { + const vaultContract = await getContract(vault.vaultAddress, VAULT_ABI) + const userLockedValue: BigNumber = await vaultContract.balanceOf(account) + const yearnVaultContract = await getContract(vault.yearnVault, VAULT_ABI) + const totalTVL: BigNumber = await yearnVaultContract.balanceOf( + vault.vaultAddress + ) + dispatch( + lockedAmounts({ + vault, + userLockedValue: userLockedValue.toBigInt(), + totalTVL: totalTVL.toBigInt(), + }) + ) + return { + ...vault, + userDeposited: userLockedValue.toBigInt(), + totalDeposited: totalTVL.toBigInt(), + } + }) + } +) + +export const vaultWithdraw = createBackgroundAsyncThunk( + "earn/vaultWithdraw", + async ({ vault }: { vault: AvailableVault }, { dispatch }) => { + const vaultContract = await getContract(vault.vaultAddress, VAULT_ABI) + + // TODO Support partial withdrawal + // const withdrawAmount = parseUnits(amount, vault.asset.decimals) + + const tx = await vaultContract.functions["withdraw()"]() + const receipt = await tx.wait() + if (receipt.status === 1) { + dispatch(updateLockedValues()) + } + } +) + export const vaultDeposit = createBackgroundAsyncThunk( "signing/vaultDeposit", async ( { - vaultContractAddress, + vault, amount, - tokenAddress, }: { - vaultContractAddress: HexString + vault: AvailableVault amount: string tokenAddress: HexString }, @@ -292,107 +278,75 @@ export const vaultDeposit = createBackgroundAsyncThunk( const state = getState() const { earn } = state as { earn: EarnState } - const vaultContract = await getContract(vaultContractAddress, VAULT_ABI) + const { signature } = earn - const timestamp = await getCurrentTimestamp() + const { vaultAddress } = vault - const TokenContract = await getContract(tokenAddress, ERC2612_INTERFACE) + const depositAmount = parseUnits(amount, vault.asset.decimals) - const decimals: BigNumber = await TokenContract.decimals() - // ! cleanup - const signatureDeadline = BigInt(timestamp) + BigInt(12) * BigInt(HOUR) - - const amountPermitted = await TokenContract.allowance( - signerAddress, - APPROVAL_TARGET_CONTRACT_ADDRESS - ) + const vaultContract = await getContract(vaultAddress, VAULT_ABI) const depositTransactionData = await vaultContract.populateTransaction.depositWithApprovalTarget( - toFixedPoint(Number(amount), decimals.toNumber()), + depositAmount, signerAddress, signerAddress, - amountPermitted, - signatureDeadline, - earn.signature.v, - earn.signature.r, - earn.signature.s + depositAmount, + ethers.BigNumber.from(1690792895n), // TODO not sure how to handle, remove hardcode + signature.v, + signature.r, + signature.s ) if (USE_MAINNET_FORK) { - depositTransactionData.gasLimit = BigNumber.from(350000) // for mainnet fork only + depositTransactionData.gasLimit = BigNumber.from(850000) // for mainnet fork only } - const response = signer.sendTransaction(depositTransactionData) - const result = await response - const receipt = await result.wait() + const response = await signer.sendTransaction(depositTransactionData) + const receipt = await response.wait() if (receipt.status === 1) { dispatch(currentlyDepositing(false)) - dispatch( - deposited({ - vaultAddress: normalizeEVMAddress(vaultContractAddress), - depositedAmount: toFixedPoint(Number(amount), decimals.toNumber()), - }) - ) + dispatch(clearSignature()) + dispatch(updateLockedValues()) } + dispatch(currentlyDepositing(false)) dispatch(dispatch(depositError(true))) } ) -export const updateEarnedOnDepositedPools = createBackgroundAsyncThunk( +export const updateEarnedValues = createBackgroundAsyncThunk( "earn/updateEarnedOnDepositedPools", async (_, { getState, dispatch }) => { const currentState = getState() const { earn } = currentState as { earn: EarnState } - const { depositedVaults } = earn + const { availableVaults } = earn const provider = getProvider() const signer = provider.getSigner() const account = signer.getAddress() - - const pendingAmounts = depositedVaults.map(async (vault) => { + availableVaults.forEach(async (vault) => { const vaultContract = await getContract(vault.vaultAddress, VAULT_ABI) const earned: BigNumber = await vaultContract.earned(account) - return { - vaultAddress: vault.vaultAddress, - pendingAmount: fromFixedPointNumber( - { amount: earned.toBigInt(), decimals: doggoTokenDecimalDigits }, - 0 - ), - } + dispatch( + earnedOnVault({ vault: vault.vaultAddress, amount: earned.toBigInt() }) + ) }) - - const amounts = await Promise.all(pendingAmounts) - dispatch(earnedAmounts(amounts)) - return amounts } ) -export const updateLockedValues = createBackgroundAsyncThunk( - "earn/updateLockedValues", - async (_, { getState, dispatch }) => { - const currentState = getState() - const { earn } = currentState as { earn: EarnState } - const { availableVaults } = earn +export const claimVaultRewards = createBackgroundAsyncThunk( + "earn/clamRewards", + async (vaultContractAddress: HexString, { dispatch }) => { + const provider = getProvider() + const signer = provider.getSigner() - const locked = availableVaults.map(async (vault) => { - const wantTokenContract = await getContract(vault.wantToken, ERC20_ABI) - const lockedValue: BigNumber = await wantTokenContract.balanceOf( - vault.contractAddress - ) - return { - vaultAddress: vault.contractAddress, - vaultTokenSymbol: vault.symbol, - lockedValue: lockedValue.toBigInt(), - asset: { - contractAddress: vault.wantToken, - name: vault.name, - symbol: vault.symbol, - } as AnyAsset, - } - }) - // TODO Convert below to be showing $ value rather than BigNumber - const amounts = await Promise.all(locked) - dispatch(lockedAmounts(amounts)) - return amounts + const vaultContract = new ethers.Contract( + vaultContractAddress, + VAULT_ABI, + signer + ) + const tx = await vaultContract.functions["getReward()"]() + const response = signer.sendTransaction(tx) + await tx.wait(response) + dispatch(updateEarnedValues()) } ) @@ -419,15 +373,7 @@ export const approveApprovalTarget = createBackgroundAsyncThunk( } const tx = await signer.sendTransaction(approvalTransactionData) await tx.wait() - const { r, s, v } = tx - if ( - typeof r !== "undefined" && - typeof v !== "undefined" && - typeof s !== "undefined" - ) { - dispatch(earnSlice.actions.saveSignature({ r, s, v })) - dispatch(currentlyApproving(false)) - } + dispatch(currentlyApproving(false)) return tx } catch (error) { dispatch(currentlyApproving(false)) @@ -438,43 +384,25 @@ export const approveApprovalTarget = createBackgroundAsyncThunk( export const checkApprovalTargetApproval = createBackgroundAsyncThunk( "earn/checkApprovalTargetApproval", - async (tokenContractAddress: HexString, { getState, dispatch }) => { - const currentState = getState() - const { earn } = currentState as { earn: EarnState } + async (tokenContractAddress: HexString, { dispatch }) => { const assetContract = await getContract(tokenContractAddress, ERC20_ABI) const signerAddress = await getSignerAddress() - - const knownAllowanceIndex = earn.approvalTargetAllowances.findIndex( - (allowance: ApprovalTargetAllowance) => - allowance.contractAddress === tokenContractAddress - ) - if (knownAllowanceIndex === -1) { - try { - const allowance: BigNumber = await assetContract.allowance( - signerAddress, - APPROVAL_TARGET_CONTRACT_ADDRESS - ) - const amount = fromFixedPointNumber( - { amount: allowance.toBigInt(), decimals: 18 }, - 2 - ) - dispatch( - earnSlice.actions.saveAllowance({ - contractAddress: tokenContractAddress, - allowance: amount, - }) - ) - return { - contractAddress: tokenContractAddress, - allowance: amount, - } as ApprovalTargetAllowance - } catch (err) { - return undefined - } + try { + const allowance: BigNumber = await assetContract.allowance( + signerAddress, + APPROVAL_TARGET_CONTRACT_ADDRESS + ) + const amount = fromFixedPointNumber( + { amount: allowance.toBigInt(), decimals: 18 }, + 2 + ) + return { + contractAddress: tokenContractAddress, + allowance: amount, + } as ApprovalTargetAllowance + } catch (err) { + return undefined } - return earn.approvalTargetAllowances.find( - (_, i) => i === knownAllowanceIndex - ) } ) @@ -482,11 +410,11 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( "earn/permitVaultDeposit", async ( { - vaultContractAddress, + vault, amount, tokenAddress, }: { - vaultContractAddress: HexString + vault: AvailableVault amount: string tokenAddress: HexString }, @@ -497,50 +425,36 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const signerAddress = await getSignerAddress() const chainID = await signer.getChainId() - const timestamp = await getCurrentTimestamp() - const signatureDeadline = timestamp + 12 * HOUR - - const TokenContract = await getContract(tokenAddress, ERC2612_INTERFACE) - const nonceValue = await TokenContract.nonces(signerAddress) - - const decimals: BigNumber = await TokenContract.decimals() + const depositAmount = parseUnits(amount, vault.asset.decimals) + const ApprovalTargetContract = await getContract( + APPROVAL_TARGET_CONTRACT_ADDRESS, + APPROVAL_TARGET_ABI + ) + const nonceValue = await ApprovalTargetContract.nonces(signerAddress) const types = { - Message: [ - { - name: "owner", - type: "address", - }, - { - name: "spender", - type: "address", - }, - { - name: "value", - type: "uint256", - }, - { - name: "nonce", - type: "uint256", - }, - { - name: "deadline", - type: "uint256", - }, + PermitAndTransferFrom: [ + { name: "erc20", type: "address" }, + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, ], } const domain = { - name: "Spend assets with ApprovalTarget", + name: "ApprovalTarget", + chainId: USE_MAINNET_FORK ? 1337 : chainID, version: "1", - verifyingContract: vaultContractAddress, - chainId: chainID, + verifyingContract: APPROVAL_TARGET_CONTRACT_ADDRESS, } const message = { + erc20: tokenAddress, owner: signerAddress, - spender: vaultContractAddress, - value: toFixedPoint(Number(amount), decimals.toNumber()), + spender: vault.vaultAddress, + value: depositAmount, nonce: nonceValue, - deadline: signatureDeadline, + deadline: ethers.BigNumber.from(1690792895n), // TODO not sure how to handle, remove hardcode } // _signTypedData is the ethers function name, once the official release will be ready _ will be dropped @@ -578,13 +492,11 @@ export const selectAvailableVaults = createSelector( (earnState: EarnState) => earnState.availableVaults ) -// TODO This should include a maincurrency value for each element in the array export const selectLockedValues = createSelector( (state: { earn: EarnState }): EarnState => state.earn, (earnState: EarnState) => earnState.lockedAmounts ) -// TODO This should return a maincurrency value export const selectTotalLockedValue = createSelector( (state: { earn: EarnState }): EarnState => state.earn, (earnState: EarnState) => From 8018c71529da9d88d43615d532bba3ae842b8d05 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Thu, 24 Mar 2022 16:00:05 +0100 Subject: [PATCH 18/36] Remove remnants of the past aka ui merkle tree --- background/lib/balance-tree.ts | 60 ---------------- background/lib/merkle-tree.ts | 128 --------------------------------- 2 files changed, 188 deletions(-) delete mode 100644 background/lib/balance-tree.ts delete mode 100644 background/lib/merkle-tree.ts diff --git a/background/lib/balance-tree.ts b/background/lib/balance-tree.ts deleted file mode 100644 index 5319e6b064..0000000000 --- a/background/lib/balance-tree.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { BigNumber, utils } from "ethers" -import MerkleTree from "./merkle-tree" - -export default class BalanceTree { - private readonly tree: MerkleTree - - constructor(balances: { account: string; amount: BigNumber }[]) { - this.tree = new MerkleTree( - balances.map(({ account, amount }, index) => { - return BalanceTree.toNode(index, account, amount) - }) - ) - } - - public static verifyProof( - index: number | BigNumber, - account: string, - amount: BigNumber, - proof: Buffer[], - root: Buffer - ): boolean { - let pair = BalanceTree.toNode(index, account, amount) - // eslint-disable-next-line no-restricted-syntax - for (const item of proof) { - pair = MerkleTree.combinedHash(pair, item) - } - - return pair.equals(root) - } - - // keccak256(abi.encode(index, account, amount)) - public static toNode( - index: number | BigNumber, - account: string, - amount: BigNumber - ): Buffer { - return Buffer.from( - utils - .solidityKeccak256( - ["uint256", "address", "uint256"], - [index, account, amount] - ) - .substr(2), - "hex" - ) - } - - public getHexRoot(): string { - return this.tree.getHexRoot() - } - - // returns the hex bytes32 values of the proof - public getProof( - index: number | BigNumber, - account: string, - amount: BigNumber - ): string[] { - return this.tree.getHexProof(BalanceTree.toNode(index, account, amount)) - } -} diff --git a/background/lib/merkle-tree.ts b/background/lib/merkle-tree.ts deleted file mode 100644 index 1baa7039fa..0000000000 --- a/background/lib/merkle-tree.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { bufferToHex, keccak256 } from "ethereumjs-util" - -export default class MerkleTree { - private readonly elements: Buffer[] - - private readonly bufferElementPositionIndex: { [hexElement: string]: number } - - private readonly layers: Buffer[][] - - constructor(elements: Buffer[]) { - this.elements = [...elements] - // Sort elements - this.elements.sort(Buffer.compare) - // Deduplicate elements - this.elements = MerkleTree.bufDedup(this.elements) - - this.bufferElementPositionIndex = this.elements.reduce<{ - [hexElement: string]: number - }>((memo, el, index) => { - // eslint-disable-next-line no-param-reassign - memo[bufferToHex(el)] = index - return memo - }, {}) - - // Create layers - this.layers = this.getLayers(this.elements) - } - - getLayers(elements: Buffer[]): Buffer[][] { - if (elements.length === 0) { - throw new Error("empty tree") - } - - const layers = [] - layers.push(elements) - - // Get next layer until we reach the root - while (layers[layers.length - 1].length > 1) { - layers.push(this.getNextLayer(layers[layers.length - 1])) - } - - return layers - } - - // eslint-disable-next-line class-methods-use-this - getNextLayer(elements: Buffer[]): Buffer[] { - return elements.reduce((layer, el, idx, arr) => { - if (idx % 2 === 0) { - // Hash the current element with its pair element - layer.push(MerkleTree.combinedHash(el, arr[idx + 1])) - } - - return layer - }, []) - } - - static combinedHash(first: Buffer, second: Buffer): Buffer { - if (!first) { - return second - } - if (!second) { - return first - } - - return keccak256(MerkleTree.sortAndConcat(first, second)) - } - - getRoot(): Buffer { - return this.layers[this.layers.length - 1][0] - } - - getHexRoot(): string { - return bufferToHex(this.getRoot()) - } - - getProof(el: Buffer) { - let idx = this.bufferElementPositionIndex[bufferToHex(el)] - - if (typeof idx !== "number") { - throw new Error("Element does not exist in Merkle tree") - } - - return this.layers.reduce((proof, layer) => { - const pairElement = MerkleTree.getPairElement(idx, layer) - - if (pairElement) { - proof.push(pairElement) - } - - idx = Math.floor(idx / 2) - - return proof - }, []) - } - - getHexProof(el: Buffer): string[] { - const proof = this.getProof(el) - - return MerkleTree.bufArrToHexArr(proof) - } - - private static getPairElement(idx: number, layer: Buffer[]): Buffer | null { - const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1 - - if (pairIdx < layer.length) { - return layer[pairIdx] - } - return null - } - - private static bufDedup(elements: Buffer[]): Buffer[] { - return elements.filter((el, idx) => { - return idx === 0 || !elements[idx - 1].equals(el) - }) - } - - private static bufArrToHexArr(arr: Buffer[]): string[] { - if (arr.some((el) => !Buffer.isBuffer(el))) { - throw new Error("Array is not an array of buffers") - } - - return arr.map((el) => `0x${el.toString("hex")}`) - } - - private static sortAndConcat(...args: Buffer[]): Buffer { - return Buffer.concat([...args].sort(Buffer.compare)) - } -} From 086f93c7dbc6fa83e57764c5829520ffb983339f Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Thu, 24 Mar 2022 16:14:58 +0100 Subject: [PATCH 19/36] Update contract addresses and rm double redirect --- background/redux-slices/earn.ts | 4 ++-- background/services/chain/asset-data-helper.ts | 2 +- ui/pages/EarnDeposit.tsx | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 9b0dac87ce..9853055146 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -71,7 +71,7 @@ export const initialState: EarnState = { contractAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7", decimals: 6, }, - vaultAddress: "0xBfAF0E71db32EdE62BB4D43b362a9d3E5F19a9a2", + vaultAddress: "0x6575a8E8Ca0FD1Fb974419AE1f9128cCb1055209", yearnVault: "0x7Da96a3891Add058AdA2E826306D812C638D87a7", userDeposited: 0n, totalDeposited: 0n, @@ -107,7 +107,7 @@ export type SignTypedDataRequest = { } const APPROVAL_TARGET_CONTRACT_ADDRESS = - "0x35613d0bF1a18e8E95181322cd5C60B9dD7Af2A8" + "0x76465982fD8070FC74c91FD4CFfC7eb56Fc6b03a" const earnSlice = createSlice({ name: "earn", diff --git a/background/services/chain/asset-data-helper.ts b/background/services/chain/asset-data-helper.ts index b6289e3874..02085cc051 100644 --- a/background/services/chain/asset-data-helper.ts +++ b/background/services/chain/asset-data-helper.ts @@ -71,7 +71,7 @@ export default class AssetDataHelper { if (USE_MAINNET_FORK) { const tokens = [ "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT - "0x7EE7c993FBAD3AAFf795973ee14ff2034311a966", // DOGGO + "0xC8B1e49A5dDE816BCde63F23e7E787086229FE62", // DOGGO ] const balances = tokens.map(async (token) => { const balance = await getBalance( diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index 8936131c3c..0983904a13 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -259,7 +259,6 @@ export default function EarnDeposit(): ReactElement { type="primary" size="large" isDisabled={hasError || amount === "" || isCurrentlyApproving} - linkTo="/sign-transaction" onClick={approve} > {approveButtonText()} From 7b1f05302c80434f20fb9598f4573b6d98a938e2 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Fri, 25 Mar 2022 19:02:53 +0100 Subject: [PATCH 20/36] Clear earn signature when switching accounts --- .../AccountsNotificationPanelAccounts.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/components/AccountsNotificationPanel/AccountsNotificationPanelAccounts.tsx b/ui/components/AccountsNotificationPanel/AccountsNotificationPanelAccounts.tsx index 5b29e032bd..ed5bdc9f2a 100644 --- a/ui/components/AccountsNotificationPanel/AccountsNotificationPanelAccounts.tsx +++ b/ui/components/AccountsNotificationPanel/AccountsNotificationPanelAccounts.tsx @@ -14,6 +14,7 @@ import { normalizeEVMAddress, sameEVMAddress, } from "@tallyho/tally-background/lib/utils" +import { clearSignature } from "@tallyho/tally-background/redux-slices/earn" import SharedButton from "../Shared/SharedButton" import { useBackgroundDispatch, @@ -156,6 +157,7 @@ export default function AccountsNotificationPanelAccounts({ useBackgroundSelector(selectCurrentAccount).address const updateCurrentAccount = (address: string) => { + dispatch(clearSignature()) setPendingSelectedAddress(address) dispatch( setNewSelectedAccount({ From c54addd60167bc310c3865095e146a3c396433ac Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Fri, 25 Mar 2022 19:03:45 +0100 Subject: [PATCH 21/36] Get new earn data on switch of an account --- ui/pages/Earn.tsx | 2 +- ui/pages/EarnDeposit.tsx | 62 ++++++++++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/ui/pages/Earn.tsx b/ui/pages/Earn.tsx index 1d78747c27..e5849d9e13 100644 --- a/ui/pages/Earn.tsx +++ b/ui/pages/Earn.tsx @@ -33,7 +33,7 @@ function EarnCard({ vault, isComingSoon }: EarnCardProps) { to={{ pathname: "/earn/deposit", state: { - vault, + vaultAddress: vault.vaultAddress, }, }} className="earn" diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index 0983904a13..2c9b26cd74 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -1,16 +1,21 @@ import React, { ReactElement, useEffect, useState } from "react" -import { selectAccountAndTimestampedActivities } from "@tallyho/tally-background/redux-slices/selectors" +import { + selectAccountAndTimestampedActivities, + selectCurrentAccount, +} from "@tallyho/tally-background/redux-slices/selectors" import { ApprovalTargetAllowance, approveApprovalTarget, - AvailableVault, checkApprovalTargetApproval, claimVaultRewards, inputAmount, permitVaultDeposit, selectCurrentlyApproving, selectEarnInputAmount, + selectEnrichedAvailableVaults, selectIsSignatureAvailable, + updateEarnedValues, + updateLockedValues, vaultDeposit, vaultWithdraw, } from "@tallyho/tally-background/redux-slices/earn" @@ -20,6 +25,7 @@ import { } from "@tallyho/tally-background/redux-slices/transaction-construction" import { fromFixedPointNumber } from "@tallyho/tally-background/lib/fixed-point" import { doggoTokenDecimalDigits } from "@tallyho/tally-background/constants" +import { HexString } from "@tallyho/tally-background/types" import { useHistory, useLocation } from "react-router-dom" import BackButton from "../components/Shared/SharedBackButton" @@ -45,33 +51,29 @@ export default function EarnDeposit(): ReactElement { const history = useHistory() - const { vault } = useLocation().state as { - vault: AvailableVault & { - localValueTotalDeposited: string | undefined - localValueUserDeposited: string | undefined - } + const { vaultAddress } = useLocation().state as { + vaultAddress: HexString } + const isCurrentlyApproving = useBackgroundSelector(selectCurrentlyApproving) const signatureAvailable = useBackgroundSelector(selectIsSignatureAvailable) - const pendingRewards = fromFixedPointNumber( - { amount: vault.pendingRewards, decimals: doggoTokenDecimalDigits }, - 2 + const enrichedVaults = useBackgroundSelector(selectEnrichedAvailableVaults) + const account = useBackgroundSelector(selectCurrentAccount) + + const vault = enrichedVaults.find( + (enrichedVault) => enrichedVault?.vaultAddress === vaultAddress ) - if ( - typeof vault.localValueUserDeposited !== "undefined" && - Number(vault.localValueUserDeposited) > 0 && - deposited === false - ) { - setDeposited(true) - } + const { combinedData } = useBackgroundSelector( + selectAccountAndTimestampedActivities + ) useEffect(() => { const checkApproval = async () => { const getApprovalAmount = async () => { const approvedAmount = (await dispatch( - checkApprovalTargetApproval(vault?.asset?.contractAddress) + checkApprovalTargetApproval(vault?.asset?.contractAddress || "0x") )) as unknown as ApprovalTargetAllowance return approvedAmount.allowance } @@ -80,12 +82,30 @@ export default function EarnDeposit(): ReactElement { setIsApproved(allowanceGreaterThanAmount) } checkApproval() - }, [vault?.asset?.contractAddress, dispatch, amount, isCurrentlyApproving]) + }, [amount, dispatch, vault?.asset?.contractAddress, account.address]) - const { combinedData } = useBackgroundSelector( - selectAccountAndTimestampedActivities + useEffect(() => { + dispatch(updateLockedValues()) + dispatch(updateEarnedValues()) + }, [dispatch, account.address]) + + if (typeof vault === "undefined") { + return <> + } + + const pendingRewards = fromFixedPointNumber( + { amount: vault.pendingRewards, decimals: doggoTokenDecimalDigits }, + 2 ) + if ( + typeof vault.localValueUserDeposited !== "undefined" && + Number(vault.localValueUserDeposited) > 0 && + deposited === false + ) { + setDeposited(true) + } + const showWithdrawalModal = () => { setWithdrawalSlideupVisible(true) } From c5b3d4749af378fb80797a66b11133af8add4914 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Fri, 25 Mar 2022 19:04:08 +0100 Subject: [PATCH 22/36] Format data to have main currency values in redux --- background/redux-slices/earn.ts | 61 +++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 9853055146..11e30e30c2 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -15,19 +15,14 @@ import { getProvider, getSignerAddress, } from "./utils/contract-utils" +import { AssetsState, selectAssetPricePoint } from "./assets" +import { enrichAssetAmountWithMainCurrencyValues } from "./utils/asset-utils" export type ApprovalTargetAllowance = { contractAddress: HexString allowance: number } -export type LockedValue = { - vaultAddress: HexString - lockedValue: bigint - vaultTokenSymbol: string - asset: AnyAsset & { contractAddress: string } -} - export type AvailableVault = { vaultAddress: HexString active: boolean @@ -41,7 +36,6 @@ export type AvailableVault = { export type EarnState = { signature: Signature approvalTargetAllowances: ApprovalTargetAllowance[] - lockedAmounts: LockedValue[] availableVaults: AvailableVault[] currentlyDepositing: boolean currentlyApproving: boolean @@ -62,7 +56,6 @@ export const initialState: EarnState = { v: undefined, }, approvalTargetAllowances: [], - lockedAmounts: [], availableVaults: [ { asset: { @@ -492,19 +485,6 @@ export const selectAvailableVaults = createSelector( (earnState: EarnState) => earnState.availableVaults ) -export const selectLockedValues = createSelector( - (state: { earn: EarnState }): EarnState => state.earn, - (earnState: EarnState) => earnState.lockedAmounts -) - -export const selectTotalLockedValue = createSelector( - (state: { earn: EarnState }): EarnState => state.earn, - (earnState: EarnState) => - earnState.lockedAmounts.reduce((total, vault) => { - return total + Number(vault.lockedValue) - }, 0) -) - export const selectIsSignatureAvailable = createSelector( (state: { earn: EarnState }): EarnState => state.earn, (earnState: EarnState) => { @@ -523,3 +503,40 @@ export const selectEarnInputAmount = createSelector( (state: { earn: EarnState }): EarnState => state.earn, (earnState: EarnState) => earnState.inputAmount ) + +export const selectEnrichedAvailableVaults = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (state: { assets: AssetsState }): AssetsState => state.assets, + (earnState: EarnState, assetsState: AssetsState) => { + // FIXME make this proper main currency + const mainCurrencySymbol = "USD" + const vaultsWithMainCurrencyValues = earnState.availableVaults.map( + (vault) => { + const assetPricePoint = selectAssetPricePoint( + assetsState, + vault.asset.symbol, + mainCurrencySymbol + ) + const userTVL = enrichAssetAmountWithMainCurrencyValues( + { amount: vault.userDeposited, asset: vault.asset }, + assetPricePoint, + 2 + ) + const totalTVL = enrichAssetAmountWithMainCurrencyValues( + { amount: vault.totalDeposited, asset: vault.asset }, + assetPricePoint, + 2 + ) + + return { + ...vault, + localValueUserDeposited: userTVL.localizedMainCurrencyAmount, + localValueTotalDeposited: totalTVL.localizedMainCurrencyAmount, + numberValueUserDeposited: userTVL.mainCurrencyAmount, + numberValueTotalDeposited: totalTVL.mainCurrencyAmount, + } + } + ) + return vaultsWithMainCurrencyValues + } +) From c56968df03c0866e7d16d20e48af8b31cbf46190 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Fri, 25 Mar 2022 19:20:18 +0100 Subject: [PATCH 23/36] Use selector instead transforming data in the ui --- ui/pages/Earn.tsx | 43 +++++----------------------------------- ui/pages/EarnDeposit.tsx | 10 ++++++++-- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/ui/pages/Earn.tsx b/ui/pages/Earn.tsx index e5849d9e13..fd4e3263cb 100644 --- a/ui/pages/Earn.tsx +++ b/ui/pages/Earn.tsx @@ -1,15 +1,9 @@ import { AvailableVault, - selectAvailableVaults, + selectEnrichedAvailableVaults, updateEarnedValues, updateLockedValues, } from "@tallyho/tally-background/redux-slices/earn" -import { - getAssetsState, - selectMainCurrencySymbol, -} from "@tallyho/tally-background/redux-slices/selectors" -import { selectAssetPricePoint } from "@tallyho/tally-background/redux-slices/assets" -import { enrichAssetAmountWithMainCurrencyValues } from "@tallyho/tally-background/redux-slices/utils/asset-utils" import React, { ReactElement, useEffect, useState } from "react" import { Link } from "react-router-dom" @@ -167,7 +161,9 @@ export default function Earn(): ReactElement { const dispatch = useBackgroundDispatch() - const availableVaults = useBackgroundSelector(selectAvailableVaults) + const vaultsWithMainCurrencyValues = useBackgroundSelector( + selectEnrichedAvailableVaults + ) const isComingSoon = false @@ -176,35 +172,6 @@ export default function Earn(): ReactElement { dispatch(updateEarnedValues()) }, [dispatch]) - const assets = useBackgroundSelector(getAssetsState) - const mainCurrencySymbol = useBackgroundSelector(selectMainCurrencySymbol) - - const vaultsWithMainCurrencyValues = availableVaults.map((vault) => { - const assetPricePoint = selectAssetPricePoint( - assets, - vault.asset.symbol, - mainCurrencySymbol - ) - const userTVL = enrichAssetAmountWithMainCurrencyValues( - { amount: vault.userDeposited, asset: vault.asset }, - assetPricePoint, - 2 - ) - const totalTVL = enrichAssetAmountWithMainCurrencyValues( - { amount: vault.totalDeposited, asset: vault.asset }, - assetPricePoint, - 2 - ) - - return { - ...vault, - localValueUserDeposited: userTVL.localizedMainCurrencyAmount, - localValueTotalDeposited: totalTVL.localizedMainCurrencyAmount, - numberValueUserDeposited: userTVL.mainCurrencyAmount, - numberValueTotalDeposited: totalTVL.mainCurrencyAmount, - } - }) - const totalTVL = vaultsWithMainCurrencyValues .map((item) => { return typeof item.numberValueTotalDeposited !== "undefined" @@ -283,7 +250,7 @@ export default function Earn(): ReactElement {
      - {availableVaults.map((asset) => ( + {vaultsWithMainCurrencyValues.map((asset) => (
    • 0 && + typeof vault.numberValueUserDeposited !== "undefined" && + vault.numberValueUserDeposited > 0 && deposited === false ) { setDeposited(true) + } else if ( + typeof vault.numberValueUserDeposited !== "undefined" && + vault.numberValueUserDeposited === 0 && + deposited === true + ) { + setDeposited(false) } const showWithdrawalModal = () => { From 07a7bcd1514e7a33105b24ec185504bf8647863a Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Fri, 25 Mar 2022 21:41:43 +0100 Subject: [PATCH 24/36] Add conditional wrapper and remove hardcoded value --- ui/pages/EarnDeposit.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index 17b884b3b3..cd96702f8c 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -70,18 +70,20 @@ export default function EarnDeposit(): ReactElement { ) useEffect(() => { - const checkApproval = async () => { - const getApprovalAmount = async () => { - const approvedAmount = (await dispatch( - checkApprovalTargetApproval(vault?.asset?.contractAddress || "0x") - )) as unknown as ApprovalTargetAllowance - return approvedAmount.allowance + if (typeof vault?.asset?.contractAddress !== "undefined") { + const checkApproval = async () => { + const getApprovalAmount = async () => { + const approvedAmount = (await dispatch( + checkApprovalTargetApproval(vault.asset.contractAddress) + )) as unknown as ApprovalTargetAllowance + return approvedAmount.allowance + } + const allowance = await getApprovalAmount() + const allowanceGreaterThanAmount = allowance >= Number(amount) + setIsApproved(allowanceGreaterThanAmount) } - const allowance = await getApprovalAmount() - const allowanceGreaterThanAmount = allowance >= Number(amount) - setIsApproved(allowanceGreaterThanAmount) + checkApproval() } - checkApproval() }, [amount, dispatch, vault?.asset?.contractAddress, account.address]) useEffect(() => { From b30a106c116ddc170b3c9d4a2e7974c82d572ab4 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Mon, 28 Mar 2022 15:42:52 +0200 Subject: [PATCH 25/36] Fix token balance fetching in Earn Deposit --- ui/pages/EarnDeposit.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index cd96702f8c..6b7ef20f8d 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -1,7 +1,7 @@ import React, { ReactElement, useEffect, useState } from "react" import { - selectAccountAndTimestampedActivities, selectCurrentAccount, + selectCurrentAccountBalances, } from "@tallyho/tally-background/redux-slices/selectors" import { ApprovalTargetAllowance, @@ -65,9 +65,7 @@ export default function EarnDeposit(): ReactElement { (enrichedVault) => enrichedVault?.vaultAddress === vaultAddress ) - const { combinedData } = useBackgroundSelector( - selectAccountAndTimestampedActivities - ) + const accountBalances = useBackgroundSelector(selectCurrentAccountBalances) useEffect(() => { if (typeof vault?.asset?.contractAddress !== "undefined") { @@ -268,7 +266,7 @@ export default function EarnDeposit(): ReactElement { {panelNumber === 0 ? (
      handleAmountChange(value, errorMessage) From e02e6e3c0726f6220c2186682888573f19f18038 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Mon, 28 Mar 2022 18:21:13 +0200 Subject: [PATCH 26/36] Small css changes and update props --- ui/components/Earn/EarnDepositedCard.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/components/Earn/EarnDepositedCard.tsx b/ui/components/Earn/EarnDepositedCard.tsx index 3544e45d48..555ef52ed4 100644 --- a/ui/components/Earn/EarnDepositedCard.tsx +++ b/ui/components/Earn/EarnDepositedCard.tsx @@ -8,17 +8,19 @@ export default function EarnDepositedCard({ asset, depositedAmount, availableRewards, + vaultAddress, }: { asset: (AnyAsset & { contractAddress: HexString }) | undefined depositedAmount: number availableRewards: number + vaultAddress: HexString }): ReactElement { return (
    • Deposited amount - {depositedAmount} + ${depositedAmount}
    • Available rewards @@ -47,11 +49,12 @@ export default function EarnDepositedCard({ width: 352px; height: 176px; border-radius: 8px; - background-color: var(--green-95); + background: linear-gradient(var(--green-95) 100%, var(--green-95)); box-sizing: border-box; padding: 16px; margin-bottom: 20px; margin-top: 30px; + transition: all 0.2s ease; } .card:hover { box-shadow: 0px 10px 12px 0px #0014138a; From 1db72cdb49307f3ecabdd8fd75e65370bdc2e2b6 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Mon, 28 Mar 2022 18:21:40 +0200 Subject: [PATCH 27/36] Add user TVL and pending rewards calculation --- ui/pages/Earn.tsx | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/ui/pages/Earn.tsx b/ui/pages/Earn.tsx index fd4e3263cb..3414dc13bd 100644 --- a/ui/pages/Earn.tsx +++ b/ui/pages/Earn.tsx @@ -4,6 +4,10 @@ import { updateEarnedValues, updateLockedValues, } from "@tallyho/tally-background/redux-slices/earn" +import { formatCurrencyAmount } from "@tallyho/tally-background/redux-slices/utils/asset-utils" +import { selectMainCurrencySymbol } from "@tallyho/tally-background/redux-slices/selectors" +import { doggoTokenDecimalDigits } from "@tallyho/tally-background/constants" +import { fromFixedPointNumber } from "@tallyho/tally-background/lib/fixed-point" import React, { ReactElement, useEffect, useState } from "react" import { Link } from "react-router-dom" @@ -165,6 +169,8 @@ export default function Earn(): ReactElement { selectEnrichedAvailableVaults ) + const mainCurrencySymbol = useBackgroundSelector(selectMainCurrencySymbol) + const isComingSoon = false useEffect(() => { @@ -180,6 +186,23 @@ export default function Earn(): ReactElement { }) .reduce((prev, curr) => prev + curr, 0) + const userTVL = vaultsWithMainCurrencyValues + .map((item) => { + return typeof item.numberValueUserDeposited !== "undefined" + ? item.numberValueUserDeposited + : 0 + }) + .reduce((prev, curr) => prev + curr, 0) + + const userPendingRewards = vaultsWithMainCurrencyValues + .map((item) => { + return fromFixedPointNumber( + { amount: item.pendingRewards, decimals: doggoTokenDecimalDigits }, + 2 + ) + }) + .reduce((prev, curr) => prev + curr, 0) + return ( <> {isComingSoon ? ( @@ -204,7 +227,7 @@ export default function Earn(): ReactElement {
      Total value locked
      $ - {totalTVL} + {formatCurrencyAmount(mainCurrencySymbol, totalTVL, 2)}
    @@ -242,20 +265,29 @@ export default function Earn(): ReactElement {
    Total deposits
    -
    $134,928
    +
    + ${formatCurrencyAmount(mainCurrencySymbol, userTVL, 2)} +
    Total available rewards
    -
    ~$12,328
    +
    {userPendingRewards} DOGGO
      - {vaultsWithMainCurrencyValues.map((asset) => ( + {vaultsWithMainCurrencyValues.map((vault) => (
    • ))} From a69f953a37bb61e2b3ea87e2d9415995ab0bff04 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Mon, 28 Mar 2022 19:43:08 +0200 Subject: [PATCH 28/36] Add two new deposit pools --- background/redux-slices/earn.ts | 28 +++++++++++++++++++ .../services/chain/asset-data-helper.ts | 2 ++ ui/pages/Earn.tsx | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 11e30e30c2..9e240db035 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -71,6 +71,34 @@ export const initialState: EarnState = { pendingRewards: 0n, active: true, }, + { + asset: { + name: "Wrapped BTC", + symbol: "WBTC", + contractAddress: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + decimals: 8, + }, + vaultAddress: "0xAAfcDd71F8eb9B6229852fD6B005F0c39394Af06", + yearnVault: "0xA696a63cc78DfFa1a63E9E50587C197387FF6C7E", + userDeposited: 0n, + totalDeposited: 0n, + pendingRewards: 0n, + active: true, + }, + { + asset: { + name: "ChainLink", + symbol: "LINK", + contractAddress: "0x514910771AF9Ca656af840dff83E8264EcF986CA", + decimals: 18, + }, + vaultAddress: "0x30EEB5c3d3B3FB3aC532c77cD76dd59f78Ff9070", + yearnVault: "0x671a912C10bba0CFA74Cfc2d6Fba9BA1ed9530B2", + userDeposited: 0n, + totalDeposited: 0n, + pendingRewards: 0n, + active: false, + }, ], currentlyDepositing: false, currentlyApproving: false, diff --git a/background/services/chain/asset-data-helper.ts b/background/services/chain/asset-data-helper.ts index 02085cc051..2c2040dfbb 100644 --- a/background/services/chain/asset-data-helper.ts +++ b/background/services/chain/asset-data-helper.ts @@ -71,6 +71,8 @@ export default class AssetDataHelper { if (USE_MAINNET_FORK) { const tokens = [ "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", // WBTC + "0x514910771AF9Ca656af840dff83E8264EcF986CA", // LINK "0xC8B1e49A5dDE816BCde63F23e7E787086229FE62", // DOGGO ] const balances = tokens.map(async (token) => { diff --git a/ui/pages/Earn.tsx b/ui/pages/Earn.tsx index 3414dc13bd..8c03f56142 100644 --- a/ui/pages/Earn.tsx +++ b/ui/pages/Earn.tsx @@ -243,7 +243,7 @@ export default function Earn(): ReactElement {
        {vaultsWithMainCurrencyValues?.map((vault) => (
      • - +
      • ))}
      From 83c62008823fe1d963bca71104fcc1b29256c48c Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Tue, 29 Mar 2022 11:01:15 +0200 Subject: [PATCH 29/36] Remove unused types --- background/redux-slices/earn.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index ef30c615fa..693af65fc6 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -106,21 +106,6 @@ export const initialState: EarnState = { inputAmount: "", } -export type EIP712DomainType = { - name?: string - version?: string - chainId?: number - verifyingContract?: HexString -} - -export type PermitRequest = { - account: HexString - liquidityTokenAddress: HexString - liquidityAmount: BigNumber - nonce: BigNumber - deadline: BigNumber - spender: HexString -} const APPROVAL_TARGET_CONTRACT_ADDRESS = "0x76465982fD8070FC74c91FD4CFfC7eb56Fc6b03a" From a6c3890b7fac33687538542e7dce079a6d9deabe Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Tue, 29 Mar 2022 13:09:18 +0200 Subject: [PATCH 30/36] Replace hardcoded deadline for earn signatures Remove hardcoded deadline for earn deposits in favor of a 12 hour signature validity. Add UI check to verify the signature validity. Sneak in user deposited value bug fix. --- background/redux-slices/earn.ts | 44 ++++++++++++++++++++++++--------- ui/pages/EarnDeposit.tsx | 39 ++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 693af65fc6..40cde36cf1 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -12,6 +12,7 @@ import { HexString } from "../types" import { createBackgroundAsyncThunk } from "./utils" import { getContract, + getCurrentTimestamp, getProvider, getSignerAddress, } from "./utils/contract-utils" @@ -47,6 +48,7 @@ export type Signature = { r: string | undefined s: string | undefined v: number | undefined + deadline: number | undefined } export const initialState: EarnState = { @@ -54,6 +56,7 @@ export const initialState: EarnState = { r: undefined, s: undefined, v: undefined, + deadline: undefined, }, approvalTargetAllowances: [], availableVaults: [ @@ -115,14 +118,19 @@ const earnSlice = createSlice({ reducers: { saveSignature: ( state, - { payload: { r, s, v } }: { payload: Signature } + { payload: { r, s, v, deadline } }: { payload: Signature } ) => ({ ...state, - signature: { r, s, v }, + signature: { r, s, v, deadline }, }), clearSignature: (state) => ({ ...state, - signature: { r: undefined, s: undefined, v: undefined }, + signature: { + r: undefined, + s: undefined, + v: undefined, + deadline: undefined, + }, }), currentlyDepositing: (immerState, { payload }: { payload: boolean }) => { immerState.currentlyDepositing = payload @@ -136,6 +144,12 @@ const earnSlice = createSlice({ inputAmount: payload, } }, + clearInputAmount: (state) => { + return { + ...state, + inputAmount: "", + } + }, earnedOnVault: ( state, { payload }: { payload: { vault: HexString; amount: bigint } } @@ -205,6 +219,7 @@ export const { lockedAmounts, inputAmount, clearSignature, + clearInputAmount, } = earnSlice.actions export default earnSlice.reducer @@ -292,7 +307,7 @@ export const vaultDeposit = createBackgroundAsyncThunk( signerAddress, signerAddress, depositAmount, - ethers.BigNumber.from(1690792895n), // TODO not sure how to handle, remove hardcode + ethers.BigNumber.from(signature.deadline), signature.v, signature.r, signature.s @@ -306,8 +321,10 @@ export const vaultDeposit = createBackgroundAsyncThunk( dispatch(currentlyDepositing(false)) dispatch(clearSignature()) dispatch(updateLockedValues()) + dispatch(clearInputAmount()) } - + dispatch(clearSignature()) + dispatch(clearInputAmount()) dispatch(currentlyDepositing(false)) dispatch(dispatch(depositError(true))) } @@ -431,6 +448,10 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( APPROVAL_TARGET_CONTRACT_ADDRESS, APPROVAL_TARGET_ABI ) + + const timestamp = await getCurrentTimestamp() + const deadline = timestamp + 12 * 60 * 60 + const nonceValue = await ApprovalTargetContract.nonces(signerAddress) const types = { PermitAndTransferFrom: [ @@ -454,7 +475,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( spender: vault.vaultAddress, value: depositAmount, nonce: nonceValue, - deadline: ethers.BigNumber.from(1690792895n), // TODO not sure how to handle, remove hardcode + deadline: ethers.BigNumber.from(deadline), // TODO not sure how to handle, remove hardcode } // _signTypedData is the ethers function name, once the official release will be ready _ will be dropped @@ -464,7 +485,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const splitSignature = ethers.utils.splitSignature(tx) const { r, s, v } = splitSignature - dispatch(earnSlice.actions.saveSignature({ r, s, v })) + dispatch(earnSlice.actions.saveSignature({ r, s, v, deadline })) } ) export const selectApprovalTargetApprovals = createSelector( @@ -492,17 +513,18 @@ export const selectAvailableVaults = createSelector( (earnState: EarnState) => earnState.availableVaults ) -export const selectIsSignatureAvailable = createSelector( +export const selectSignature = createSelector( (state: { earn: EarnState }): EarnState => state.earn, (earnState: EarnState) => { if ( typeof earnState.signature.r !== "undefined" && typeof earnState.signature.v !== "undefined" && - typeof earnState.signature.s !== "undefined" + typeof earnState.signature.s !== "undefined" && + typeof earnState.signature.deadline !== "undefined" ) { - return true + return earnState.signature } - return false + return undefined } ) diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index 6b7ef20f8d..c2c770dc3b 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -8,12 +8,13 @@ import { approveApprovalTarget, checkApprovalTargetApproval, claimVaultRewards, + clearSignature, inputAmount, permitVaultDeposit, selectCurrentlyApproving, selectEarnInputAmount, selectEnrichedAvailableVaults, - selectIsSignatureAvailable, + selectSignature, updateEarnedValues, updateLockedValues, vaultDeposit, @@ -26,6 +27,7 @@ import { import { fromFixedPointNumber } from "@tallyho/tally-background/lib/fixed-point" import { doggoTokenDecimalDigits } from "@tallyho/tally-background/constants" import { HexString } from "@tallyho/tally-background/types" +import { getCurrentTimestamp } from "@tallyho/tally-background/redux-slices/utils/contract-utils" import { useHistory, useLocation } from "react-router-dom" import BackButton from "../components/Shared/SharedBackButton" @@ -56,7 +58,7 @@ export default function EarnDeposit(): ReactElement { } const isCurrentlyApproving = useBackgroundSelector(selectCurrentlyApproving) - const signatureAvailable = useBackgroundSelector(selectIsSignatureAvailable) + const signature = useBackgroundSelector(selectSignature) const enrichedVaults = useBackgroundSelector(selectEnrichedAvailableVaults) const account = useBackgroundSelector(selectCurrentAccount) @@ -84,9 +86,25 @@ export default function EarnDeposit(): ReactElement { } }, [amount, dispatch, vault?.asset?.contractAddress, account.address]) + useEffect(() => { + const checkCurrentSignatureDeadline = async () => { + const timestamp = await getCurrentTimestamp() + if ( + typeof signature?.deadline !== "undefined" && + timestamp > signature.deadline + ) { + dispatch(clearSignature) + } + } + checkCurrentSignatureDeadline() + }, [dispatch, signature?.deadline]) + useEffect(() => { dispatch(updateLockedValues()) dispatch(updateEarnedValues()) + return () => { + dispatch(clearSignature()) + } }, [dispatch, account.address]) if (typeof vault === "undefined") { @@ -98,6 +116,11 @@ export default function EarnDeposit(): ReactElement { 2 ) + const userDeposited = fromFixedPointNumber( + { amount: vault.userDeposited, decimals: vault.asset.decimals }, + 4 + ) + if ( typeof vault.numberValueUserDeposited !== "undefined" && vault.numberValueUserDeposited > 0 && @@ -178,7 +201,7 @@ export default function EarnDeposit(): ReactElement { } const depositButtonText = () => { - if (!isEnabled && !signatureAvailable) { + if (!isEnabled && typeof signature === "undefined") { return "Enable" } if (deposited) { @@ -235,7 +258,7 @@ export default function EarnDeposit(): ReactElement {
    • Deposited amount
      - {vault.localValueUserDeposited} + {userDeposited} {vault?.asset.symbol}
    • @@ -293,7 +316,11 @@ export default function EarnDeposit(): ReactElement { {depositButtonText()} @@ -345,7 +372,7 @@ export default function EarnDeposit(): ReactElement {
    • Deposited amount
      - {vault.localValueUserDeposited} + {userDeposited} {vault.asset.symbol}
    • From b6af0052db181b1199d072ecbb3d61e010fe6560 Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Tue, 29 Mar 2022 13:12:17 +0200 Subject: [PATCH 31/36] Replace approval page title for approval target --- ui/pages/SignData.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/pages/SignData.tsx b/ui/pages/SignData.tsx index 3f116c05ad..45ac0ede3b 100644 --- a/ui/pages/SignData.tsx +++ b/ui/pages/SignData.tsx @@ -63,13 +63,20 @@ export default function SignData(): ReactElement { history.goBack() } + const getTitle = () => { + if (typedDataRequest.typedData.primaryType === "PermitAndTransferFrom") { + return "Authorize Deposit" + } + return `Sign ${typedDataRequest.typedData.primaryType ?? "Message"}` + } + return ( } reviewPanel={} isTransactionSigning={isTransactionSigning} From 670fda0997be23302e51878aa870fd507c011d6f Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Tue, 29 Mar 2022 16:05:41 +0200 Subject: [PATCH 32/36] Remove outdated comment and add missing fn call --- background/redux-slices/earn.ts | 2 +- ui/pages/EarnDeposit.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 40cde36cf1..a4aad8c416 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -475,7 +475,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( spender: vault.vaultAddress, value: depositAmount, nonce: nonceValue, - deadline: ethers.BigNumber.from(deadline), // TODO not sure how to handle, remove hardcode + deadline: ethers.BigNumber.from(deadline), } // _signTypedData is the ethers function name, once the official release will be ready _ will be dropped diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index c2c770dc3b..004763c5ec 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -93,7 +93,7 @@ export default function EarnDeposit(): ReactElement { typeof signature?.deadline !== "undefined" && timestamp > signature.deadline ) { - dispatch(clearSignature) + dispatch(clearSignature()) } } checkCurrentSignatureDeadline() From 33cd151a988cd74849d3e93852b0dd70194b29ef Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 30 Mar 2022 11:07:32 +0200 Subject: [PATCH 33/36] Change the signing data and tx to be one process In order to reduce number of user steps needed to deposit the signing typed data success will auto fire signing the tx for deposit. Once the sign tx is done we emit an event to show the snack bar success message. --- background/main.ts | 5 +++++ background/redux-slices/earn.ts | 38 +++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/background/main.ts b/background/main.ts index 4622cde50e..57a500ef82 100644 --- a/background/main.ts +++ b/background/main.ts @@ -87,6 +87,7 @@ import { SignTypedDataRequest, SignDataRequest, } from "./utils/signing" +import { emitter as earnSliceEmitter } from "./redux-slices/earn" import { resetLedgerState, setDeviceConnectionStatus, @@ -664,6 +665,10 @@ export default class Main extends BaseService { ) }) + earnSliceEmitter.on("depositSuccessful", (message) => { + this.store.dispatch(setSnackbarMessage(message)) + }) + transactionConstructionSliceEmitter.on("updateOptions", async (options) => { const { values: { maxFeePerGas, maxPriorityFeePerGas }, diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index a4aad8c416..8df0d1f890 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -2,6 +2,8 @@ import { TransactionResponse } from "@ethersproject/abstract-provider" import { createSlice, createSelector } from "@reduxjs/toolkit" import { BigNumber, ethers } from "ethers" import { parseUnits } from "ethers/lib/utils" +import Emittery from "emittery" + import { AnyAsset } from "../assets" import { USE_MAINNET_FORK } from "../features/features" import { ERC20_ABI } from "../lib/erc20" @@ -42,6 +44,7 @@ export type EarnState = { currentlyApproving: boolean depositError: boolean inputAmount: string + depositingProcess: boolean } export type Signature = { @@ -51,6 +54,12 @@ export type Signature = { deadline: number | undefined } +export type Events = { + depositSuccessful: string +} + +export const emitter = new Emittery() + export const initialState: EarnState = { signature: { r: undefined, @@ -107,6 +116,7 @@ export const initialState: EarnState = { currentlyApproving: false, depositError: false, inputAmount: "", + depositingProcess: false, } const APPROVAL_TARGET_CONTRACT_ADDRESS = @@ -132,9 +142,16 @@ const earnSlice = createSlice({ deadline: undefined, }, }), + clearInput: (state) => ({ + ...state, + inputAmount: "", + }), currentlyDepositing: (immerState, { payload }: { payload: boolean }) => { immerState.currentlyDepositing = payload }, + depositProcess: (immerState, { payload }: { payload: boolean }) => { + immerState.depositingProcess = payload + }, currentlyApproving: (immerState, { payload }: { payload: boolean }) => { immerState.currentlyApproving = payload }, @@ -144,12 +161,6 @@ const earnSlice = createSlice({ inputAmount: payload, } }, - clearInputAmount: (state) => { - return { - ...state, - inputAmount: "", - } - }, earnedOnVault: ( state, { payload }: { payload: { vault: HexString; amount: bigint } } @@ -219,7 +230,8 @@ export const { lockedAmounts, inputAmount, clearSignature, - clearInputAmount, + clearInput, + depositProcess, } = earnSlice.actions export default earnSlice.reducer @@ -286,6 +298,7 @@ export const vaultDeposit = createBackgroundAsyncThunk( }, { getState, dispatch } ) => { + dispatch(depositProcess(false)) const provider = getProvider() const signer = provider.getSigner() const signerAddress = await getSignerAddress() @@ -315,16 +328,17 @@ export const vaultDeposit = createBackgroundAsyncThunk( if (USE_MAINNET_FORK) { depositTransactionData.gasLimit = BigNumber.from(850000) // for mainnet fork only } + dispatch(currentlyDepositing(true)) + dispatch(clearInput()) const response = await signer.sendTransaction(depositTransactionData) const receipt = await response.wait() if (receipt.status === 1) { dispatch(currentlyDepositing(false)) dispatch(clearSignature()) dispatch(updateLockedValues()) - dispatch(clearInputAmount()) + await emitter.emit("depositSuccessful", "Asset successfully deposited") } dispatch(clearSignature()) - dispatch(clearInputAmount()) dispatch(currentlyDepositing(false)) dispatch(dispatch(depositError(true))) } @@ -486,6 +500,7 @@ export const permitVaultDeposit = createBackgroundAsyncThunk( const { r, s, v } = splitSignature dispatch(earnSlice.actions.saveSignature({ r, s, v, deadline })) + dispatch(depositProcess(true)) } ) export const selectApprovalTargetApprovals = createSelector( @@ -533,6 +548,11 @@ export const selectEarnInputAmount = createSelector( (earnState: EarnState) => earnState.inputAmount ) +export const selectDepositingProcess = createSelector( + (state: { earn: EarnState }): EarnState => state.earn, + (earnState: EarnState) => earnState.depositingProcess +) + export const selectEnrichedAvailableVaults = createSelector( (state: { earn: EarnState }): EarnState => state.earn, (state: { assets: AssetsState }): AssetsState => state.assets, From a68e3efe5ab42bae0eba0087e7f3c3d0a9e2df4b Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 30 Mar 2022 11:08:44 +0200 Subject: [PATCH 34/36] Add UI flow to trigger two transactions User will now only click once and UI will kind of queue two transactions. --- ui/pages/EarnDeposit.tsx | 63 +++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/ui/pages/EarnDeposit.tsx b/ui/pages/EarnDeposit.tsx index 004763c5ec..531430194c 100644 --- a/ui/pages/EarnDeposit.tsx +++ b/ui/pages/EarnDeposit.tsx @@ -12,6 +12,8 @@ import { inputAmount, permitVaultDeposit, selectCurrentlyApproving, + selectCurrentlyDepositing, + selectDepositingProcess, selectEarnInputAmount, selectEnrichedAvailableVaults, selectSignature, @@ -46,7 +48,6 @@ export default function EarnDeposit(): ReactElement { const [hasError, setHasError] = useState(false) const [withdrawSlideupVisible, setWithdrawalSlideupVisible] = useState(false) const [isApproved, setIsApproved] = useState(false) - const [isEnabled, setIsEnabled] = useState(false) const [deposited, setDeposited] = useState(false) const dispatch = useBackgroundDispatch() @@ -59,6 +60,8 @@ export default function EarnDeposit(): ReactElement { const isCurrentlyApproving = useBackgroundSelector(selectCurrentlyApproving) const signature = useBackgroundSelector(selectSignature) + const inDepositProcess = useBackgroundSelector(selectDepositingProcess) + const isDepositPending = useBackgroundSelector(selectCurrentlyDepositing) const enrichedVaults = useBackgroundSelector(selectEnrichedAvailableVaults) const account = useBackgroundSelector(selectCurrentAccount) @@ -84,7 +87,13 @@ export default function EarnDeposit(): ReactElement { } checkApproval() } - }, [amount, dispatch, vault?.asset?.contractAddress, account.address]) + }, [ + amount, + dispatch, + vault?.asset?.contractAddress, + account.address, + isCurrentlyApproving, + ]) useEffect(() => { const checkCurrentSignatureDeadline = async () => { @@ -107,6 +116,20 @@ export default function EarnDeposit(): ReactElement { } }, [dispatch, account.address]) + useEffect(() => { + if (inDepositProcess && typeof vault !== "undefined") { + dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) + dispatch( + vaultDeposit({ + vault, + amount, + tokenAddress: vault.asset.contractAddress, + }) + ) + history.push("/sign-transaction") + } + }, [amount, dispatch, history, inDepositProcess, vault]) + if (typeof vault === "undefined") { return <> } @@ -145,8 +168,8 @@ export default function EarnDeposit(): ReactElement { history.push("/sign-transaction") } - const enable = () => { - setIsEnabled(true) + const deposit = async () => { + await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) dispatch( permitVaultDeposit({ vault, @@ -157,18 +180,6 @@ export default function EarnDeposit(): ReactElement { history.push("/sign-data") } - const deposit = async () => { - await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) - dispatch( - vaultDeposit({ - vault, - amount, - tokenAddress: vault.asset.contractAddress, - }) - ) - history.push("/sign-transaction") - } - const withdraw = async () => { await dispatch(clearTransactionState(TransactionConstructionStatus.Pending)) dispatch( @@ -200,21 +211,11 @@ export default function EarnDeposit(): ReactElement { } } - const depositButtonText = () => { - if (!isEnabled && typeof signature === "undefined") { - return "Enable" - } - if (deposited) { - return "Deposit more" - } - return "Deposit" - } - const approveButtonText = () => { if (isCurrentlyApproving === true) { return "Approving..." } - return "Approve Approval Target" + return "Approve asset" } return ( @@ -316,14 +317,10 @@ export default function EarnDeposit(): ReactElement { - {depositButtonText()} + {isDepositPending ? "Depositing..." : "Authorize & Deposit"} )}
    From e9876237ea1f4024619c982a137cda35f1dac00c Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 30 Mar 2022 16:30:08 +0200 Subject: [PATCH 35/36] Fix bug causing the button to be stuck in pending --- background/redux-slices/earn.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index 8df0d1f890..da0efb70ca 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -328,9 +328,9 @@ export const vaultDeposit = createBackgroundAsyncThunk( if (USE_MAINNET_FORK) { depositTransactionData.gasLimit = BigNumber.from(850000) // for mainnet fork only } - dispatch(currentlyDepositing(true)) dispatch(clearInput()) const response = await signer.sendTransaction(depositTransactionData) + dispatch(currentlyDepositing(true)) const receipt = await response.wait() if (receipt.status === 1) { dispatch(currentlyDepositing(false)) @@ -375,8 +375,8 @@ export const claimVaultRewards = createBackgroundAsyncThunk( signer ) const tx = await vaultContract.functions["getReward()"]() - const response = signer.sendTransaction(tx) - await tx.wait(response) + await signer.sendTransaction(tx) + dispatch(updateEarnedValues()) } ) From c1c462af78cb85a9e8c776cef1b5bf1caaaae7ae Mon Sep 17 00:00:00 2001 From: Wojciech Turek Date: Wed, 30 Mar 2022 16:39:25 +0200 Subject: [PATCH 36/36] Fix bug causing users to be unable to claim reward --- background/redux-slices/earn.ts | 4 ++-- background/services/internal-ethereum-provider/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/background/redux-slices/earn.ts b/background/redux-slices/earn.ts index da0efb70ca..e64df4dc61 100644 --- a/background/redux-slices/earn.ts +++ b/background/redux-slices/earn.ts @@ -375,8 +375,8 @@ export const claimVaultRewards = createBackgroundAsyncThunk( signer ) const tx = await vaultContract.functions["getReward()"]() - await signer.sendTransaction(tx) - + const response = signer.sendTransaction(tx) + await tx.wait(response) dispatch(updateEarnedValues()) } ) diff --git a/background/services/internal-ethereum-provider/index.ts b/background/services/internal-ethereum-provider/index.ts index 9e77dc2851..8b254505a1 100644 --- a/background/services/internal-ethereum-provider/index.ts +++ b/background/services/internal-ethereum-provider/index.ts @@ -122,6 +122,7 @@ export default class InternalEthereumProviderService extends BaseService )}` case "eth_blockNumber": case "eth_call": + case "eth_estimateGas": case "eth_feeHistory": case "eth_gasPrice": case "eth_getBalance": @@ -194,7 +195,6 @@ export default class InternalEthereumProviderService extends BaseService case "metamask_sendDomainMetadata": case "wallet_requestPermissions": case "wallet_watchAsset": - case "eth_estimateGas": case "estimateGas": // --- eip1193-bridge only method -- case "eth_coinbase": // --- MM only methods --- case "eth_decrypt":