Skip to content
Open
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
6 changes: 6 additions & 0 deletions docs/pages/changelogs/coins.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Coins Changelog


## 2.2.1

### Patch Changes

- [c96e0c5e](https://github.com/ourzora/zora-protocol/commit/c96e0c5e): Fix bug where liquidity cannot be migrated if there is a position with 0 liquidity

## 2.2.0

### Minor Changes
Expand Down
6 changes: 6 additions & 0 deletions packages/coins/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @zoralabs/coins

## 2.2.1

### Patch Changes

- c96e0c5e: Fix bug where liquidity cannot be migrated if there is a position with 0 liquidity

## 2.2.0

### Minor Changes
Expand Down
12 changes: 6 additions & 6 deletions packages/coins/addresses/8453.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"COIN_V3_IMPL": "0x45Bf86430af7CD071Ea23aE52325A78C8d12aD5a",
"COIN_V4_IMPL": "0xca72309AaF706d290E08608b1Af47943902f69b2",
"COIN_VERSION": "1.1.0",
"CREATOR_COIN_HOOK": "0xd61A675F8a0c67A73DC3B54FB7318B4D91409040",
"CREATOR_COIN_HOOK_SALT": "0x0000000000000000000000000000000000000000000000000000000000000ae8",
"CREATOR_COIN_HOOK": "0x1258e5f3C71ca9dCE95Ce734Ba5759532E46D040",
"CREATOR_COIN_HOOK_SALT": "0x00000000000000000000000000000000000000000000000000000000000029b1",
"CREATOR_COIN_IMPL": "0x88CC4E08C7608723f3E44e17aC669Fb43b6A8313",
"HOOK_UPGRADE_GATE": "0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2",
"ZORA_FACTORY": "0x777777751622c0d3258f214F9DF38E35BF45baF3",
"ZORA_FACTORY_IMPL": "0x0e2ea62E5377D46FeF114A60AfBE3d5eA7490577",
"ZORA_V4_COIN_HOOK": "0x9ea932730A7787000042e34390B8E435dD839040",
"ZORA_V4_COIN_HOOK_SALT": "0x0000000000000000000000000000000000000000000000000000000000002fa2",
"ZORA_HOOK_REGISTRY": "0x777777C4c14b133858c3982D41Dbf02509fc18d7"
"ZORA_FACTORY_IMPL": "0x57e338b97757f6E416965BEB9A5Cd2DB48b10c42",
"ZORA_HOOK_REGISTRY": "0x777777C4c14b133858c3982D41Dbf02509fc18d7",
"ZORA_V4_COIN_HOOK": "0x2b15a16B3Ef024005bA899Bb51764FCd58Cf9040",
"ZORA_V4_COIN_HOOK_SALT": "0x0000000000000000000000000000000000000000000000000000000000000f90"
}
2 changes: 1 addition & 1 deletion packages/coins/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zoralabs/coins",
"version": "2.2.0",
"version": "2.2.1",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
Expand Down
11 changes: 11 additions & 0 deletions packages/coins/src/libs/V4Liquidity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,17 @@ library V4Liquidity {
for (uint256 i; i < positions.length; i++) {
uint128 liquidity = getLiquidity(poolManager, address(this), poolKey, positions[i].tickLower, positions[i].tickUpper);

// Skip positions that have no liquidity to avoid CannotUpdateEmptyPosition error
if (liquidity == 0) {
burnedPositions[i] = BurnedPosition({
tickLower: positions[i].tickLower,
tickUpper: positions[i].tickUpper,
amount0Received: 0,
amount1Received: 0
});
continue;
}

ModifyLiquidityParams memory params = ModifyLiquidityParams({
tickLower: positions[i].tickLower,
tickUpper: positions[i].tickUpper,
Expand Down
2 changes: 1 addition & 1 deletion packages/coins/src/version/ContractVersionBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersion
contract ContractVersionBase is IVersionedContract {
/// @notice The version of the contract
function contractVersion() external pure override returns (string memory) {
return "2.2.0";
return "2.2.1";
}
}
44 changes: 44 additions & 0 deletions packages/coins/test/LiquidityMigration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {ICoin} from "../src/interfaces/ICoin.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {CoinCommon} from "../src/libs/CoinCommon.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {MultiOwnable} from "../src/utils/MultiOwnable.sol";
import {IHooksUpgradeGate} from "../src/interfaces/IHooksUpgradeGate.sol";
import {BaseCoin} from "../src/BaseCoin.sol";

contract LiquidityMigrationReceiver is IUpgradeableDestinationV4Hook, IERC165 {
function initializeFromMigration(
Expand Down Expand Up @@ -45,6 +47,8 @@ contract InvalidLiquidityMigrationReceiver is IERC165 {
contract LiquidityMigrationTest is BaseTest {
MockERC20 internal mockERC20A;

address constant coinVersionLookup = 0x777777751622c0d3258f214F9DF38E35BF45baF3;

function setUp() public override {
super.setUpWithBlockNumber(30267794);

Expand Down Expand Up @@ -388,4 +392,44 @@ contract LiquidityMigrationTest is BaseTest {
// Should match isRegisteredUpgradePath
assertEq(hookUpgradeGate.isAllowedHookUpgrade(baseImpl, upgradeImpl), hookUpgradeGate.isRegisteredUpgradePath(baseImpl, upgradeImpl));
}

function test_migrateLiquidity_failsWithEmptyPositionBug() public {
// Reproduce the bug discovered in hook version 1.1.2 where migration
// tries to modify liquidity positions that have zero liquidity
vm.createSelectFork("base", 35671635);

address contentCoin = 0x81f5F30217dA777a5d6441606AFa57E093833d7C;
address oldHook = 0x9ea932730A7787000042e34390B8E435dD839040; // v1.1.2 hook
address newHook = 0xff74Be9D3596eA7a33BB4983DD7906fB34135040; // current hook
address upgradeGate = 0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2; // deployed upgrade gate

BaseCoin coin = BaseCoin(contentCoin);

// Register upgrade path
address[] memory baseImpls = new address[](1);
baseImpls[0] = oldHook;

vm.prank(Ownable(upgradeGate).owner());
IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, newHook);

// Get coin owner
address coinOwner = MultiOwnable(contentCoin).owners()[0];

// First, demonstrate the bug exists - this should fail
vm.prank(coinOwner);
vm.expectRevert();
coin.migrateLiquidity(newHook, "");

// Now fix the bug by etching fixed hook code onto the old hook address
bytes memory creationCode = HooksDeployment.contentCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);

(IHooks fixedHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));

// Etch the fixed hook code onto the old hook address
vm.etch(oldHook, address(fixedHook).code);

// Now migration should work
vm.prank(coinOwner);
coin.migrateLiquidity(newHook, "");
}
}
Loading