diff --git a/test/integration-tests/Router.test.ts b/test/integration-tests/Router.test.ts index ce21cef6..49dd8ca9 100644 --- a/test/integration-tests/Router.test.ts +++ b/test/integration-tests/Router.test.ts @@ -22,6 +22,7 @@ import { getAdvancedOrderParams, AdvancedOrder, Order, + defaultAvailableAdvancedOrders, } from './shared/protocolHelpers/seaport' import { resetFork, WETH, DAI } from './shared/mainnetForkHelpers' import { CommandType, RoutePlanner } from './shared/planner' @@ -197,14 +198,112 @@ describe('Router', () => { [DAI.address, WETH.address], router.address, ]) - planner.addCommand(CommandType.UNWRAP_WETH, [alice.address, value]) + planner.addCommand(CommandType.UNWRAP_WETH, [router.address, value]) planner.addCommand(CommandType.SEAPORT, [value.toString(), calldata]) const { commands, inputs } = planner const covenBalanceBefore = await covenContract.balanceOf(alice.address) - await router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE, { value }) + await router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE) const covenBalanceAfter = await covenContract.balanceOf(alice.address) expect(covenBalanceAfter.sub(covenBalanceBefore)).to.eq(1) }) + + it('completes a trade for ERC20 --> ETH --> NFTs, invalid Seaport order', async () => { + const maxAmountIn = expandTo18DecimalsBN(100_000) + // in this case there is leftover dai in the router, and the unspent eth gets sent to alice + await daiContract.transfer(router.address, maxAmountIn) + + let invalidSeaportOrder = JSON.parse(JSON.stringify(seaportOrders[0])) + const { order: seaportOrder, value: seaportValue } = getOrderParams(invalidSeaportOrder) + let nftxValue: BigNumber = expandTo18DecimalsBN(4) + let totalValue = seaportValue.add(nftxValue) + + // invalidate Seaport order + invalidSeaportOrder.protocol_data.signature = '0xdeadbeef' + const calldataOpensea = seaportInterface.encodeFunctionData('fulfillOrder', [seaportOrder, OPENSEA_CONDUIT_KEY]) + + // valid NFTX order + let numCovensNFTX = 2 + const calldataNFTX = nftxZapInterface.encodeFunctionData('buyAndRedeem', [ + NFTX_COVEN_VAULT_ID, + numCovensNFTX, + [], + [WETH.address, NFTX_COVEN_VAULT], + alice.address, + ]) + + planner.addCommand(CommandType.V2_SWAP_EXACT_OUT, [ + totalValue, + maxAmountIn, + [DAI.address, WETH.address], + router.address, + ]) + planner.addCommand(CommandType.UNWRAP_WETH, [router.address, totalValue]) + planner.addCommand(CommandType.SEAPORT, [seaportValue.toString(), calldataOpensea], true) + + planner.addCommand(CommandType.NFTX, [nftxValue, calldataNFTX]) + + const { commands, inputs } = planner + + const routerEthBalanceBefore = await ethers.provider.getBalance(router.address) + const covenBalanceBefore = await covenContract.balanceOf(alice.address) + + await router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE) + + const covenBalanceAfter = await covenContract.balanceOf(alice.address) + const routerEthBalanceAfter = await ethers.provider.getBalance(router.address) + + expect(covenBalanceAfter.sub(covenBalanceBefore)).to.eq(numCovensNFTX) + expect(routerEthBalanceAfter).to.eq(routerEthBalanceBefore) + }) + + it('completes a trade for ERC20 --> ETH --> NFTs with Seaport, fulfillAvailableAdvancedOrders fill', async () => { + const maxAmountIn = expandTo18DecimalsBN(100_000) + // in this case there is leftover dai in the router and all eth gets spent on the nfts + await daiContract.transfer(router.address, maxAmountIn) + + const { advancedOrder: advancedOrder0, value: value1 } = getAdvancedOrderParams(seaportOrders[0]) + const { advancedOrder: advancedOrder1, value: value2 } = getAdvancedOrderParams(seaportOrders[1]) + const params0 = advancedOrder0.parameters + const params1 = advancedOrder1.parameters + const totalValue = value1.add(value2) + + const calldata = defaultAvailableAdvancedOrders(alice.address, advancedOrder0, advancedOrder1) + + planner.addCommand(CommandType.V2_SWAP_EXACT_OUT, [ + totalValue, + maxAmountIn, + [DAI.address, WETH.address], + router.address, + ]) + planner.addCommand(CommandType.UNWRAP_WETH, [router.address, totalValue]) + + planner.addCommand(CommandType.SEAPORT, [totalValue, calldata]) + const { commands, inputs } = planner + + const nftId0 = params0.offer[0].identifierOrCriteria + const nftId1 = params1.offer[0].identifierOrCriteria + + const owner0Before = await covenContract.ownerOf(nftId0) + const owner1Before = await covenContract.ownerOf(nftId1) + const ethBefore = await ethers.provider.getBalance(alice.address) + const routerEthBalanceBefore = await ethers.provider.getBalance(router.address) + + const receipt = await (await router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)).wait() + + const owner0After = await covenContract.ownerOf(nftId0) + const owner1After = await covenContract.ownerOf(nftId1) + const ethAfter = await ethers.provider.getBalance(alice.address) + const routerEthBalanceAfter = await ethers.provider.getBalance(router.address) + const gasSpent = receipt.gasUsed.mul(receipt.effectiveGasPrice) + const ethDelta = ethBefore.sub(ethAfter) + + expect(owner0Before.toLowerCase()).to.eq(params0.offerer) + expect(owner1Before.toLowerCase()).to.eq(params1.offerer) + expect(owner0After).to.eq(alice.address) + expect(owner1After).to.eq(alice.address) + expect(ethDelta).to.eq(gasSpent) // eth spent only on gas bc trade came from DAI + expect(routerEthBalanceBefore).to.eq(routerEthBalanceAfter) // ensure no eth is left in the router + }) }) }) diff --git a/test/integration-tests/SeaportRouter.test.ts b/test/integration-tests/SeaportRouter.test.ts index 135f459d..55b25a0e 100644 --- a/test/integration-tests/SeaportRouter.test.ts +++ b/test/integration-tests/SeaportRouter.test.ts @@ -4,7 +4,12 @@ import { expect } from './shared/expect' import { BigNumber } from 'ethers' import { Router } from '../../typechain' import { abi as ERC721_ABI } from '../../artifacts/solmate/src/tokens/ERC721.sol/ERC721.json' -import { seaportOrders, seaportInterface, getAdvancedOrderParams } from './shared/protocolHelpers/seaport' +import { + seaportOrders, + seaportInterface, + getAdvancedOrderParams, + defaultAvailableAdvancedOrders, +} from './shared/protocolHelpers/seaport' import deployRouter from './shared/deployRouter' import { resetFork } from './shared/mainnetForkHelpers' import { ALICE_ADDRESS, COVEN_ADDRESS, DEADLINE, OPENSEA_CONDUIT_KEY } from './shared/constants' @@ -62,40 +67,23 @@ describe('Seaport', () => { const params0 = advancedOrder0.parameters const params1 = advancedOrder1.parameters const value = value1.add(value2) - const considerationFulfillment = [ - [[0, 0]], - [ - [0, 1], - [1, 1], - ], - [ - [0, 2], - [1, 2], - ], - [[1, 0]], - ] - - const calldata = seaportInterface.encodeFunctionData('fulfillAvailableAdvancedOrders', [ - [advancedOrder0, advancedOrder1], - [], - [[[0, 0]], [[1, 0]]], - considerationFulfillment, - OPENSEA_CONDUIT_KEY, - alice.address, - 100, - ]) + + const calldata = defaultAvailableAdvancedOrders(alice.address, advancedOrder0, advancedOrder1) planner.addCommand(CommandType.SEAPORT, [value.toString(), calldata]) const { commands, inputs } = planner - const owner0Before = await covenContract.ownerOf(params0.offer[0].identifierOrCriteria) - const owner1Before = await covenContract.ownerOf(params1.offer[0].identifierOrCriteria) + const nftId0 = params0.offer[0].identifierOrCriteria + const nftId1 = params1.offer[0].identifierOrCriteria + + const owner0Before = await covenContract.ownerOf(nftId0) + const owner1Before = await covenContract.ownerOf(nftId1) const ethBefore = await ethers.provider.getBalance(alice.address) const receipt = await (await router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE, { value })).wait() - const owner0After = await covenContract.ownerOf(params0.offer[0].identifierOrCriteria) - const owner1After = await covenContract.ownerOf(params1.offer[0].identifierOrCriteria) + const owner0After = await covenContract.ownerOf(nftId0) + const owner1After = await covenContract.ownerOf(nftId1) const ethAfter = await ethers.provider.getBalance(alice.address) const gasSpent = receipt.gasUsed.mul(receipt.effectiveGasPrice) const ethDelta = ethBefore.sub(ethAfter) diff --git a/test/integration-tests/shared/protocolHelpers/seaport.ts b/test/integration-tests/shared/protocolHelpers/seaport.ts index becc534c..7affa408 100644 --- a/test/integration-tests/shared/protocolHelpers/seaport.ts +++ b/test/integration-tests/shared/protocolHelpers/seaport.ts @@ -3,6 +3,7 @@ import { BigNumber } from 'ethers' import { expandTo18DecimalsBN } from '../helpers' import fs from 'fs' import hre from 'hardhat' +import { OPENSEA_CONDUIT_KEY } from '../constants' const { ethers } = hre export const seaportOrders = JSON.parse( @@ -75,3 +76,34 @@ export function calculateValue(considerations: ConsiderationItem[]): BigNumber { expandTo18DecimalsBN(0) ) } + +export function defaultAvailableAdvancedOrders( + address: string, + advancedOrder0: AdvancedOrder, + advancedOrder1: AdvancedOrder +): string { + const considerationFulfillment = [ + [[0, 0]], + [ + [0, 1], + [1, 1], + ], + [ + [0, 2], + [1, 2], + ], + [[1, 0]], + ] + + const calldata = seaportInterface.encodeFunctionData('fulfillAvailableAdvancedOrders', [ + [advancedOrder0, advancedOrder1], + [], + [[[0, 0]], [[1, 0]]], + considerationFulfillment, + OPENSEA_CONDUIT_KEY, + address, + 100, + ]) + + return calldata +}