From 7235c885b9f6c8a6610ea5fdc1cb99cb202a66e0 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 28 Nov 2022 14:19:27 -0800 Subject: [PATCH 01/11] erc20 bridge + approve + requires allowance --- packages/bridge-ui/src/App.svelte | 10 +- .../src/components/buttons/SelectToken.svelte | 9 +- .../src/components/form/BridgeForm.svelte | 32 ++- packages/bridge-ui/src/constants/abi/ERC20.ts | 222 +++++++++++++++++ packages/bridge-ui/src/domain/bridge.ts | 2 + packages/bridge-ui/src/erc20/bridge.spec.ts | 227 ++++++++++++++++++ packages/bridge-ui/src/erc20/bridge.ts | 104 ++++++++ packages/bridge-ui/src/eth/bridge.spec.ts | 20 ++ packages/bridge-ui/src/eth/bridge.ts | 6 +- 9 files changed, 622 insertions(+), 10 deletions(-) create mode 100644 packages/bridge-ui/src/constants/abi/ERC20.ts create mode 100644 packages/bridge-ui/src/erc20/bridge.spec.ts create mode 100644 packages/bridge-ui/src/erc20/bridge.ts diff --git a/packages/bridge-ui/src/App.svelte b/packages/bridge-ui/src/App.svelte index 5342eb9e00..9aa4c28752 100644 --- a/packages/bridge-ui/src/App.svelte +++ b/packages/bridge-ui/src/App.svelte @@ -5,25 +5,22 @@ import Navbar from "./components/Navbar.svelte"; import { SvelteToast } from "@zerodevx/svelte-toast"; - import { onMount } from "svelte"; import Home from "./pages/home/Home.svelte"; import { setupI18n } from "./i18n"; import { BridgeType } from "./domain/bridge"; import ETHBridge from "./eth/bridge"; import { bridges, chainIdToBridgeAddress } from "./store/bridge"; import { CHAIN_MAINNET, CHAIN_TKO } from "./domain/chain"; - - const { chains, provider } = configureChains( - [mainnet, taiko], - [publicProvider()] - ); + import ERC20Bridge from "./erc20/bridge"; setupI18n({ withLocale: "en" }); const ethBridge = new ETHBridge(); + const erc20Bridge = new ERC20Bridge(); bridges.update((store) => { store.set(BridgeType.ETH, ethBridge); + store.set(BridgeType.ERC20, erc20Bridge); return store; }); @@ -46,7 +43,6 @@
-
diff --git a/packages/bridge-ui/src/components/buttons/SelectToken.svelte b/packages/bridge-ui/src/components/buttons/SelectToken.svelte index 889f078ad1..0f7a5bb1b0 100644 --- a/packages/bridge-ui/src/components/buttons/SelectToken.svelte +++ b/packages/bridge-ui/src/components/buttons/SelectToken.svelte @@ -1,13 +1,20 @@ diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index 8a1411d259..0c09b17e93 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -2,10 +2,17 @@ import { _ } from "svelte-i18n"; import { token } from "../../store/token"; import { fromChain, toChain } from "../../store/chain"; - import { activeBridge, chainIdToBridgeAddress } from "../../store/bridge"; + import { + activeBridge, + chainIdToBridgeAddress, + bridgeType, + } from "../../store/bridge"; import { signer } from "../../store/signer"; import { BigNumber, ethers, Signer } from "ethers"; import { toast } from "@zerodevx/svelte-toast"; + import type { Token } from "../../domain/token"; + import type { BridgeType } from "../../domain/bridge"; + import type { Chain } from "../../domain/chain"; let amount: string; let btnDisabled: boolean = true; @@ -14,6 +21,29 @@ .then((d) => (btnDisabled = d)) .catch((e) => console.log(e)); + $: checkAllowance(amount, $token, $bridgeType, $fromChain, $signer); + + async function checkAllowance( + amt: string, + token: Token, + bridgeType: BridgeType, + fromChain: Chain, + signer: Signer + ) { + return await $activeBridge.RequiresAllowance({ + amountInWei: amt + ? ethers.utils.parseUnits(amt, token.decimals) + : BigNumber.from(0), + signer: signer, + tokenAddress: token.address, + fromChainId: fromChain.id, + toChainId: $toChain.id, + bridgeAddress: $chainIdToBridgeAddress.get(fromChain.id), + processingFeeInWei: BigNumber.from(100), + memo: "memo", + }); + } + async function isBtnDisabled(signer: Signer, amount: string) { if (!signer) return true; if (!amount) return true; diff --git a/packages/bridge-ui/src/constants/abi/ERC20.ts b/packages/bridge-ui/src/constants/abi/ERC20.ts new file mode 100644 index 0000000000..f8bd8a6651 --- /dev/null +++ b/packages/bridge-ui/src/constants/abi/ERC20.ts @@ -0,0 +1,222 @@ +export default [ + { + constant: true, + inputs: [], + name: "name", + outputs: [ + { + name: "", + type: "string", + }, + ], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { + name: "_spender", + type: "address", + }, + { + name: "_value", + type: "uint256", + }, + ], + name: "approve", + outputs: [ + { + name: "", + type: "bool", + }, + ], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "totalSupply", + outputs: [ + { + name: "", + type: "uint256", + }, + ], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { + name: "_from", + type: "address", + }, + { + name: "_to", + type: "address", + }, + { + name: "_value", + type: "uint256", + }, + ], + name: "transferFrom", + outputs: [ + { + name: "", + type: "bool", + }, + ], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "decimals", + outputs: [ + { + name: "", + type: "uint8", + }, + ], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [ + { + name: "_owner", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + name: "balance", + type: "uint256", + }, + ], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "symbol", + outputs: [ + { + name: "", + type: "string", + }, + ], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { + name: "_to", + type: "address", + }, + { + name: "_value", + type: "uint256", + }, + ], + name: "transfer", + outputs: [ + { + name: "", + type: "bool", + }, + ], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [ + { + name: "_owner", + type: "address", + }, + { + name: "_spender", + type: "address", + }, + ], + name: "allowance", + outputs: [ + { + name: "", + type: "uint256", + }, + ], + payable: false, + stateMutability: "view", + type: "function", + }, + { + payable: true, + stateMutability: "payable", + type: "fallback", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: "owner", + type: "address", + }, + { + indexed: true, + name: "spender", + type: "address", + }, + { + indexed: false, + name: "value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: "from", + type: "address", + }, + { + indexed: true, + name: "to", + type: "address", + }, + { + indexed: false, + name: "value", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, +]; diff --git a/packages/bridge-ui/src/domain/bridge.ts b/packages/bridge-ui/src/domain/bridge.ts index 63eeb9caff..1fc0fe5077 100644 --- a/packages/bridge-ui/src/domain/bridge.ts +++ b/packages/bridge-ui/src/domain/bridge.ts @@ -11,6 +11,7 @@ type ApproveOpts = { amountInWei: BigNumber; contractAddress: string; signer: ethers.Signer; + spenderAddress: string; }; type BridgeOpts = { @@ -26,6 +27,7 @@ type BridgeOpts = { }; interface Bridge { + RequiresAllowance(opts: BridgeOpts): Promise; Approve(opts: ApproveOpts): Promise; Bridge(opts: BridgeOpts): Promise; } diff --git a/packages/bridge-ui/src/erc20/bridge.spec.ts b/packages/bridge-ui/src/erc20/bridge.spec.ts new file mode 100644 index 0000000000..b6615bc79a --- /dev/null +++ b/packages/bridge-ui/src/erc20/bridge.spec.ts @@ -0,0 +1,227 @@ +import { BigNumber, Wallet } from "ethers"; +import { mainnet, taiko } from "../domain/chain"; +import type { ApproveOpts, Bridge, BridgeOpts } from "../domain/bridge"; +import ERC20Bridge from "./bridge"; +const mockSigner = { + getAddress: jest.fn(), +}; + +const mockContract = { + sendERC20: jest.fn(), + allowance: jest.fn(), + approve: jest.fn(), +}; + +jest.mock("ethers", () => ({ + /* eslint-disable-next-line */ + ...(jest.requireActual("ethers") as object), + Wallet: function () { + return mockSigner; + }, + Signer: function () { + return mockSigner; + }, + Contract: function () { + return mockContract; + }, +})); + +const wallet = new Wallet("0x"); + +const opts: BridgeOpts = { + amountInWei: BigNumber.from(1), + signer: wallet, + tokenAddress: "0xtoken", + fromChainId: mainnet.id, + toChainId: taiko.id, + bridgeAddress: "0x456", + processingFeeInWei: BigNumber.from(2), + memo: "memo", +}; + +const approveOpts: ApproveOpts = { + amountInWei: BigNumber.from(1), + signer: wallet, + contractAddress: "0x456", + spenderAddress: "0x789", +}; + +describe("bridge tests", () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it("requires allowance returns true when allowance has not been set", async () => { + mockContract.allowance.mockImplementationOnce(() => + opts.amountInWei.sub(1) + ); + + mockSigner.getAddress.mockImplementationOnce(() => "0xfake"); + + const bridge: Bridge = new ERC20Bridge(); + + expect(mockContract.allowance).not.toHaveBeenCalled(); + const requires = await bridge.RequiresAllowance(opts); + + expect(mockSigner.getAddress).toHaveBeenCalled(); + expect(mockContract.allowance).toHaveBeenCalledWith( + "0xfake", + opts.bridgeAddress + ); + expect(requires).toBe(true); + }); + + it("requires allowance returns true when allowance is > than amount", async () => { + mockContract.allowance.mockImplementationOnce(() => + opts.amountInWei.add(1) + ); + mockSigner.getAddress.mockImplementationOnce(() => "0xfake"); + + const bridge: Bridge = new ERC20Bridge(); + + expect(mockContract.allowance).not.toHaveBeenCalled(); + const requires = await bridge.RequiresAllowance(opts); + + expect(mockSigner.getAddress).toHaveBeenCalled(); + expect(mockContract.allowance).toHaveBeenCalledWith( + "0xfake", + opts.bridgeAddress + ); + expect(requires).toBe(false); + }); + + it("requires allowance returns true when allowance is === amount", async () => { + mockContract.allowance.mockImplementationOnce(() => opts.amountInWei); + mockSigner.getAddress.mockImplementationOnce(() => "0xfake"); + + const bridge: Bridge = new ERC20Bridge(); + + expect(mockContract.allowance).not.toHaveBeenCalled(); + const requires = await bridge.RequiresAllowance(opts); + + expect(mockSigner.getAddress).toHaveBeenCalled(); + expect(mockContract.allowance).toHaveBeenCalledWith( + "0xfake", + opts.bridgeAddress + ); + expect(requires).toBe(false); + }); + + it("approve throws when amount is already greater than whats set", async () => { + mockContract.allowance.mockImplementationOnce(() => + opts.amountInWei.sub(1) + ); + + mockSigner.getAddress.mockImplementationOnce(() => "0xfake"); + + const bridge: Bridge = new ERC20Bridge(); + + expect(mockContract.allowance).not.toHaveBeenCalled(); + await expect(bridge.Approve(approveOpts)).rejects.toThrowError( + "token vault does not have required allowance" + ); + + expect(mockSigner.getAddress).toHaveBeenCalled(); + expect(mockContract.allowance).toHaveBeenCalledWith( + "0xfake", + approveOpts.spenderAddress + ); + }); + + it("approve succeeds when allowance is less than what is being requested", async () => { + mockContract.allowance.mockImplementationOnce(() => + opts.amountInWei.add(1) + ); + + mockSigner.getAddress.mockImplementationOnce(() => "0xfake"); + + const bridge: Bridge = new ERC20Bridge(); + + expect(mockContract.allowance).not.toHaveBeenCalled(); + await bridge.Approve(approveOpts); + + expect(mockSigner.getAddress).toHaveBeenCalled(); + expect(mockContract.allowance).toHaveBeenCalledWith( + "0xfake", + approveOpts.spenderAddress + ); + expect(mockContract.approve).toHaveBeenCalledWith( + approveOpts.spenderAddress, + approveOpts.amountInWei + ); + }); + + it("bridge throws when requires approval", async () => { + mockContract.allowance.mockImplementationOnce(() => + opts.amountInWei.sub(1) + ); + + const bridge: Bridge = new ERC20Bridge(); + + expect(mockContract.sendERC20).not.toHaveBeenCalled(); + + await expect(bridge.Bridge(opts)).rejects.toThrowError( + "token vault does not have required allowance" + ); + + expect(mockContract.sendERC20).not.toHaveBeenCalled(); + }); + + it("bridge calls senderc20 when doesnt requires approval", async () => { + mockContract.allowance.mockImplementationOnce(() => + opts.amountInWei.add(1) + ); + mockSigner.getAddress.mockImplementation(() => "0xfake"); + + const bridge: Bridge = new ERC20Bridge(); + + expect(mockContract.sendERC20).not.toHaveBeenCalled(); + + await bridge.Bridge(opts); + + expect(mockContract.sendERC20).toHaveBeenCalled(); + expect(mockContract.sendERC20).toHaveBeenCalledWith( + opts.toChainId, + "0xfake", + opts.tokenAddress, + opts.amountInWei, + BigNumber.from(100000), + opts.processingFeeInWei, + "0xfake", + opts.memo + ); + }); + + it("bridge calls senderc20 when doesnt requires approval, with no processing fee and memo", async () => { + mockContract.allowance.mockImplementationOnce(() => + opts.amountInWei.add(1) + ); + mockSigner.getAddress.mockImplementation(() => "0xfake"); + + const bridge: Bridge = new ERC20Bridge(); + + expect(mockContract.sendERC20).not.toHaveBeenCalled(); + + const opts: BridgeOpts = { + amountInWei: BigNumber.from(1), + signer: wallet, + tokenAddress: "0xtoken", + fromChainId: mainnet.id, + toChainId: taiko.id, + bridgeAddress: "0x456", + }; + + await bridge.Bridge(opts); + + expect(mockContract.sendERC20).toHaveBeenCalledWith( + opts.toChainId, + "0xfake", + opts.tokenAddress, + opts.amountInWei, + BigNumber.from(0), + BigNumber.from(0), + "0xfake", + "" + ); + }); +}); diff --git a/packages/bridge-ui/src/erc20/bridge.ts b/packages/bridge-ui/src/erc20/bridge.ts new file mode 100644 index 0000000000..b957f14920 --- /dev/null +++ b/packages/bridge-ui/src/erc20/bridge.ts @@ -0,0 +1,104 @@ +import { BigNumber, Contract, Signer } from "ethers"; +import type { Transaction } from "ethers"; +import type { ApproveOpts, Bridge, BridgeOpts } from "../domain/bridge"; +import TokenVault from "../constants/abi/TokenVault"; +import ERC20 from "../constants/abi/ERC20"; + +class ERC20Bridge implements Bridge { + private async spenderRequiresAllowance( + tokenAddress: string, + signer: Signer, + amount: BigNumber, + bridgeAddress: string + ): Promise { + const contract: Contract = new Contract(tokenAddress, ERC20, signer); + const allowance: BigNumber = await contract.allowance( + await signer.getAddress(), + bridgeAddress + ); + + return allowance.lt(amount); + } + + async RequiresAllowance(opts: BridgeOpts): Promise { + return await this.spenderRequiresAllowance( + opts.tokenAddress, + opts.signer, + opts.amountInWei, + opts.bridgeAddress + ); + } + + async Approve(opts: ApproveOpts): Promise { + if ( + await this.spenderRequiresAllowance( + opts.contractAddress, + opts.signer, + opts.amountInWei, + opts.spenderAddress + ) + ) { + throw Error("token vault does not have required allowance"); + } + + const contract: Contract = new Contract( + opts.contractAddress, + ERC20, + opts.signer + ); + + const tx = await contract.approve(opts.spenderAddress, opts.amountInWei); + return tx; + } + + async Bridge(opts: BridgeOpts): Promise { + if ( + await this.spenderRequiresAllowance( + opts.tokenAddress, + opts.signer, + opts.amountInWei, + opts.bridgeAddress + ) + ) { + throw Error("token vault does not have required allowance"); + } + + const contract: Contract = new Contract( + opts.bridgeAddress, + TokenVault, + opts.signer + ); + + const owner = await opts.signer.getAddress(); + const message = { + sender: owner, + srcChainId: opts.fromChainId, + destChainId: opts.toChainId, + owner: owner, + to: owner, + refundAddress: owner, + depositValue: opts.amountInWei, + callValue: 0, + processingFee: opts.processingFeeInWei ?? BigNumber.from(0), + gasLimit: opts.processingFeeInWei + ? BigNumber.from(100000) + : BigNumber.from(0), + memo: opts.memo ?? "", + }; + + const tx = await contract.sendERC20( + message.destChainId, + owner, + opts.tokenAddress, + opts.amountInWei, + message.gasLimit, + message.processingFee, + message.refundAddress, + message.memo + ); + + return tx; + } +} + +export default ERC20Bridge; diff --git a/packages/bridge-ui/src/eth/bridge.spec.ts b/packages/bridge-ui/src/eth/bridge.spec.ts index 4d247f0303..9472c2dfbe 100644 --- a/packages/bridge-ui/src/eth/bridge.spec.ts +++ b/packages/bridge-ui/src/eth/bridge.spec.ts @@ -30,6 +30,25 @@ describe("bridge tests", () => { jest.resetAllMocks(); }); + it("requires allowance returns false", async () => { + const bridge: Bridge = new ETHBridge(); + const wallet = new Wallet("0x"); + + const opts: BridgeOpts = { + amountInWei: BigNumber.from(1), + signer: wallet, + tokenAddress: "", + fromChainId: mainnet.id, + toChainId: taiko.id, + bridgeAddress: "0x456", + processingFeeInWei: BigNumber.from(2), + memo: "memo", + }; + + const requires = await bridge.RequiresAllowance(opts); + expect(requires).toBe(false); + }); + it("approve returns empty transaction", async () => { const bridge: Bridge = new ETHBridge(); @@ -37,6 +56,7 @@ describe("bridge tests", () => { amountInWei: BigNumber.from(1), signer: new Wallet("0x"), contractAddress: "0x1234", + spenderAddress: "0x", }); }); diff --git a/packages/bridge-ui/src/eth/bridge.ts b/packages/bridge-ui/src/eth/bridge.ts index e56f89bf80..127f8845b5 100644 --- a/packages/bridge-ui/src/eth/bridge.ts +++ b/packages/bridge-ui/src/eth/bridge.ts @@ -4,6 +4,10 @@ import type { ApproveOpts, Bridge, BridgeOpts } from "../domain/bridge"; import TokenVault from "../constants/abi/TokenVault"; class ETHBridge implements Bridge { + RequiresAllowance(opts: BridgeOpts): Promise { + return new Promise((resolve) => resolve(false)); + } + // ETH does not need to be approved for transacting Approve(opts: ApproveOpts): Promise { return new Promise((resolve) => resolve({} as unknown as Transaction)); @@ -18,7 +22,7 @@ class ETHBridge implements Bridge { const owner = await opts.signer.getAddress(); const message = { - sender: opts.signer.getAddress(), + sender: owner, srcChainId: opts.fromChainId, destChainId: opts.toChainId, owner: owner, From 76d4bc334712528ad2a4a108c738197a67c6e113 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 28 Nov 2022 14:22:05 -0800 Subject: [PATCH 02/11] bridge form checks allowance, disables button --- packages/bridge-ui/src/components/form/BridgeForm.svelte | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index 0c09b17e93..db2fcd2880 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -15,13 +15,16 @@ import type { Chain } from "../../domain/chain"; let amount: string; + let requiresAllowance: boolean = true; let btnDisabled: boolean = true; $: isBtnDisabled($signer, amount) .then((d) => (btnDisabled = d)) .catch((e) => console.log(e)); - $: checkAllowance(amount, $token, $bridgeType, $fromChain, $signer); + $: checkAllowance(amount, $token, $bridgeType, $fromChain, $signer) + .then((a) => (requiresAllowance = a)) + .catch((e) => console.log(e)); async function checkAllowance( amt: string, @@ -30,6 +33,8 @@ fromChain: Chain, signer: Signer ) { + if (!signer || !amt || !token || !fromChain) return true; + return await $activeBridge.RequiresAllowance({ amountInWei: amt ? ethers.utils.parseUnits(amt, token.decimals) @@ -47,6 +52,7 @@ async function isBtnDisabled(signer: Signer, amount: string) { if (!signer) return true; if (!amount) return true; + if (requiresAllowance) return true; const balance = await signer.getBalance("latest"); if (balance.lt(ethers.utils.parseUnits(amount, $token.decimals))) return true; From 831661517f5eea12259d9e8ce1405a130bb81663 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 28 Nov 2022 14:24:52 -0800 Subject: [PATCH 03/11] if allowance is required, show approve button instead --- .../src/components/form/BridgeForm.svelte | 46 ++++++++++++++++--- packages/bridge-ui/src/i18n.js | 3 +- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index db2fcd2880..7f47923ef1 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -60,8 +60,30 @@ return false; } + async function approve() { + try { + if (!requiresAllowance) + throw Error("does not require additional allowance"); + + const tx = await $activeBridge.Approve({ + amountInWei: ethers.utils.parseUnits(amount, $token.decimals), + signer: $signer, + contractAddress: $token.address, + spenderAddress: $chainIdToBridgeAddress.get($fromChain.id), + }); + + console.log("approved", tx); + toast.push($_("toast.transactionSent")); + } catch (e) { + console.log(e); + toast.push($_("toast.errorSendingTransaction")); + } + } + async function bridge() { try { + if (requiresAllowance) throw Error("requires additional allowance"); + const tx = await $activeBridge.Bridge({ amountInWei: ethers.utils.parseUnits(amount, $token.decimals), signer: $signer, @@ -112,10 +134,20 @@ - +{#if requiresAllowance} + +{:else} + +{/if} diff --git a/packages/bridge-ui/src/i18n.js b/packages/bridge-ui/src/i18n.js index 8adccadf3c..f4ac2eb5e2 100644 --- a/packages/bridge-ui/src/i18n.js +++ b/packages/bridge-ui/src/i18n.js @@ -9,7 +9,8 @@ function setupI18n({ withLocale: _locale } = { withLocale: "en" }) { selectToken: "Select Token", from: "From", to: "To", - bridge: "Bridge" + bridge: "Bridge", + approve: "Approve" }, nav: { connect: "Connect Wallet" From ad8af789de6a2e9402e2f911a58c361cba8cd54e Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 28 Nov 2022 14:26:11 -0800 Subject: [PATCH 04/11] set allowance required to false after successful approval --- packages/bridge-ui/src/components/form/BridgeForm.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index 7f47923ef1..9a2cc19727 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -71,8 +71,11 @@ contractAddress: $token.address, spenderAddress: $chainIdToBridgeAddress.get($fromChain.id), }); + console.log("approved, waiting for confirmations ", tx); + $signer.provider.waitForTransaction(tx.hash, 3); + + requiresAllowance = false; - console.log("approved", tx); toast.push($_("toast.transactionSent")); } catch (e) { console.log(e); From c0c3d7bf23d1f756e16e79079296479fd0e712d8 Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 28 Nov 2022 14:30:15 -0800 Subject: [PATCH 05/11] requiresallowance should take approveopts, not bridgeopts --- .../src/components/form/BridgeForm.svelte | 8 ++------ packages/bridge-ui/src/domain/bridge.ts | 2 +- packages/bridge-ui/src/erc20/bridge.spec.ts | 13 +++++++------ packages/bridge-ui/src/erc20/bridge.ts | 6 +++--- packages/bridge-ui/src/eth/bridge.spec.ts | 16 +++++----------- packages/bridge-ui/src/eth/bridge.ts | 2 +- 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index 9a2cc19727..6cb251fd33 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -40,12 +40,8 @@ ? ethers.utils.parseUnits(amt, token.decimals) : BigNumber.from(0), signer: signer, - tokenAddress: token.address, - fromChainId: fromChain.id, - toChainId: $toChain.id, - bridgeAddress: $chainIdToBridgeAddress.get(fromChain.id), - processingFeeInWei: BigNumber.from(100), - memo: "memo", + contractAddress: token.address, + spenderAddress: $chainIdToBridgeAddress.get(fromChain.id), }); } diff --git a/packages/bridge-ui/src/domain/bridge.ts b/packages/bridge-ui/src/domain/bridge.ts index 1fc0fe5077..9767f95b38 100644 --- a/packages/bridge-ui/src/domain/bridge.ts +++ b/packages/bridge-ui/src/domain/bridge.ts @@ -27,7 +27,7 @@ type BridgeOpts = { }; interface Bridge { - RequiresAllowance(opts: BridgeOpts): Promise; + RequiresAllowance(opts: ApproveOpts): Promise; Approve(opts: ApproveOpts): Promise; Bridge(opts: BridgeOpts): Promise; } diff --git a/packages/bridge-ui/src/erc20/bridge.spec.ts b/packages/bridge-ui/src/erc20/bridge.spec.ts index b6615bc79a..27c04859d7 100644 --- a/packages/bridge-ui/src/erc20/bridge.spec.ts +++ b/packages/bridge-ui/src/erc20/bridge.spec.ts @@ -2,6 +2,7 @@ import { BigNumber, Wallet } from "ethers"; import { mainnet, taiko } from "../domain/chain"; import type { ApproveOpts, Bridge, BridgeOpts } from "../domain/bridge"; import ERC20Bridge from "./bridge"; + const mockSigner = { getAddress: jest.fn(), }; @@ -61,12 +62,12 @@ describe("bridge tests", () => { const bridge: Bridge = new ERC20Bridge(); expect(mockContract.allowance).not.toHaveBeenCalled(); - const requires = await bridge.RequiresAllowance(opts); + const requires = await bridge.RequiresAllowance(approveOpts); expect(mockSigner.getAddress).toHaveBeenCalled(); expect(mockContract.allowance).toHaveBeenCalledWith( "0xfake", - opts.bridgeAddress + approveOpts.spenderAddress ); expect(requires).toBe(true); }); @@ -80,12 +81,12 @@ describe("bridge tests", () => { const bridge: Bridge = new ERC20Bridge(); expect(mockContract.allowance).not.toHaveBeenCalled(); - const requires = await bridge.RequiresAllowance(opts); + const requires = await bridge.RequiresAllowance(approveOpts); expect(mockSigner.getAddress).toHaveBeenCalled(); expect(mockContract.allowance).toHaveBeenCalledWith( "0xfake", - opts.bridgeAddress + approveOpts.spenderAddress ); expect(requires).toBe(false); }); @@ -97,12 +98,12 @@ describe("bridge tests", () => { const bridge: Bridge = new ERC20Bridge(); expect(mockContract.allowance).not.toHaveBeenCalled(); - const requires = await bridge.RequiresAllowance(opts); + const requires = await bridge.RequiresAllowance(approveOpts); expect(mockSigner.getAddress).toHaveBeenCalled(); expect(mockContract.allowance).toHaveBeenCalledWith( "0xfake", - opts.bridgeAddress + approveOpts.spenderAddress ); expect(requires).toBe(false); }); diff --git a/packages/bridge-ui/src/erc20/bridge.ts b/packages/bridge-ui/src/erc20/bridge.ts index b957f14920..0ea6063da1 100644 --- a/packages/bridge-ui/src/erc20/bridge.ts +++ b/packages/bridge-ui/src/erc20/bridge.ts @@ -20,12 +20,12 @@ class ERC20Bridge implements Bridge { return allowance.lt(amount); } - async RequiresAllowance(opts: BridgeOpts): Promise { + async RequiresAllowance(opts: ApproveOpts): Promise { return await this.spenderRequiresAllowance( - opts.tokenAddress, + opts.contractAddress, opts.signer, opts.amountInWei, - opts.bridgeAddress + opts.spenderAddress ); } diff --git a/packages/bridge-ui/src/eth/bridge.spec.ts b/packages/bridge-ui/src/eth/bridge.spec.ts index 9472c2dfbe..10e45728a2 100644 --- a/packages/bridge-ui/src/eth/bridge.spec.ts +++ b/packages/bridge-ui/src/eth/bridge.spec.ts @@ -34,18 +34,12 @@ describe("bridge tests", () => { const bridge: Bridge = new ETHBridge(); const wallet = new Wallet("0x"); - const opts: BridgeOpts = { + const requires = await bridge.RequiresAllowance({ amountInWei: BigNumber.from(1), - signer: wallet, - tokenAddress: "", - fromChainId: mainnet.id, - toChainId: taiko.id, - bridgeAddress: "0x456", - processingFeeInWei: BigNumber.from(2), - memo: "memo", - }; - - const requires = await bridge.RequiresAllowance(opts); + signer: new Wallet("0x"), + contractAddress: "0x1234", + spenderAddress: "0x", + }); expect(requires).toBe(false); }); diff --git a/packages/bridge-ui/src/eth/bridge.ts b/packages/bridge-ui/src/eth/bridge.ts index 127f8845b5..ca3cf26a7e 100644 --- a/packages/bridge-ui/src/eth/bridge.ts +++ b/packages/bridge-ui/src/eth/bridge.ts @@ -4,7 +4,7 @@ import type { ApproveOpts, Bridge, BridgeOpts } from "../domain/bridge"; import TokenVault from "../constants/abi/TokenVault"; class ETHBridge implements Bridge { - RequiresAllowance(opts: BridgeOpts): Promise { + RequiresAllowance(opts: ApproveOpts): Promise { return new Promise((resolve) => resolve(false)); } From e128f80e56d879fab7c6acf068173ed20a8649fc Mon Sep 17 00:00:00 2001 From: Jeffery Walsh Date: Mon, 28 Nov 2022 14:38:11 -0800 Subject: [PATCH 06/11] show Approve text appropriatley --- packages/bridge-ui/src/components/form/BridgeForm.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index 6cb251fd33..754da7847c 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -133,7 +133,7 @@ -{#if requiresAllowance} +{#if !requiresAllowance} {/if} From c1b490d5bde6ae7d4ecf1edd0627864a35e2b891 Mon Sep 17 00:00:00 2001 From: jeff <113397187+cyberhorsey@users.noreply.github.com> Date: Wed, 30 Nov 2022 07:44:15 -0800 Subject: [PATCH 07/11] Update packages/bridge-ui/src/components/form/BridgeForm.svelte Co-authored-by: David <104078303+davidtaikocha@users.noreply.github.com> --- packages/bridge-ui/src/components/form/BridgeForm.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index 754da7847c..942c3eafce 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -68,7 +68,7 @@ spenderAddress: $chainIdToBridgeAddress.get($fromChain.id), }); console.log("approved, waiting for confirmations ", tx); - $signer.provider.waitForTransaction(tx.hash, 3); + await $signer.provider.waitForTransaction(tx.hash, 3); requiresAllowance = false; From c31100022b7092211890b51ca9d5af84caeb549f Mon Sep 17 00:00:00 2001 From: jeff <113397187+cyberhorsey@users.noreply.github.com> Date: Wed, 30 Nov 2022 07:44:29 -0800 Subject: [PATCH 08/11] Update packages/bridge-ui/src/eth/bridge.ts Co-authored-by: David <104078303+davidtaikocha@users.noreply.github.com> --- packages/bridge-ui/src/eth/bridge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-ui/src/eth/bridge.ts b/packages/bridge-ui/src/eth/bridge.ts index ca3cf26a7e..b3809bff0c 100644 --- a/packages/bridge-ui/src/eth/bridge.ts +++ b/packages/bridge-ui/src/eth/bridge.ts @@ -5,7 +5,7 @@ import TokenVault from "../constants/abi/TokenVault"; class ETHBridge implements Bridge { RequiresAllowance(opts: ApproveOpts): Promise { - return new Promise((resolve) => resolve(false)); + return Promise.resolve(false); } // ETH does not need to be approved for transacting From d4b3fa5f560c023ebadee8a8a68f5d21b8d694a3 Mon Sep 17 00:00:00 2001 From: jeff <113397187+cyberhorsey@users.noreply.github.com> Date: Wed, 30 Nov 2022 07:46:05 -0800 Subject: [PATCH 09/11] Update packages/bridge-ui/src/components/form/BridgeForm.svelte Co-authored-by: David <104078303+davidtaikocha@users.noreply.github.com> --- packages/bridge-ui/src/components/form/BridgeForm.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-ui/src/components/form/BridgeForm.svelte b/packages/bridge-ui/src/components/form/BridgeForm.svelte index 942c3eafce..a16c33b5de 100644 --- a/packages/bridge-ui/src/components/form/BridgeForm.svelte +++ b/packages/bridge-ui/src/components/form/BridgeForm.svelte @@ -136,7 +136,7 @@ {#if !requiresAllowance}