Skip to content
Closed
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
9 changes: 9 additions & 0 deletions src/ForeignController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ contract ForeignController is AccessControl {
// ERC4626 exchange rate thresholds (1e36 precision)
mapping(address token => uint256 maxExchangeRate) public maxExchangeRates;

// Stores UniswapV3 tokenIds that were created by this controller
mapping(uint256 tokenId => bool minted) public wasMintedByController;

/**********************************************************************************************/
/*** Initialization ***/
/**********************************************************************************************/
Expand Down Expand Up @@ -898,6 +901,8 @@ contract ForeignController is AccessControl {
{
_checkRole(RELAYER);

require(tokenId == 0 || wasMintedByController[tokenId], "ForeignController/invalid-token-id");

UniswapV3Lib.UniswapV3PoolParams memory poolParams = uniswapV3PoolParams[pool];
uint256 maxSlippage = maxSlippages[pool];

Expand All @@ -920,6 +925,10 @@ contract ForeignController is AccessControl {
twapSecondsAgo : poolParams.twapSecondsAgo
})
);

if (tokenId == 0) {
wasMintedByController[tokenId_] = true;
}
}

function removeLiquidityUniswapV3(
Expand Down
9 changes: 9 additions & 0 deletions src/MainnetController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ contract MainnetController is AccessControl {
// ERC4626 exchange rate thresholds (1e36 precision)
mapping(address token => uint256 maxExchangeRate) public maxExchangeRates;

// Stores UniswapV3 tokenIds that were created by this controller
mapping(uint256 tokenId => bool minted) public wasMintedByController;

/**********************************************************************************************/
/*** Initialization ***/
/**********************************************************************************************/
Expand Down Expand Up @@ -673,6 +676,8 @@ contract MainnetController is AccessControl {
{
_checkRole(RELAYER);

require(tokenId == 0 || wasMintedByController[tokenId], "MainnetController/invalid-token-id");

UniswapV3Lib.UniswapV3PoolParams memory poolParams = uniswapV3PoolParams[pool];
uint256 maxSlippage = maxSlippages[pool];

Expand All @@ -695,6 +700,10 @@ contract MainnetController is AccessControl {
twapSecondsAgo : poolParams.twapSecondsAgo
})
);

if (tokenId == 0) {
wasMintedByController[tokenId_] = true;
}
}

function removeLiquidityUniswapV3(
Expand Down
62 changes: 52 additions & 10 deletions test/grove-base-fork/UniswapV3.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,8 @@ contract ForeignControllerAddLiquidityFailureTests is UniswapV3TestBase {

function _defaultMinPosition(UniswapV3Lib.TokenAmounts memory desired) internal pure returns (UniswapV3Lib.TokenAmounts memory) {
return UniswapV3Lib.TokenAmounts({
amount0: desired.amount0 * 99 / 100,
amount1: desired.amount1 * 99 / 100
amount0: desired.amount0 * 98 / 100,
amount1: desired.amount1 * 98 / 100
});
}

Expand Down Expand Up @@ -524,11 +524,23 @@ contract ForeignControllerAddLiquidityFailureTests is UniswapV3TestBase {
}

function test_addLiquidityUniswapV3_proxyDoesNotOwnTokenId() public {
uint256 tokenId = _mintExternalPosition();

vm.warp(block.timestamp + 1 hours);
(UniswapV3Lib.Tick memory tick, UniswapV3Lib.TokenAmounts memory desired, UniswapV3Lib.TokenAmounts memory min)
= _prepareDefaultAddLiquidity();

(uint256 tokenId,,,) = _addLiquidity(
0,
tick,
desired,
min
);

vm.startPrank(address(almProxy));
IERC721(UNISWAP_V3_POSITION_MANAGER).transferFrom(address(almProxy), stranger, tokenId);
vm.stopPrank();

assertEq(IERC721(UNISWAP_V3_POSITION_MANAGER).ownerOf(tokenId), stranger, "Token should be owned by stranger");

vm.warp(block.timestamp + 1 hours);

vm.startPrank(ALM_RELAYER);
vm.expectRevert("UniswapV3Lib/proxy-does-not-own-token-id");
Expand Down Expand Up @@ -609,11 +621,16 @@ contract ForeignControllerAddLiquidityFailureTests is UniswapV3TestBase {
foreignController.setUniswapV3AddLiquidityUpperTickBound(usdsAusdPool, 100000);
vm.stopPrank();

// Mint a USDS-USDC position and transfer it to the relayer
uint256 usdsUsdcTokenId = _mintExternalPosition();

vm.prank(stranger);
IERC721(UNISWAP_V3_POSITION_MANAGER).transferFrom(stranger, address(almProxy), usdsUsdcTokenId);
// Mint a USDS-USDC position
(UniswapV3Lib.Tick memory tick, UniswapV3Lib.TokenAmounts memory desired, UniswapV3Lib.TokenAmounts memory min)
= _prepareDefaultAddLiquidity();

(uint256 usdsUsdcTokenId,,,) = _addLiquidity(
0,
tick,
desired,
min
);

vm.warp(block.timestamp + 1 hours);

Expand Down Expand Up @@ -704,6 +721,31 @@ contract ForeignControllerAddLiquidityFailureTests is UniswapV3TestBase {
);
vm.stopPrank();
}

function test_addLiquidityUniswapV3_invalidTokenId() public {
uint256 tokenId = _mintExternalPosition();

// Transfer tokenId to proxy
vm.startPrank(stranger);
IERC721(UNISWAP_V3_POSITION_MANAGER).transferFrom(stranger, address(foreignController), tokenId);
vm.stopPrank();

(UniswapV3Lib.Tick memory tick, UniswapV3Lib.TokenAmounts memory desired, UniswapV3Lib.TokenAmounts memory min)
= _prepareDefaultAddLiquidity();


vm.startPrank(ALM_RELAYER);
vm.expectRevert("ForeignController/invalid-token-id");
foreignController.addLiquidityUniswapV3(
_getPool(),
123,
tick,
desired,
min,
block.timestamp + 1 hours
);
vm.stopPrank();
}
}

contract ForeignControllerAddLiquidityTwapProtectionTests is UniswapV3TestBase {
Expand Down
51 changes: 43 additions & 8 deletions test/grove-mainnet-fork/UniswapV3.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -854,11 +854,23 @@ contract MainnetControllerAddLiquidityFailureTests is UniswapV3TestBase {
}

function test_addLiquidityUniswapV3_proxyDoesNotOwnTokenId() public {
uint256 tokenId = _mintExternalPosition();

vm.warp(block.timestamp + 1 hours);
(UniswapV3Lib.Tick memory tick, UniswapV3Lib.TokenAmounts memory desired, UniswapV3Lib.TokenAmounts memory min)
= _prepareDefaultAddLiquidity();

(uint256 tokenId,,,) = _addLiquidity(
0,
tick,
desired,
min
);

vm.startPrank(address(almProxy));
IERC721(UNISWAP_V3_POSITION_MANAGER).transferFrom(address(almProxy), stranger, tokenId);
vm.stopPrank();

assertEq(IERC721(UNISWAP_V3_POSITION_MANAGER).ownerOf(tokenId), stranger, "Token should be owned by stranger");

vm.warp(block.timestamp + 1 hours);

vm.startPrank(relayer);
vm.expectRevert("UniswapV3Lib/proxy-does-not-own-token-id");
Expand Down Expand Up @@ -938,11 +950,16 @@ contract MainnetControllerAddLiquidityFailureTests is UniswapV3TestBase {
mainnetController.setUniswapV3AddLiquidityUpperTickBound(UNISWAP_V3_DAI_USDC_POOL, 100000);
vm.stopPrank();

// Mint a USDC-USDT position and transfer it to the relayer
uint256 usdcUsdtTokenId = _mintExternalPosition();

vm.prank(stranger);
IERC721(UNISWAP_V3_POSITION_MANAGER).transferFrom(stranger, address(almProxy), usdcUsdtTokenId);
// Mint a USDC-USDT position
(UniswapV3Lib.Tick memory tick, UniswapV3Lib.TokenAmounts memory desired, UniswapV3Lib.TokenAmounts memory min)
= _prepareDefaultAddLiquidity();

(uint256 usdcUsdtTokenId,,,) = _addLiquidity(
0,
tick,
desired,
min
);

vm.warp(block.timestamp + 1 hours);

Expand Down Expand Up @@ -1099,6 +1116,24 @@ contract MainnetControllerAddLiquidityFailureTests is UniswapV3TestBase {
);
vm.stopPrank();
}

function test_addLiquidityUniswapV3_invalidTokenId() public {
(UniswapV3Lib.Tick memory tick, UniswapV3Lib.TokenAmounts memory desired, UniswapV3Lib.TokenAmounts memory min)
= _prepareDefaultAddLiquidity();


vm.startPrank(relayer);
vm.expectRevert("MainnetController/invalid-token-id");
mainnetController.addLiquidityUniswapV3(
_getPool(),
123,
tick,
desired,
min,
block.timestamp + 1 hours
);
vm.stopPrank();
}
}

contract MainnetControllerAddLiquidityTwapProtectionTests is UniswapV3TestBase {
Expand Down
Loading