Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v4 to receive #376

Merged
merged 4 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/UniversalRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,6 @@ contract UniversalRouter is IUniversalRouter, Dispatcher {

/// @notice To receive ETH from WETH
receive() external payable {
if (msg.sender != address(WETH9)) revert InvalidEthSender();
if (msg.sender != address(WETH9) && msg.sender != address(poolManager)) revert InvalidEthSender();
}
}
6 changes: 3 additions & 3 deletions contracts/base/Dispatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,12 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, V4SwapRout
} else if (command == Commands.WRAP_ETH) {
// equivalent: abi.decode(inputs, (address, uint256))
address recipient;
uint256 amountMin;
uint256 amount;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats the name change motivation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the parameter is not a slippage check "minimum amount to wrap"... its just an amount to wrap. For unwrap and sweep it is a slippage check so i think the naming must have been copied across. For wrapping its just an amount.

assembly {
recipient := calldataload(inputs.offset)
amountMin := calldataload(add(inputs.offset, 0x20))
amount := calldataload(add(inputs.offset, 0x20))
}
Payments.wrapETH(map(recipient), amountMin);
Payments.wrapETH(map(recipient), amount);
} else if (command == Commands.UNWRAP_WETH) {
// equivalent: abi.decode(inputs, (address, uint256))
address recipient;
Expand Down
104 changes: 98 additions & 6 deletions test/integration-tests/UniswapMixed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import type { Contract } from '@ethersproject/contracts'
import { Pair } from '@uniswap/v2-sdk'
import { expect } from './shared/expect'
import { BigNumber } from 'ethers'
import { IPermit2, UniversalRouter } from '../../typechain'
import { IPermit2, PoolManager, PositionManager, UniversalRouter } from '../../typechain'
import { abi as TOKEN_ABI } from '../../artifacts/solmate/src/tokens/ERC20.sol/ERC20.json'
import { resetFork, WETH, DAI, USDC, USDT, PERMIT2 } from './shared/mainnetForkHelpers'
import {
ADDRESS_THIS,
ALICE_ADDRESS,
CONTRACT_BALANCE,
DEADLINE,
ETH_ADDRESS,
MAX_UINT,
MAX_UINT160,
MSG_SENDER,
OPEN_DELTA,
SOURCE_MSG_SENDER,
SOURCE_ROUTER,
} from './shared/constants'
Expand All @@ -24,9 +26,19 @@ import hre from 'hardhat'
import { getPermitBatchSignature } from './shared/protocolHelpers/permit2'
import { encodePathExactInput, encodePathExactOutput } from './shared/swapRouter02Helpers'
import { executeRouter } from './shared/executeRouter'
import { Actions, V4Planner } from './shared/v4Planner'
import {
addLiquidityToV4Pool,
DAI_USDC,
deployV4PoolManager,
encodeMultihopExactInPath,
ETH_USDC,
initializeV4Pool,
USDC_WETH,
} from './shared/v4Helpers'
const { ethers } = hre

describe('Uniswap V2 and V3 Tests:', () => {
describe('Uniswap V2, V3, and V4 Tests:', () => {
let alice: SignerWithAddress
let bob: SignerWithAddress
let router: UniversalRouter
Expand All @@ -35,6 +47,12 @@ describe('Uniswap V2 and V3 Tests:', () => {
let wethContract: Contract
let usdcContract: Contract
let planner: RoutePlanner
let v4Planner: V4Planner
let v4PoolManager: PoolManager
let v4PositionManager: PositionManager

// current market ETH price at block
const USD_ETH_PRICE = 3820

beforeEach(async () => {
await resetFork()
Expand All @@ -48,13 +66,20 @@ describe('Uniswap V2 and V3 Tests:', () => {
wethContract = new ethers.Contract(WETH.address, TOKEN_ABI, bob)
usdcContract = new ethers.Contract(USDC.address, TOKEN_ABI, bob)
permit2 = PERMIT2.connect(bob) as IPermit2
router = (await deployUniversalRouter()) as UniversalRouter

v4PoolManager = (await deployV4PoolManager()).connect(bob) as PoolManager
router = (await deployUniversalRouter(v4PoolManager.address)).connect(bob) as UniversalRouter

v4PositionManager = (await ethers.getContractAt('PositionManager', await router.V4_POSITION_MANAGER())).connect(
bob
) as PositionManager
planner = new RoutePlanner()
v4Planner = new V4Planner()

// alice gives bob some tokens
await daiContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(100000))
await wethContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(100))
await usdcContract.connect(alice).transfer(bob.address, expandTo6DecimalsBN(100000))
await daiContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(1000000))
await wethContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(1000))
await usdcContract.connect(alice).transfer(bob.address, expandTo6DecimalsBN(50000000))

// Bob max-approves the permit2 contract to access his DAI and WETH
await daiContract.connect(bob).approve(permit2.address, MAX_UINT)
Expand All @@ -65,6 +90,21 @@ describe('Uniswap V2 and V3 Tests:', () => {
await permit2.approve(DAI.address, router.address, MAX_UINT160, DEADLINE)
await permit2.approve(WETH.address, router.address, MAX_UINT160, DEADLINE)
await permit2.approve(USDC.address, router.address, MAX_UINT160, DEADLINE)

// for setting up pools, bob gives position manager approval on permit2
await permit2.approve(DAI.address, v4PositionManager.address, MAX_UINT160, DEADLINE)
await permit2.approve(WETH.address, v4PositionManager.address, MAX_UINT160, DEADLINE)
await permit2.approve(USDC.address, v4PositionManager.address, MAX_UINT160, DEADLINE)

// bob initializes 3 v4 pools
await initializeV4Pool(v4PoolManager, USDC_WETH.poolKey, USDC_WETH.price)
await initializeV4Pool(v4PoolManager, DAI_USDC.poolKey, DAI_USDC.price)
await initializeV4Pool(v4PoolManager, ETH_USDC.poolKey, ETH_USDC.price)

// bob adds liquidity to the pools
await addLiquidityToV4Pool(v4PositionManager, USDC_WETH, expandTo18DecimalsBN(2).toString(), bob)
await addLiquidityToV4Pool(v4PositionManager, DAI_USDC, expandTo18DecimalsBN(400).toString(), bob)
await addLiquidityToV4Pool(v4PositionManager, ETH_USDC, expandTo18DecimalsBN(0.1).toString(), bob)
})

describe('Interleaving routes', () => {
Expand Down Expand Up @@ -516,6 +556,58 @@ describe('Uniswap V2 and V3 Tests:', () => {
expect(ethBalanceAfter.sub(ethBalanceBefore)).to.eq(fullAmountOut.sub(gasSpent))
})

it('ERC20 --> ERC20 split V4 and V4 different routes, with wrap, aggregate slippage', async () => {
// route 1: DAI -> USDC -> WETH
// route 2: DAI -> USDC -> ETH, then router wraps ETH -> WETH
const route1 = [DAI_USDC.poolKey, USDC_WETH.poolKey]
const route2 = [DAI_USDC.poolKey, ETH_USDC.poolKey]
const v4AmountIn1 = expandTo18DecimalsBN(100)
const v4AmountIn2 = expandTo18DecimalsBN(150)
const aggregateMinOut = expandTo18DecimalsBN(250 / Math.floor(USD_ETH_PRICE * 1.01))

let currencyIn = daiContract.address
// add first split to v4 planner
v4Planner.addAction(Actions.SWAP_EXACT_IN, [
{
currencyIn,
path: encodeMultihopExactInPath(route1, currencyIn),
amountIn: v4AmountIn1,
amountOutMinimum: 0,
},
])
// add second split to v4 planner
v4Planner.addAction(Actions.SWAP_EXACT_IN, [
{
currencyIn,
path: encodeMultihopExactInPath(route2, currencyIn),
amountIn: v4AmountIn2,
amountOutMinimum: 0,
},
])
// settle all DAI with no limit
v4Planner.addAction(Actions.SETTLE_ALL, [currencyIn, v4AmountIn1.add(v4AmountIn2)])
// take all the WETH and all the ETH into the router
v4Planner.addAction(Actions.TAKE, [WETH.address, ADDRESS_THIS, OPEN_DELTA])
v4Planner.addAction(Actions.TAKE, [ETH_ADDRESS, ADDRESS_THIS, OPEN_DELTA])

planner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params])
// wrap all the ETH into WETH
planner.addCommand(CommandType.WRAP_ETH, [ADDRESS_THIS, CONTRACT_BALANCE])
// now we can send the WETH to the user, with aggregate slippage check
planner.addCommand(CommandType.SWEEP, [WETH.address, MSG_SENDER, aggregateMinOut])

const { daiBalanceBefore, daiBalanceAfter, wethBalanceBefore, wethBalanceAfter } = await executeRouter(
planner,
bob,
router,
wethContract,
daiContract,
usdcContract
)
expect(wethBalanceAfter.sub(wethBalanceBefore)).to.be.gte(aggregateMinOut)
expect(daiBalanceBefore.sub(daiBalanceAfter)).to.be.eq(v4AmountIn1.add(v4AmountIn2))
})

describe('Batch reverts', () => {
let subplan: RoutePlanner
const planOneTokens = [DAI.address, WETH.address]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ Object {
exports[`Payments Gas Tests Individual Command Tests gas: UNWRAP_WETH 1`] = `
Object {
"calldataByteLength": 324,
"gasUsed": 44600,
"gasUsed": 44620,
}
`;

exports[`Payments Gas Tests Individual Command Tests gas: UNWRAP_WETH_WITH_FEE 1`] = `
Object {
"calldataByteLength": 644,
"gasUsed": 51039,
"gasUsed": 51059,
}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ Object {
exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ETH split V2 and V3, exactOut, one hop 1`] = `
Object {
"calldataByteLength": 964,
"gasUsed": 192316,
"gasUsed": 192336,
}
`;

exports[`Uniswap Gas Tests Mixing V2 and V3 with Universal Router. Split routes gas: ERC20 --> ETH split V2 and V3, one hop 1`] = `
Object {
"calldataByteLength": 964,
"gasUsed": 184980,
"gasUsed": 185000,
}
`;

Expand Down Expand Up @@ -206,21 +206,21 @@ Object {
exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ERC20 --> ETH gas: exactIn, one trade, one hop 1`] = `
Object {
"calldataByteLength": 644,
"gasUsed": 123179,
"gasUsed": 123199,
}
`;

exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ERC20 --> ETH gas: exactOut, one trade, one hop 1`] = `
Object {
"calldataByteLength": 804,
"gasUsed": 128023,
"gasUsed": 128043,
}
`;

exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ERC20 --> ETH gas: exactOut, with ETH fee 1`] = `
Object {
"calldataByteLength": 964,
"gasUsed": 136047,
"gasUsed": 136067,
}
`;

Expand All @@ -234,7 +234,7 @@ Object {
exports[`Uniswap Gas Tests Trade on UniswapV2 with Universal Router. ETH --> ERC20 gas: exactOut, one trade, one hop 1`] = `
Object {
"calldataByteLength": 772,
"gasUsed": 125212,
"gasUsed": 125232,
}
`;

Expand Down Expand Up @@ -325,14 +325,14 @@ Object {
exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ETH gas: exactIn swap 1`] = `
Object {
"calldataByteLength": 644,
"gasUsed": 121969,
"gasUsed": 121989,
}
`;

exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ERC20 --> ETH gas: exactOut swap 1`] = `
Object {
"calldataByteLength": 644,
"gasUsed": 129476,
"gasUsed": 129496,
}
`;

Expand All @@ -346,6 +346,6 @@ Object {
exports[`Uniswap Gas Tests Trade on UniswapV3 with Universal Router. ETH --> ERC20 gas: exactOut swap 1`] = `
Object {
"calldataByteLength": 772,
"gasUsed": 124663,
"gasUsed": 124683,
}
`;
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`UniversalRouter Gas Tests gas: bytecode size 1`] = `18715`;
exports[`UniversalRouter Gas Tests gas: bytecode size 1`] = `18786`;
Loading