Commit 088f1d9a authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

test(e2e): use aliases for permit2 test (#6656)

* test(e2e): disable video compression

* refactor: improve popupList impl/test

* test(e2e): log JSON-RPC calls

* fix: retry backoff logic

* test(e2e): wait for hardhat/popup assertions

* fix: remove transactions after expired

* test(e2e): re-enable other swap tests
Co-authored-by: default avatarJordan Frankfurt <jordanwfrankfurt@gmail.com>

* chore: rm console.log

* fix: expire txs after 6h

* refactor: dry trade info

* test(e2e): use default deadline

* test(e2e): use aliases for permit2 test

* test(e2e): automine/off in beforeEach

* test(e2e): rm intermediate screen with no pause

* fix merge

* fix only

---------
Co-authored-by: default avatarJordan Frankfurt <jordanwfrankfurt@gmail.com>
parent 89a7d98b
...@@ -5,212 +5,192 @@ import { getTestSelector } from '../utils' ...@@ -5,212 +5,192 @@ import { getTestSelector } from '../utils'
/** Initiates a swap. */ /** Initiates a swap. */
function initiateSwap() { function initiateSwap() {
// The swap button is re-rendered once enable, so we must wait until the original button is not disabled to re-select the appropriate button. // The swap button is re-rendered once enabled, so we must wait until the original button is not disabled to re-select the appropriate button.
cy.get('#swap-button').should('not.be.disabled') cy.get('#swap-button').should('not.be.disabled')
// Completes the swap. // Completes the swap.
cy.get('#swap-button').click() cy.get('#swap-button').click()
cy.get(getTestSelector('confirm-swap-button')).click() cy.contains('Confirm swap').click()
} }
describe('Permit2', () => { describe('Permit2', () => {
// The same tokens & swap-amount combination is used for all permit2 tests. // The same tokens are used for all permit2 tests.
const INPUT_TOKEN = DAI const INPUT_TOKEN = DAI
const OUTPUT_TOKEN = USDC_MAINNET const OUTPUT_TOKEN = USDC_MAINNET
const TEST_BALANCE_INCREMENT = 0.01
beforeEach(() => { beforeEach(() => {
// Sets up a swap between INPUT_TOKEN and OUTPUT_TOKEN. // Sets up a swap between INPUT_TOKEN and OUTPUT_TOKEN.
cy.visit(`/swap/?inputCurrency=${INPUT_TOKEN.address}&outputCurrency=${OUTPUT_TOKEN.address}`, { cy.visit(`/swap/?inputCurrency=${INPUT_TOKEN.address}&outputCurrency=${OUTPUT_TOKEN.address}`, {
ethereum: 'hardhat', ethereum: 'hardhat',
}) })
cy.get('#swap-currency-input .token-amount-input').type(TEST_BALANCE_INCREMENT.toString()) cy.get('#swap-currency-input .token-amount-input').type('0.01')
}) })
/** Asserts permit2 has a max approval for spend of the input token on-chain. */ /** Asserts permit2 has a max approval for spend of the input token on-chain. */
function expectTokenAllowanceForPermit2ToBeMax() { function expectTokenAllowanceForPermit2ToBeMax() {
// check token approval // check token approval
return cy cy.hardhat()
.hardhat()
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN })) .then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }))
.should('deep.equal', MaxUint256) .should('deep.equal', MaxUint256)
} }
/** Asserts the universal router has a max permit2 approval for spend of the input token on-chain. */ /** Asserts the universal router has a max permit2 approval for spend of the input token on-chain. */
function expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime: number) { function expectPermit2AllowanceForUniversalRouterToBeMax() {
return cy cy.hardhat()
.hardhat()
.then((hardhat) => hardhat.approval.getPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN })) .then((hardhat) => hardhat.approval.getPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN }))
.then((allowance) => { .then((allowance) => {
cy.wrap(MaxUint160.eq(allowance.amount)).should('eq', true) cy.wrap(MaxUint160.eq(allowance.amount)).should('eq', true)
// Asserts that the on-chain expiration is in 30 days, within a tolerance of 40 seconds. // Asserts that the on-chain expiration is in 30 days, within a tolerance of 40 seconds.
const expected = Math.floor((approvalTime + 2_592_000_000) / 1000) const THIRTY_DAYS_SECONDS = 2_592_000
const expected = Math.floor(Date.now() / 1000 + THIRTY_DAYS_SECONDS)
cy.wrap(allowance.expiration).should('be.closeTo', expected, 40) cy.wrap(allowance.expiration).should('be.closeTo', expected, 40)
}) })
} }
it('swaps when user has already approved token and permit2', () => { describe('approval process (with intermediate screens)', () => {
cy.hardhat().then(({ approval, wallet }) => { // Turn off automine so that intermediate screens are available to assert on.
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }) beforeEach(() => cy.hardhat({ automine: false }))
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN })
})
initiateSwap()
cy.get(getTestSelector('confirmation-close-icon')).click()
// Verifies that there is a successful swap notification.
cy.contains('Swapped').should('exist')
})
it('swaps after completing full permit2 approval process', () => { it('swaps after completing full permit2 approval process', () => {
cy.hardhat().then(({ provider }) => { initiateSwap()
cy.spy(provider, 'send').as('permitApprovalSpy')
})
initiateSwap()
cy.contains('Enable spending DAI on Uniswap').should('exist')
cy.contains('Approved').should('exist')
cy.contains('Allow DAI to be used for swapping').should('exist') // Verify token approval
cy.contains('Confirm Swap').should('exist') cy.contains('Enable spending DAI on Uniswap')
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax()
cy.then(() => { // Verify permit2 approval
const approvalTime = Date.now() cy.contains('Allow DAI to be used for swapping')
cy.wait('@eth_signTypedData_v4')
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
})
cy.contains('Swapped').should('exist') it('swaps with existing permit approval and missing token approval', () => {
cy.hardhat().then(async (hardhat) => {
await hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN })
await hardhat.mine()
})
initiateSwap()
// Verify token approval
cy.contains('Enable spending DAI on Uniswap')
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax() expectTokenAllowanceForPermit2ToBeMax()
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
cy.get('@permitApprovalSpy').should('have.been.calledWith', 'eth_signTypedData_v4') // Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
}) })
}) })
it('swaps when user has already approved token and permit2', () => {
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }),
])
)
initiateSwap()
// Verify transaction
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
})
it('swaps after handling user rejection of both approval and signature', () => { it('swaps after handling user rejection of both approval and signature', () => {
const USER_REJECTION = { code: 4001 } const USER_REJECTION = { code: 4001 }
cy.hardhat().then((hardhat) => { cy.hardhat().then((hardhat) => {
const tokenApprovalStub = cy.stub(hardhat.wallet, 'sendTransaction') // Reject token approval
tokenApprovalStub.rejects(USER_REJECTION) // reject token approval const tokenApprovalStub = cy.stub(hardhat.wallet, 'sendTransaction').log(false)
const permitApprovalStub = cy.stub(hardhat.provider, 'send') tokenApprovalStub.rejects(USER_REJECTION) // rejects token approval
permitApprovalStub.withArgs('eth_signTypedData_v4').rejects(USER_REJECTION) // reject permit approval
permitApprovalStub.callThrough() // allows non-eth_signTypedData_v4 send calls to return non-stubbed values
initiateSwap() initiateSwap()
// tokenApprovalStub should reject here, and the modal should revert to the review state. // Verify token approval rejection
cy.contains('Review swap').should('be.visible') cy.wrap(tokenApprovalStub).should('be.calledOnce')
cy.then(() => {
// The user is now allowing approval, but the permit2 signature will be rejected by the user (permitApprovalStub).
tokenApprovalStub.restore() // allow token approval
})
cy.get(getTestSelector('confirm-swap-button')).click()
cy.contains('Enable spending DAI on Uniswap').should('exist')
cy.contains('Approved').should('exist')
// permitApprovalStub should reject here, and the modal should revert to the review state.
cy.contains('Review swap') cy.contains('Review swap')
.should('be.visible')
.then(() => {
permitApprovalStub.restore() // allow permit approval
})
cy.get(getTestSelector('confirm-swap-button')).click()
// The swap should now be able to proceed, as the permit2 signature will be accepted by the user. // Allow token approval
const approvalTime = Date.now() cy.then(() => tokenApprovalStub.restore())
cy.contains('Confirm Swap').should('exist') // Reject permit2 approval
cy.contains('Swapped').should('exist') const permitApprovalStub = cy.stub(hardhat.provider, 'send').log(false)
permitApprovalStub.withArgs('eth_signTypedData_v4').rejects(USER_REJECTION) // rejects permit approval
permitApprovalStub.callThrough() // allows non-eth_signTypedData_v4 send calls to return non-stubbed values
cy.contains('Confirm swap').click()
// Verify token approval
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax() expectTokenAllowanceForPermit2ToBeMax()
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
})
})
it('swaps with existing token approval and missing permit approval', () => { // Verify permit2 approval rejection
cy.hardhat().then(({ approval, wallet, provider }) => { cy.wrap(permitApprovalStub).should('be.calledWith', 'eth_signTypedData_v4')
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }) cy.contains('Review swap')
cy.spy(provider, 'send').as('permitApprovalSpy')
})
cy.then(() => initiateSwap())
cy.then(() => {
const approvalTime = Date.now()
cy.contains('Confirm Swap').should('exist') // Allow permit2 approval
cy.contains('Swapped').should('exist') cy.then(() => permitApprovalStub.restore())
cy.contains('Confirm swap').click()
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime) // Verify permit2 approval
cy.get('@permitApprovalSpy').should('have.been.calledWith', 'eth_signTypedData_v4') cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
}) })
}) })
it('swaps with existing permit approval and missing token approval', () => { it('prompts token approval when existing approval amount is too low', () => {
cy.hardhat().then(({ approval, wallet }) => approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN })) cy.hardhat().then(({ approval, wallet }) =>
cy.then(() => { Promise.all([
initiateSwap() approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }),
}) approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }, 1),
cy.then(() => { ])
const approvalTime = Date.now() )
initiateSwap()
cy.contains('Confirm Swap').should('exist')
cy.contains('Swapped').should('exist')
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime) // Verify token approval
}) cy.get(getTestSelector('popups')).contains('Approved')
expectPermit2AllowanceForUniversalRouterToBeMax()
}) })
it('prompts signature when existing permit approval is expired', () => { it('prompts signature when existing permit approval is expired', () => {
const expiredAllowance = { expiration: Math.floor((Date.now() - 1) / 1000) } const expiredAllowance = { expiration: Math.floor((Date.now() - 1) / 1000) }
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, expiredAllowance),
])
)
initiateSwap()
cy.hardhat().then(({ approval, wallet, provider }) => { // Verify permit2 approval
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }) cy.wait('@eth_signTypedData_v4')
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, expiredAllowance) cy.contains('Success')
cy.spy(provider, 'send').as('permitApprovalSpy') cy.get(getTestSelector('popups')).contains('Swapped')
}) expectPermit2AllowanceForUniversalRouterToBeMax()
cy.then(() => {
initiateSwap()
})
cy.then(() => {
const approvalTime = Date.now()
cy.contains('Confirm Swap').should('exist')
cy.contains('Swapped').should('exist')
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
cy.get('@permitApprovalSpy').should('have.been.calledWith', 'eth_signTypedData_v4')
})
}) })
it('prompts signature when existing permit approval amount is too low', () => { it('prompts signature when existing permit approval amount is too low', () => {
const smallAllowance = { amount: 1 } const smallAllowance = { amount: 1 }
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, smallAllowance),
])
)
initiateSwap()
cy.hardhat().then(({ approval, wallet, provider }) => { // Verify permit2 approval
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }) cy.wait('@eth_signTypedData_v4')
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, smallAllowance) cy.contains('Success')
cy.spy(provider, 'send').as('permitApprovalSpy') cy.get(getTestSelector('popups')).contains('Swapped')
initiateSwap() expectPermit2AllowanceForUniversalRouterToBeMax()
const approvalTime = Date.now()
cy.contains('Confirm Swap').should('exist')
cy.contains('Swapped').should('exist')
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
cy.get('@permitApprovalSpy').should('have.been.calledWith', 'eth_signTypedData_v4')
})
})
it('prompts token approval when existing approval amount is too low', () => {
cy.hardhat()
.then(({ approval, wallet }) => {
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN })
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }, 1)
})
.then(() => {
initiateSwap()
const approvalTime = Date.now()
cy.contains('Enable spending DAI on Uniswap').should('exist')
cy.contains('Confirm Swap').should('exist')
cy.contains('Swapped').should('exist')
expectPermit2AllowanceForUniversalRouterToBeMax(approvalTime)
})
}) })
}) })
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment