From f8c34379c9ea0c0deadab19cda6056c66bf2c8a2 Mon Sep 17 00:00:00 2001 From: Fawad Ali Date: Mon, 14 Oct 2024 12:47:39 +0200 Subject: [PATCH] Handle permit for unifi tokens internally when depositing --- .../handlers/nucleus-teller-handler.test.ts | 39 +++++-- .../handlers/nucleus-teller-handler.ts | 100 ++++++++++++++---- lib/contracts/tokens.ts | 3 +- 3 files changed, 111 insertions(+), 31 deletions(-) diff --git a/lib/contracts/handlers/nucleus-teller-handler.test.ts b/lib/contracts/handlers/nucleus-teller-handler.test.ts index 6075a43..1a8ba77 100644 --- a/lib/contracts/handlers/nucleus-teller-handler.test.ts +++ b/lib/contracts/handlers/nucleus-teller-handler.test.ts @@ -7,8 +7,9 @@ import { mockAccount, testingUtils } from '../../../test/setup-tests'; import { NUCLEUS_TELLER_ABIS } from '../abis/nucleus-teller-abis'; import { NucleusTellerHandler } from './nucleus-teller-handler'; import { generateAddress } from '../../../test/mocks/address'; -import { Token, TOKENS_ADDRESSES } from '../tokens'; +import { Token, TOKENS_ADDRESSES, UnifiToken } from '../tokens'; import { isHash } from 'viem'; +import { mockPermitSignature } from '../../../test/mocks/permit-signature'; describe('NucleusBoringVaultHandler', () => { const contractTestingUtils = testingUtils.generateContractUtils( @@ -55,15 +56,37 @@ describe('NucleusBoringVaultHandler', () => { expect(isPaused).toBe(mockIsPaused); }); - it('should deposit without a permit', async () => { + it('should deposit with preapproval', async () => { contractTestingUtils.mockTransaction('deposit'); - const { transact, estimate } = handler.deposit( - mockAccount, - Token.pufETH, - 100n, - 0n, - ); + const { transact, estimate } = await handler.deposit({ + account: mockAccount, + token: Token.pufETH, + unifiToken: UnifiToken.unifiETH, + amount: 100n, + minimumMint: 0n, + isPreapproved: true, + }); + + expect(typeof (await estimate())).toBe('bigint'); + expect(isHash(await transact())).toBe(true); + }); + + it('should deposit with generated permit', async () => { + contractTestingUtils.mockTransaction('depositWithPermit'); + jest + .spyOn((handler as any).erc20PermitHandler, 'getPermitSignature') + .mockReturnValue(Promise.resolve(mockPermitSignature)); + + const { transact, estimate } = await handler.deposit({ + account: mockAccount, + token: Token.pufETH, + unifiToken: UnifiToken.unifiETH, + amount: 100n, + minimumMint: 0n, + // This is set by default. + // isPreapproved: false, + }); expect(typeof (await estimate())).toBe('bigint'); expect(isHash(await transact())).toBe(true); diff --git a/lib/contracts/handlers/nucleus-teller-handler.ts b/lib/contracts/handlers/nucleus-teller-handler.ts index d0dc056..5aef447 100644 --- a/lib/contracts/handlers/nucleus-teller-handler.ts +++ b/lib/contracts/handlers/nucleus-teller-handler.ts @@ -1,5 +1,3 @@ -/* istanbul ignore file */ - import { WalletClient, PublicClient, @@ -10,7 +8,17 @@ import { import { Chain, VIEM_CHAINS, ViemChain } from '../../chains/constants'; import { CONTRACT_ADDRESSES } from '../addresses'; import { NUCLEUS_TELLER_ABIS } from '../abis/nucleus-teller-abis'; -import { Token, TOKENS_ADDRESSES } from '../tokens'; +import { Token, TOKENS_ADDRESSES, UnifiToken } from '../tokens'; +import { ERC20PermitHandler } from './erc20-permit-handler'; + +export type DepositParams = { + account: Address; + token: Token; + unifiToken: UnifiToken; + amount: bigint; + minimumMint: bigint; + isPreapproved?: boolean; +}; export type DepositWithPermitParams = { account: Address; @@ -28,6 +36,7 @@ export type DepositWithPermitParams = { */ export class NucleusTellerHandler { private viemChain: ViemChain; + private erc20PermitHandler: ERC20PermitHandler; /** * Create the handler for processing tokens. @@ -44,6 +53,11 @@ export class NucleusTellerHandler { private publicClient: PublicClient, ) { this.viemChain = VIEM_CHAINS[chain]; + this.erc20PermitHandler = new ERC20PermitHandler( + chain, + walletClient, + publicClient, + ); } /** @@ -111,38 +125,80 @@ export class NucleusTellerHandler { } /** - * Deposit the given token for staking. + * Deposit the given token for staking. This doesn't make the + * transaction but returns two methods namely `transact` and + * `estimate`. * - * @param walletAddress Address of the caller of the transaction. - * @param token Token to deposit. - * @param amount Amount of the token to deposit. - * @param minimumMint Minimum amount of shares to mint. + * @param params Deposit parameters. + * @param params.account Address of the caller of the transaction. + * @param params.token Token to deposit. + * @param params.unifiToken UniFi token to get in return of the deposit. + * @param params.amount Amount of the token to deposit. + * @param params.minimumMint Minimum amount of shares to mint. + * @param params.isPreapproved Whether the token is preapproved. * @returns `transact: () => Promise
` - Used to make the * transaction with the given value. * * `estimate: () => Promise` - Gas estimate of the * transaction. */ - public deposit( - walletAddress: Address, - token: Token, - amount: bigint, - minimumMint: bigint, - ) { + public async deposit(params: DepositParams) { + const { + token, + unifiToken, + account, + amount, + minimumMint, + isPreapproved = false, + } = params; const tokenAddress = TOKENS_ADDRESSES[token][this.chain]; + if (isPreapproved) { + return { + transact: () => + this.getContract().write.deposit( + [tokenAddress, amount, minimumMint], + { + account, + chain: this.viemChain, + }, + ), + estimate: () => + this.getContract().estimateGas.deposit( + [tokenAddress, amount, minimumMint], + { account }, + ), + }; + } + + const { r, s, v, deadline } = await this.erc20PermitHandler + .withToken(token) + .getPermitSignature( + account, + // The UniFi token contract is the spender. + TOKENS_ADDRESSES[unifiToken][this.chain], + amount, + ); + + const depositArgs = [ + tokenAddress, + amount, + minimumMint, + deadline, + Number(v), + r, + s, + ]; + const transact = () => - this.getContract().write.deposit([tokenAddress, amount, minimumMint], { - account: walletAddress, + this.getContract().write.depositWithPermit(depositArgs, { + account, chain: this.viemChain, }); const estimate = () => - this.getContract().estimateGas.deposit( - [tokenAddress, amount, minimumMint], - { - account: walletAddress, - }, - ); + this.getContract().estimateGas.depositWithPermit(depositArgs, { + account, + }); return { transact, estimate }; } diff --git a/lib/contracts/tokens.ts b/lib/contracts/tokens.ts index fa3a909..e6b8c46 100644 --- a/lib/contracts/tokens.ts +++ b/lib/contracts/tokens.ts @@ -92,7 +92,8 @@ export const TOKENS_PERMIT_VERSION: { [key in AnyToken]: string } = { [Token.pufETH]: '1', // UniFi Tokens - [UnifiToken.unifiETH]: '', + // https://etherscan.io/address/0x196ead472583bc1e9af7a05f860d9857e1bd3dcc#code#F7#L172 + [UnifiToken.unifiETH]: '1', }; export const TOKENS_SALT: Partial<{