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

chore: update v4-core:latest #15

Merged
merged 15 commits into from
Nov 28, 2023
53 changes: 53 additions & 0 deletions 01a_CreatePool.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import {PoolInitializeTest} from "@uniswap/v4-core/contracts/test/PoolInitializeTest.sol";
import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol";

contract CreatePoolScript is Script {
using CurrencyLibrary for Currency;

//addresses with contracts deployed
address constant POOL_INITIALIZE_ROUTER = address(0x0); // TODO: Update once deployed
address constant MUNI_ADDRESS = address(0xbD97BF168FA913607b996fab823F88610DCF7737); //mUNI deployed to GOERLI -- insert your own contract address here
address constant MUSDC_ADDRESS = address(0xa468864e673a807572598AB6208E49323484c6bF); //mUSDC deployed to GOERLI -- insert your own contract address here
address constant HOOK_ADDRESS = address(0x3CA2cD9f71104a6e1b67822454c725FcaeE35fF6); //address of the hook contract deployed to goerli -- you can use this hook address or deploy your own!

PoolInitializeTest initializeRouter = PoolInitializeTest(POOL_INITIALIZE_ROUTER);

function run() external {
// sort the tokens!
address token0 = uint160(MUSDC_ADDRESS) < uint160(MUNI_ADDRESS) ? MUSDC_ADDRESS : MUNI_ADDRESS;
address token1 = uint160(MUSDC_ADDRESS) < uint160(MUNI_ADDRESS) ? MUNI_ADDRESS : MUSDC_ADDRESS;
uint24 swapFee = 4000;
int24 tickSpacing = 10;

// floor(sqrt(1) * 2^96)
uint160 startingPrice = 79228162514264337593543950336;

bytes memory hookData = abi.encode(block.timestamp);

PoolKey memory pool = PoolKey({
currency0: Currency.wrap(token0),
currency1: Currency.wrap(token1),
fee: swapFee,
tickSpacing: tickSpacing,
hooks: IHooks(HOOK_ADDRESS)
});

// Turn the Pool into an ID so you can use it for modifying positions, swapping, etc.
PoolId id = PoolIdLibrary.toId(pool);
bytes32 idBytes = PoolId.unwrap(id);

console.log("Pool ID Below");
console.logBytes32(bytes32(idBytes));

vm.broadcast();
initializeRouter.initialize(pool, startingPrice, hookData);
}
}
33 changes: 22 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,26 @@
6. This template is built using Foundry

<details>
<summary><b>NOTE: v4-core versioning</b></summary>
<summary>Updating to v4-template:latest</summary>

Previous versions of `v4-template` accessed `v4-core` as the nested dependency of `v4-periphery`. With recent core activity, `v4-periphery` is slightly out of sync.

As of 11/16/2023, `v4-template` has pinned a version of `v4-core` that is most similar to the testnet deployments.

If there are issues, please do not hesistate on reaching out to [saucepoint](https://t.me/saucepoint)
This template is actively maintained -- you can update the v4 dependencies, scripts, and helpers:
```bash
git remote add template https://github.com/uniswapfoundation/v4-template
git fetch template
git merge template/main <BRANCH> --allow-unrelated-histories
```

</details>

---

### Local Development (Anvil)
# Linux / WSL2 (TSTORE/TLOAD)

Please update [foundry.toml](foundry.toml#L9) to use the linux `solc`

Mac users do not need to change anything by default

## Set up

*requires [foundry](https://book.getfoundry.sh)*

Expand All @@ -35,23 +42,27 @@ forge install
forge test
```

Because v4 exceeds the bytecode limit of Ethereum and it's *business licensed*, we can only deploy & test hooks on [anvil](https://book.getfoundry.sh/anvil/).
### Local Development (Anvil)

Because v4 depends on TSTORE and its *business licensed*, you can only deploy & test hooks on [anvil](https://book.getfoundry.sh/anvil/)

```bash
# start anvil, with a larger code limit
anvil --code-size-limit 30000
# start anvil with TSTORE support
# (`foundryup`` to update if cancun is not an option)
anvil --hardfork cancun

# in a new terminal
forge script script/Anvil.s.sol \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--code-size-limit 30000 \
--broadcast
```

<details>
<summary><h3>Goerli Testnet</h3></summary>

NOTE: 11/21/2023, the Goerli deployment is out of sync with the latest v4. It is recommend to use local testing instead

For testing on Goerli Testnet the Uniswap Foundation team has deployed a slimmed down version of the V4 contract (due to current contract size limits) on the network.

The relevant addresses for testing on Goerli are the ones below
Expand Down
4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ out = "out"
libs = ["lib"]
ffi = true
fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}]
cancun = true

# For Linux/WSL2 systems, please change to `lib/v4-core/bin/solc-static-linux`
solc = "lib/v4-core/bin/solc-mac"

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
2 changes: 1 addition & 1 deletion lib/v4-core
Submodule v4-core updated 98 files
+7 −0 .env
+1 −1 .forge-snapshots/SwapMath_oneForZero_exactInCapped.snap
+1 −1 .forge-snapshots/SwapMath_oneForZero_exactInPartial.snap
+1 −1 .forge-snapshots/SwapMath_oneForZero_exactOutCapped.snap
+1 −1 .forge-snapshots/SwapMath_oneForZero_exactOutPartial.snap
+1 −1 .forge-snapshots/SwapMath_zeroForOne_exactInCapped.snap
+1 −1 .forge-snapshots/SwapMath_zeroForOne_exactInPartial.snap
+1 −1 .forge-snapshots/SwapMath_zeroForOne_exactOutCapped.snap
+1 −1 .forge-snapshots/SwapMath_zeroForOne_exactOutPartial.snap
+1 −1 .forge-snapshots/before swap hook, already cached dynamic fee.snap
+1 −1 .forge-snapshots/cached dynamic fee, no hooks.snap
+1 −1 .forge-snapshots/donate gas with 1 token.snap
+1 −1 .forge-snapshots/donate gas with 2 tokens.snap
+1 −0 .forge-snapshots/erc20 collect protocol fees.snap
+1 −1 .forge-snapshots/gas overhead of no-op lock.snap
+1 −1 .forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse.snap
+1 −1 .forge-snapshots/getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue.snap
+1 −1 .forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas.snap
+1 −1 .forge-snapshots/getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas.snap
+1 −1 .forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas.snap
+1 −1 .forge-snapshots/getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas.snap
+1 −1 .forge-snapshots/initialize.snap
+1 −1 .forge-snapshots/mint with empty hook.snap
+1 −1 .forge-snapshots/mint with native token.snap
+1 −1 .forge-snapshots/mint.snap
+1 −0 .forge-snapshots/modify position with noop.snap
+1 −0 .forge-snapshots/native collect protocol fees.snap
+1 −1 .forge-snapshots/poolManager bytecode size.snap
+1 −0 .forge-snapshots/simple swap with native.snap
+1 −1 .forge-snapshots/simple swap.snap
+1 −1 .forge-snapshots/swap against liquidity with native token.snap
+1 −1 .forge-snapshots/swap against liquidity.snap
+1 −0 .forge-snapshots/swap burn claim for input.snap
+0 −1 .forge-snapshots/swap mint 1155 as output.snap
+1 −0 .forge-snapshots/swap mint output as claim.snap
+0 −1 .forge-snapshots/swap with 1155 as input.snap
+1 −1 .forge-snapshots/swap with dynamic fee.snap
+1 −1 .forge-snapshots/swap with hooks.snap
+1 −1 .forge-snapshots/swap with native.snap
+1 −0 .forge-snapshots/swap with noop.snap
+1 −1 .forge-snapshots/update dynamic fee in before swap.snap
+1 −1 .github/workflows/lint.yml
+8 −0 .github/workflows/tests.yml
+0 −1 .nvmrc
+0 −2 .prettierignore
+0 −5 .prettierrc
+11 −1 CONTRIBUTING.md
+1 −1 README.md
+ bin/solc-mac
+ bin/solc-static-linux
+9 −2 foundry.toml
+8 −9 justfile
+51 −0 src/Claims.sol
+62 −67 src/PoolManager.sol
+31 −0 src/interfaces/IClaims.sol
+15 −14 src/interfaces/IPoolManager.sol
+20 −0 src/libraries/Hooks.sol
+0 −50 src/libraries/LockDataLibrary.sol
+97 −0 src/libraries/Lockers.sol
+14 −10 src/libraries/Pool.sol
+8 −4 src/libraries/SafeCast.sol
+16 −11 src/libraries/SqrtPriceMath.sol
+5 −0 src/libraries/TickMath.sol
+1 −1 src/test/DynamicFeesTestHook.sol
+2 −1 src/test/EmptyTestHooks.sol
+19 −0 src/test/MockClaims.sol
+54 −0 src/test/NoOpTestHooks.sol
+30 −30 src/test/PoolDonateTest.sol
+47 −0 src/test/PoolInitializeTest.sol
+21 −34 src/test/PoolModifyPositionTest.sol
+47 −65 src/test/PoolSwapTest.sol
+25 −38 src/test/PoolTakeTest.sol
+51 −0 src/test/PoolTestBase.sol
+3 −3 src/test/TestInvalidERC20.sol
+8 −1 src/types/BalanceDelta.sol
+99 −0 test/Claims.t.sol
+41 −55 test/DynamicFees.t.sol
+26 −53 test/Fees.t.sol
+138 −121 test/Hooks.t.sol
+136 −0 test/LockersLibrary.t.sol
+4 −4 test/Pool.t.sol
+367 −730 test/PoolManager.t.sol
+317 −0 test/PoolManagerInitialize.t.sol
+29 −29 test/PoolManagerReentrancyTest.t.sol
+4 −4 test/SafeCast.t.sol
+10 −10 test/SqrtPriceMath.t.sol
+19 −14 test/Tick.t.sol
+6 −22 test/TickMath.t.sol
+0 −0 test/js-scripts/.yarnrc
+4 −4 test/js-scripts/package.json
+0 −0 test/js-scripts/src/getSqrtRatioAtTick.ts
+0 −0 test/js-scripts/src/getTickAtSqrtRatio.ts
+0 −0 test/js-scripts/tsconfig.json
+0 −0 test/js-scripts/yarn.lock
+0 −3 test/utils/Constants.sol
+96 −38 test/utils/Deployers.sol
+21 −0 test/utils/JavascriptFfi.sol
+0 −20 test/utils/TokenFixture.sol
111 changes: 102 additions & 9 deletions script/Anvil.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
pragma solidity ^0.8.19;

import "forge-std/Script.sol";
import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol";
import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol";
import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol";
import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
import {PoolInitializeTest} from "@uniswap/v4-core/contracts/test/PoolInitializeTest.sol";
import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol";
import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol";
import {PoolDonateTest} from "@uniswap/v4-core/contracts/test/PoolDonateTest.sol";
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol";
import {Constants} from "@uniswap/v4-core/contracts/../test/utils/Constants.sol";
import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol";
import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol";
import {Counter} from "../src/Counter.sol";
import {HookMiner} from "../test/utils/HookMiner.sol";

Expand All @@ -20,28 +27,114 @@ contract CounterScript is Script {

function run() public {
vm.broadcast();
PoolManager manager = new PoolManager(500000);
IPoolManager manager = deployPoolManager();

// hook contracts must have specific flags encoded in the address
uint160 flags = uint160(
uint160 permissions = uint160(
Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG
| Hooks.AFTER_MODIFY_POSITION_FLAG
);

// Mine a salt that will produce a hook address with the correct flags
// Mine a salt that will produce a hook address with the correct permissions
(address hookAddress, bytes32 salt) =
HookMiner.find(CREATE2_DEPLOYER, flags, type(Counter).creationCode, abi.encode(address(manager)));
HookMiner.find(CREATE2_DEPLOYER, permissions, type(Counter).creationCode, abi.encode(address(manager)));

// Deploy the hook using CREATE2
// ----------------------------- //
// Deploy the hook using CREATE2 //
// ----------------------------- //
vm.broadcast();
Counter counter = new Counter{salt: salt}(IPoolManager(address(manager)));
Counter counter = new Counter{salt: salt}(manager);
require(address(counter) == hookAddress, "CounterScript: hook address mismatch");

// Additional helpers for interacting with the pool
vm.startBroadcast();
new PoolModifyPositionTest(IPoolManager(address(manager)));
new PoolSwapTest(IPoolManager(address(manager)));
new PoolDonateTest(IPoolManager(address(manager)));
(PoolInitializeTest initializeRouter, PoolModifyPositionTest lpRouter, PoolSwapTest swapRouter,) =
deployRouters(manager);
vm.stopBroadcast();

// test the lifecycle (create pool, add liquidity, swap)
vm.startBroadcast();
testLifecycle(address(counter), initializeRouter, lpRouter, swapRouter);
vm.stopBroadcast();
}

// -----------------------------------------------------------
// Helpers
// -----------------------------------------------------------
function deployPoolManager() internal returns (IPoolManager) {
return IPoolManager(address(new PoolManager(500000)));
}

function deployRouters(IPoolManager manager)
internal
returns (
PoolInitializeTest initializeRouter,
PoolModifyPositionTest lpRouter,
PoolSwapTest swapRouter,
PoolDonateTest donateRouter
)
{
initializeRouter = new PoolInitializeTest(manager);
lpRouter = new PoolModifyPositionTest(manager);
swapRouter = new PoolSwapTest(manager);
donateRouter = new PoolDonateTest(manager);
}

function deployTokens() internal returns (MockERC20 token0, MockERC20 token1) {
MockERC20 tokenA = new MockERC20("MockA", "A", 18);
MockERC20 tokenB = new MockERC20("MockB", "B", 18);
if (uint160(address(tokenA)) < uint160(address(tokenB))) {
token0 = tokenA;
token1 = tokenB;
} else {
token0 = tokenB;
token1 = tokenA;
}
}

function testLifecycle(
address hook,
PoolInitializeTest initializeRouter,
PoolModifyPositionTest lpRouter,
PoolSwapTest swapRouter
) internal {
(MockERC20 token0, MockERC20 token1) = deployTokens();
token0.mint(msg.sender, 100_000 ether);
token1.mint(msg.sender, 100_000 ether);

bytes memory ZERO_BYTES = new bytes(0);

// initialize the pool
int24 tickSpacing = 60;
PoolKey memory poolKey =
PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, tickSpacing, IHooks(hook));
initializeRouter.initialize(poolKey, Constants.SQRT_RATIO_1_1, ZERO_BYTES);

// approve the tokens to the routers
token0.approve(address(lpRouter), type(uint256).max);
token1.approve(address(lpRouter), type(uint256).max);
token0.approve(address(swapRouter), type(uint256).max);
token1.approve(address(swapRouter), type(uint256).max);

// add full range liquidity to the pool
lpRouter.modifyPosition(
poolKey,
IPoolManager.ModifyPositionParams(
TickMath.minUsableTick(tickSpacing), TickMath.maxUsableTick(tickSpacing), 100 ether
),
ZERO_BYTES
);

// swap some tokens
bool zeroForOne = true;
int256 amountSpecified = 1 ether;
IPoolManager.SwapParams memory params = IPoolManager.SwapParams({
zeroForOne: zeroForOne,
amountSpecified: amountSpecified,
sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 // unlimited impact
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true});
swapRouter.swap(poolKey, params, testSettings, ZERO_BYTES);
}
}
3 changes: 2 additions & 1 deletion src/Counter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ contract Counter is BaseHook {
beforeSwap: true,
afterSwap: true,
beforeDonate: false,
afterDonate: false
afterDonate: false,
noOp: false
});
}

Expand Down
7 changes: 3 additions & 4 deletions test/Counter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol";
import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol";
import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol";
import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol";
import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol";
import {Deployers} from "@uniswap/v4-core/contracts/../test/utils/Deployers.sol";
import {Constants} from "@uniswap/v4-core/contracts/../test/utils/Constants.sol";
import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol";
import {HookTest} from "./utils/HookTest.sol";
import {Counter} from "../src/Counter.sol";
import {HookMiner} from "./utils/HookMiner.sol";

contract CounterTest is HookTest, Deployers, GasSnapshot {
contract CounterTest is HookTest {
using PoolIdLibrary for PoolKey;
using CurrencyLibrary for Currency;

Expand All @@ -41,7 +40,7 @@ contract CounterTest is HookTest, Deployers, GasSnapshot {
// Create the pool
poolKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 60, IHooks(counter));
poolId = poolKey.toId();
manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES);
initializeRouter.initialize(poolKey, Constants.SQRT_RATIO_1_1, ZERO_BYTES);

// Provide liquidity to the pool
modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-60, 60, 10 ether), ZERO_BYTES);
Expand Down
6 changes: 3 additions & 3 deletions test/utils/HookMiner.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.22;
pragma solidity ^0.8.20;

/// @title HookMiner - a library for mining hook addresses
/// @dev This library is intended for `forge test` environments. There may be gotchas when using salts in `forge script` or `forge create`
library HookMiner {
// mask to slice out the top 8 bit of the address
uint160 constant FLAG_MASK = 0xFF << 152;
// mask to slice out the top 12 bit of the address
uint160 constant FLAG_MASK = 0xFFF << 148;

// Maximum number of iterations to find a salt, avoid infinite loops
uint256 constant MAX_LOOP = 10_000;
Expand Down
4 changes: 4 additions & 0 deletions test/utils/HookTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol";
import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol";
import {PoolInitializeTest} from "@uniswap/v4-core/contracts/test/PoolInitializeTest.sol";
import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol";
import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol";
import {PoolDonateTest} from "@uniswap/v4-core/contracts/test/PoolDonateTest.sol";
Expand All @@ -18,12 +19,14 @@ import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol";
/// @dev Minimal initialization. Inheriting contract should set up pools and provision liquidity
contract HookTest is Test {
PoolManager manager;
PoolInitializeTest initializeRouter;
PoolModifyPositionTest modifyPositionRouter;
PoolSwapTest swapRouter;
PoolDonateTest donateRouter;
TestERC20 token0;
TestERC20 token1;

bytes constant ZERO_BYTES = new bytes(0);
uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_RATIO + 1;
uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1;

Expand All @@ -44,6 +47,7 @@ contract HookTest is Test {
manager = new PoolManager(500000);

// Helpers for interacting with the pool
initializeRouter = new PoolInitializeTest(IPoolManager(address(manager)));
modifyPositionRouter = new PoolModifyPositionTest(IPoolManager(address(manager)));
swapRouter = new PoolSwapTest(IPoolManager(address(manager)));
donateRouter = new PoolDonateTest(IPoolManager(address(manager)));
Expand Down
Loading