1+ // SPDX-License-Identifier: -- BCOM --
2+
3+ pragma solidity = 0.8.25 ;
4+
5+ import "forge-std/Test.sol " ;
6+ import "forge-std/console.sol " ;
7+
8+ // Balancer V3 interfaces
9+ import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol " ;
10+ import { IRouter } from "@balancer-labs/v3-interfaces/contracts/vault/IRouter.sol " ;
11+ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
12+ import {
13+ SwapKind
14+ } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol " ;
15+
16+ // Test for live DiscountHook_V3 contract
17+ contract DiscountHook_V3_Live_Test is Test {
18+ // Mainnet addresses
19+ IVault constant VAULT = IVault (0xbA1333333333a1BA1108E8412f11850A5C319bA9 );
20+ IRouter constant ROUTER = IRouter (payable (0xAE563E3f8219521950555F5962419C8919758Ea2 ));
21+
22+ // Live contracts
23+ address constant DISCOUNT_HOOK = 0x4F4F5347EC267E18787e56efb654D4d7A3e0C0E7 ;
24+ address constant DISCOUNT_CONFIG = 0x7AffEf8867d0f00eF6A3793EC8f0c3ba9c568AF2 ;
25+ address constant POOL_ADDRESS = 0x33832529ca354536728A2f2515E4E2106A8D8afA ;
26+
27+ // Token addresses (WETH/VERSE pool)
28+ IERC20 constant VERSE = IERC20 (0x249cA82617eC3DfB2589c4c17ab7EC9765350a18 );
29+ IERC20 constant WETH = IERC20 (0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 );
30+
31+ // Test accounts
32+ address alice = makeAddr ("alice " );
33+ address bob = makeAddr ("bob " );
34+
35+ // Whale addresses for getting tokens
36+ address constant VERSE_WHALE = 0x249cA82617eC3DfB2589c4c17ab7EC9765350a18 ; // Token contract itself
37+ address constant WETH_WHALE = 0x8EB8a3b98659Cce290402893d0123abb75E3ab28 ; // Known WETH holder
38+
39+ struct SwapResult {
40+ uint256 amountIn;
41+ uint256 amountOut;
42+ uint256 swapFeeAmount;
43+ uint256 effectiveFeePercentage;
44+ }
45+
46+ function setUp () public {
47+ // Fork mainnet using the configured RPC endpoint
48+ vm.createSelectFork ("mainnet " );
49+
50+ // Setup test accounts with ETH
51+ vm.deal (alice, 10 ether);
52+ vm.deal (bob, 10 ether);
53+
54+ // Give Alice some VERSE tokens (she will get discount)
55+ vm.prank (VERSE_WHALE);
56+ VERSE.transfer (alice, 1000e18 );
57+
58+ // Give both users some WETH and USDC for testing
59+ _setupTokenBalances ();
60+
61+ console.log ("=== Test Setup Complete === " );
62+ console.log ("Alice VERSE balance: " , VERSE.balanceOf (alice) / 1e18 , "VERSE " );
63+ console.log ("Bob VERSE balance: " , VERSE.balanceOf (bob) / 1e18 , "VERSE " );
64+ console.log ("Alice WETH balance: " , WETH.balanceOf (alice) / 1e18 , "WETH " );
65+ console.log ("Bob WETH balance: " , WETH.balanceOf (bob) / 1e18 , "WETH " );
66+ }
67+
68+ function _setupTokenBalances () internal {
69+ // Give Alice and Bob some WETH
70+ vm.prank (WETH_WHALE);
71+ WETH.transfer (alice, 5 ether);
72+ vm.prank (WETH_WHALE);
73+ WETH.transfer (bob, 5 ether);
74+
75+ // Give Alice and Bob some additional VERSE for trading
76+ vm.prank (VERSE_WHALE);
77+ VERSE.transfer (alice, 1000e18 );
78+ vm.prank (VERSE_WHALE);
79+ VERSE.transfer (bob, 1000e18 );
80+ }
81+
82+ function testDiscountHookRealSwap () public {
83+ console.log ("\n=== Testing DiscountHook_V3 Functionality === " );
84+
85+ // Test the hook directly to see if it can detect VERSE holders
86+ console.log ("Alice VERSE balance: " , VERSE.balanceOf (alice) / 1e18 , "VERSE " );
87+ console.log ("Bob VERSE balance: " , VERSE.balanceOf (bob) / 1e18 , "VERSE " );
88+
89+ // Try querying a swap instead of executing it
90+ console.log ("\nTrying to query swap rates... " );
91+
92+ try ROUTER.querySwapSingleTokenExactIn (
93+ POOL_ADDRESS,
94+ WETH,
95+ VERSE,
96+ 1 ether,
97+ alice,
98+ ""
99+ ) returns (uint256 aliceAmountOut ) {
100+ console.log ("Alice query result: " , aliceAmountOut / 1e18 , "VERSE " );
101+
102+ try ROUTER.querySwapSingleTokenExactIn (
103+ POOL_ADDRESS,
104+ WETH,
105+ VERSE,
106+ 1 ether,
107+ bob,
108+ ""
109+ ) returns (uint256 bobAmountOut ) {
110+ console.log ("Bob query result: " , bobAmountOut / 1e18 , "VERSE " );
111+
112+ if (aliceAmountOut > bobAmountOut) {
113+ console.log ("SUCCESS: Alice gets more VERSE than Bob (discount working) " );
114+ uint256 extraTokens = aliceAmountOut - bobAmountOut;
115+ console.log ("Alice gets extra tokens: " , extraTokens / 1e18 , "VERSE " );
116+ } else {
117+ console.log ("NOTE: No difference detected or Bob gets more " );
118+ }
119+ } catch {
120+ console.log ("Failed to query swap for Bob " );
121+ }
122+ } catch {
123+ console.log ("Failed to query swap for Alice " );
124+ }
125+ }
126+
127+ function _performSwap (address user , uint256 amountIn , string memory userLabel ) internal returns (SwapResult memory ) {
128+ console.log ("\n--- Testing " , userLabel, "--- " );
129+
130+ // Record balances before
131+ uint256 wethBefore = WETH.balanceOf (user);
132+ uint256 verseBefore = VERSE.balanceOf (user);
133+
134+ // Approve tokens
135+ vm.startPrank (user);
136+ WETH.approve (address (ROUTER), amountIn);
137+
138+ // Perform swap: WETH -> VERSE using the Router directly
139+ uint256 amountOut = ROUTER.swapSingleTokenExactIn (
140+ POOL_ADDRESS, // pool
141+ WETH, // tokenIn
142+ VERSE, // tokenOut
143+ amountIn, // exactAmountIn
144+ 0 , // minAmountOut (no slippage protection for test)
145+ block .timestamp + 1 hours, // deadline
146+ false , // wethIsEth
147+ "" // userData
148+ );
149+
150+ vm.stopPrank ();
151+
152+ // Record balances after
153+ uint256 wethAfter = WETH.balanceOf (user);
154+ uint256 verseAfter = VERSE.balanceOf (user);
155+
156+ // Calculate actual amounts
157+ uint256 actualAmountIn = wethBefore - wethAfter;
158+ uint256 actualAmountOut = verseAfter - verseBefore;
159+
160+ console.log ("Amount in (WETH): " , actualAmountIn / 1e18 );
161+ console.log ("Amount out (VERSE): " , actualAmountOut / 1e18 );
162+
163+ // Calculate effective fee
164+ // For WETH/VERSE pool, we'll estimate based on actual swap results
165+ // Since both tokens have 18 decimals, we can compare directly
166+ uint256 expectedOutputWithoutFee = actualAmountIn; // Simplified for test - would need actual price data
167+ uint256 feeAmount = expectedOutputWithoutFee > actualAmountOut ? expectedOutputWithoutFee - actualAmountOut : 0 ;
168+ uint256 effectiveFeePercentage = expectedOutputWithoutFee > 0 ? (feeAmount * 1e18 ) / expectedOutputWithoutFee : 0 ;
169+
170+ console.log ("Estimated fee amount (VERSE): " , feeAmount / 1e18 );
171+ console.log ("Effective fee percentage: " , effectiveFeePercentage / 1e14 , "bps " );
172+
173+ return SwapResult ({
174+ amountIn: actualAmountIn,
175+ amountOut: actualAmountOut,
176+ swapFeeAmount: feeAmount,
177+ effectiveFeePercentage: effectiveFeePercentage
178+ });
179+ }
180+
181+ function testDiscountConfigEligibility () public {
182+ console.log ("\n=== Testing DiscountConfig Eligibility === " );
183+
184+ // Test Alice (has VERSE)
185+ (bool success , bytes memory data ) = DISCOUNT_CONFIG.staticcall (
186+ abi.encodeWithSignature ("isEligible(address,address,uint256) " , alice, address (VERSE), VERSE.balanceOf (alice))
187+ );
188+
189+ if (success && data.length > 0 ) {
190+ bool aliceEligible = abi.decode (data, (bool ));
191+ console.log ("Alice eligible for discount: " , aliceEligible);
192+ assertTrue (aliceEligible, "Alice should be eligible " );
193+ }
194+
195+ // Test Bob (no VERSE)
196+ (success, data) = DISCOUNT_CONFIG.staticcall (
197+ abi.encodeWithSignature ("isEligible(address,address,uint256) " , bob, address (VERSE), VERSE.balanceOf (bob))
198+ );
199+
200+ if (success && data.length > 0 ) {
201+ bool bobEligible = abi.decode (data, (bool ));
202+ console.log ("Bob eligible for discount: " , bobEligible);
203+ assertFalse (bobEligible, "Bob should not be eligible " );
204+ }
205+ }
206+
207+ function testPoolHookConfiguration () public {
208+ console.log ("\n=== Testing Pool Hook Configuration === " );
209+
210+ // Check if pool has the hook configured
211+ (bool success , bytes memory data ) = address (VAULT).staticcall (
212+ abi.encodeWithSignature ("getHooksConfig(address) " , POOL_ADDRESS)
213+ );
214+
215+ if (success && data.length > 0 ) {
216+ console.log ("Pool hook configuration retrieved successfully " );
217+ }
218+
219+ // Log pool details
220+ console.log ("Pool address: " , POOL_ADDRESS);
221+ console.log ("Hook address: " , DISCOUNT_HOOK);
222+ console.log ("Config address: " , DISCOUNT_CONFIG);
223+ }
224+
225+ function testVERSETokenInfo () public view {
226+ console.log ("\n=== VERSE Token Information === " );
227+ console.log ("VERSE Token Address: " , address (VERSE));
228+ console.log ("Alice VERSE Balance: " , VERSE.balanceOf (alice) / 1e18 , "VERSE " );
229+ console.log ("Bob VERSE Balance: " , VERSE.balanceOf (bob) / 1e18 , "VERSE " );
230+ console.log ("Total VERSE Supply: " , VERSE.totalSupply () / 1e18 , "VERSE " );
231+ }
232+ }
0 commit comments