"use strict";(self.webpackChunk_uniswap_docs=self.webpackChunk_uniswap_docs||[]).push([[6161],{54317:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>a,metadata:()=>s,toc:()=>p});var n=o(83117),r=(o(67294),o(3905));const a={id:"testing-hooks",title:"Testing Hooks",sidebar_position:2},i=void 0,s={unversionedId:"contracts/v4/first-hook/testing-hooks",id:"contracts/v4/first-hook/testing-hooks",title:"Testing Hooks",description:"Testing hooks",source:"@site/docs/contracts/v4/first-hook/02-hook-testing.mdx",sourceDirName:"contracts/v4/first-hook",slug:"/contracts/v4/first-hook/testing-hooks",permalink:"/contracts/v4/first-hook/testing-hooks",editUrl:"https://github.com/uniswap/uniswap-docs/tree/main/docs/contracts/v4/first-hook/02-hook-testing.mdx",tags:[],version:"current",sidebarPosition:2,frontMatter:{id:"testing-hooks",title:"Testing Hooks",sidebar_position:2},sidebar:"contractsSidebar",previous:{title:"Building your own hook",permalink:"/contracts/v4/first-hook/building-your-own-hook"},next:{title:"Hook Deployment",permalink:"/contracts/v4/first-hook/hook-deployment"}},l={},p=[{value:"Testing hooks",id:"testing-hooks",level:2}],c={toc:p};function u(e){let{components:t,...o}=e;return(0,r.kt)("wrapper",(0,n.Z)({},c,o,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h2",{id:"testing-hooks"},"Testing hooks"),(0,r.kt)("p",null,"Testing hooks is same as testing contracts. The template includes a test for the Counter hook, which you can find in ",(0,r.kt)("inlineCode",{parentName:"p"},"test/Counter.t.sol"),"."),(0,r.kt)("p",null,"Here are some key points about the Counter hook test:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("p",{parentName:"li"},"The hook extends from a couple of utilities that facilitate easier testing of hooks."),(0,r.kt)("pre",{parentName:"li"},(0,r.kt)("code",{parentName:"pre",className:"language-solidity"},'import "forge-std/Test.sol";\nimport {Deployers} from "v4-core/test/utils/Deployers.sol";\n\ncontract CounterTest is Test, Deployers {\n}\n'))),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("p",{parentName:"li"},"The ",(0,r.kt)("inlineCode",{parentName:"p"},"setup")," function, called before every test, creates a few test tokens, retrieves the hook address, and then initializes the pool with this hook address."),(0,r.kt)("pre",{parentName:"li"},(0,r.kt)("code",{parentName:"pre",className:"language-solidity"},"function setup() public {\n    Deployers.deployFreshManagerAndRouters();\n    (currency0, currency1) = Deployers.deployMintAndApprove2Currencies();\n\n    // Deploy the hook to an address with the correct flags\n    uint160 flags = uint160(\n        Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG\n        | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG\n    );\n    (address hookAddress, bytes32 salt) =\n        HookMiner.find(address(this), flags, type(Counter).creationCode, abi.encode(address(manager)));\n    counter = new Counter{salt: salt}(IPoolManager(address(manager)));\n}\n")),(0,r.kt)("p",{parentName:"li"},"Pool is then initialized containing this hook"),(0,r.kt)("pre",{parentName:"li"},(0,r.kt)("code",{parentName:"pre",className:"language-solidity"},"  // Create the pool\n  key = PoolKey(currency0, currency1, 3000, 60, IHooks(counter));\n  poolId = key.toId();\n  initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES);\n"))),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("p",{parentName:"li"},"Hook tests utilize a router, namely ",(0,r.kt)("inlineCode",{parentName:"p"},"PoolModifyPositionTest"),", to modify positions. PoolModifyPositionTest implements the ",(0,r.kt)("inlineCode",{parentName:"p"},"ILockCallback")," interface and adds the ",(0,r.kt)("inlineCode",{parentName:"p"},"lockAcquired")," function, which in turn calls the ",(0,r.kt)("inlineCode",{parentName:"p"},"manager.modifyPosition")," function."),(0,r.kt)("pre",{parentName:"li"},(0,r.kt)("code",{parentName:"pre",className:"language-solidity"},"  PoolManager manager;\n  PoolModifyPositionTest modifyPositionRouter;\n\n  manager = new PoolManager(500000);\n\n  // Helpers for interacting with the pool\n  modifyPositionRouter = new PoolModifyPositionTest(IPoolManager(address(manager)));\n\n  modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-120, 120, 10 ether), ZERO_BYTES);\n")),(0,r.kt)("p",{parentName:"li"},"Similarly, for token swaps, the test uses ",(0,r.kt)("inlineCode",{parentName:"p"},"PoolSwapTest"),", which also implements the ",(0,r.kt)("inlineCode",{parentName:"p"},"ILockCallback")," interface.")),(0,r.kt)("li",{parentName:"ol"},(0,r.kt)("p",{parentName:"li"},"Testing the hook closely resembles testing any other smart contract. The function ",(0,r.kt)("inlineCode",{parentName:"p"},"testCounterHooks")," executes swaps and verifies if the counters are updated correctly."),(0,r.kt)("pre",{parentName:"li"},(0,r.kt)("code",{parentName:"pre",className:"language-solidity"},"function testCounterHooks() public {\n    // positions were created in setup()\n    assertEq(counter.beforeAddLiquidityCount(poolId), 3);\n    assertEq(counter.beforeRemoveLiquidityCount(poolId), 0);\n\n    assertEq(counter.beforeSwapCount(poolId), 0);\n    assertEq(counter.afterSwapCount(poolId), 0);\n\n    // Perform a test swap //\n    bool zeroForOne = true;\n    int256 amountSpecified = 1e18;\n    BalanceDelta swapDelta = swap(key, zeroForOne, amountSpecified, ZERO_BYTES);\n    // ------------------- //\n\n    assertEq(int256(swapDelta.amount0()), amountSpecified);\n\n    assertEq(counter.beforeSwapCount(poolId), 1);\n    assertEq(counter.afterSwapCount(poolId), 1);\n}\n\n/// Test Helper\nfunction swap(\n    PoolKey memory key,\n    bool zeroForOne,\n    int256 amountSpecified,\n    bytes memory hookData\n) internal returns (BalanceDelta delta) {\n    IPoolManager.SwapParams memory params = IPoolManager.SwapParams({\n        zeroForOne: zeroForOne,\n        amountSpecified: amountSpecified,\n        sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 // unlimited impact\n    });\n\n    PoolSwapTest.TestSettings memory testSettings =\n        PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true, currencyAlreadySent: false});\n\n    delta = swapRouter.swap(key, params, testSettings, hookData);\n}\n")))))}u.isMDXComponent=!0},3905:(e,t,o)=>{o.d(t,{Zo:()=>c,kt:()=>m});var n=o(67294);function r(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function a(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,n)}return o}function i(e){for(var t=1;t<arguments.length;t++){var o=null!=arguments[t]?arguments[t]:{};t%2?a(Object(o),!0).forEach((function(t){r(e,t,o[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(o)):a(Object(o)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(o,t))}))}return e}function s(e,t){if(null==e)return{};var o,n,r=function(e,t){if(null==e)return{};var o,n,r={},a=Object.keys(e);for(n=0;n<a.length;n++)o=a[n],t.indexOf(o)>=0||(r[o]=e[o]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n<a.length;n++)o=a[n],t.indexOf(o)>=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(r[o]=e[o])}return r}var l=n.createContext({}),p=function(e){var t=n.useContext(l),o=t;return e&&(o="function"==typeof e?e(t):i(i({},t),e)),o},c=function(e){var t=p(e.components);return n.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var o=e.components,r=e.mdxType,a=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),d=p(o),m=r,k=d["".concat(l,".").concat(m)]||d[m]||u[m]||a;return o?n.createElement(k,i(i({ref:t},c),{},{components:o})):n.createElement(k,i({ref:t},c))}));function m(e,t){var o=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=o.length,i=new Array(a);i[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:r,i[1]=s;for(var p=2;p<a;p++)i[p]=o[p];return n.createElement.apply(null,i)}return n.createElement.apply(null,o)}d.displayName="MDXCreateElement"}}]);