@@ -56,4 +56,165 @@ contract PodManagerPodVoterClaimsTest is PodManagerV2TestBase {
5656
5757 assertEq (afterBal - beforeBal, expected);
5858 }
59+
60+ function test_voterDownVotesCanClaimEmissions () public {
61+ uint256 epoch = veReppo.currentEpoch ();
62+ uint256 podOne = _mintPod (user1, 1 );
63+
64+ uint256 votes = 100 ;
65+ vm.prank (user2);
66+ podManager.vote (podOne, votes, false );
67+
68+ // Seed enough for the claim to succeed
69+ uint256 seeded = 100 ;
70+ deal (address (reppo), admin, seeded);
71+ vm.startPrank (admin);
72+ reppo.approve (address (podManager), seeded);
73+ podManager.seedREPPOEmissionsBySubnetOwner (1 , seeded);
74+ vm.stopPrank ();
75+
76+ // Advance epoch so claim is valid
77+ veReppo.setCurrentEpoch (epoch + 1 );
78+
79+ uint256 beforeBal = reppo.balanceOf (user2);
80+ vm.prank (user2);
81+ podManager.claimVoterEmissions (user2, podOne, epoch);
82+ uint256 afterBal = reppo.balanceOf (user2);
83+
84+ // --- Compute expected payout ---
85+ uint256 reppoPerEpoch = subnetManager.getReppoEmissionsPerEpoch (1 ); // 100
86+ uint256 subnetVotes = podManager.totalVotesOfSubnetOfEpoch (1 , epoch);
87+ uint256 podEmissionsShare = (reppoPerEpoch * votes) / subnetVotes;
88+ uint256 ownerSharePercent = subnetManager.getPodOwnerEmissionSharePercentage (1 ); // 50
89+ uint256 voterSharePercent = 100 - ownerSharePercent;
90+ uint256 voterEmissionsShare = (podEmissionsShare * voterSharePercent) / 100 ;
91+ uint256 taxRate = subnetManager.getTaxRate (); // 10
92+ uint256 tax = (voterEmissionsShare * taxRate) / 100 ;
93+ uint256 expected = voterEmissionsShare - tax;
94+
95+ assertEq (afterBal - beforeBal, expected);
96+ }
97+
98+ function test_voterGetsNothingWhenPodHasNetUpVotesButVoterOnlyHasDownVotes () public {
99+ uint256 epoch = veReppo.currentEpoch ();
100+ uint256 podOne = _mintPod (admin, 1 );
101+
102+ uint256 upVotes = 100 ;
103+ vm.prank (user1);
104+ podManager.vote (podOne, upVotes, true );
105+
106+ uint256 downVotes = 50 ;
107+ vm.prank (user2);
108+ podManager.vote (podOne, downVotes, false );
109+
110+ // Seed enough for the claim to succeed
111+ uint256 seeded = 100 ;
112+ deal (address (reppo), admin, seeded);
113+ vm.startPrank (admin);
114+ reppo.approve (address (podManager), seeded);
115+ podManager.seedREPPOEmissionsBySubnetOwner (1 , seeded);
116+ vm.stopPrank ();
117+
118+ // Advance epoch so claim is valid
119+ veReppo.setCurrentEpoch (epoch + 1 );
120+
121+ vm.prank (user2);
122+ podManager.claimVoterEmissions (user2, podOne, epoch);
123+ uint256 afterBal = reppo.balanceOf (user2);
124+
125+ assertEq (afterBal, 0 );
126+ }
127+
128+ function test_voterGetsNothingWhenPodHasNetDownVotesButVoterOnlyHasUpVotes () public {
129+ uint256 epoch = veReppo.currentEpoch ();
130+ uint256 podOne = _mintPod (admin, 1 );
131+
132+ uint256 upVotes = 50 ;
133+ vm.prank (user1);
134+ podManager.vote (podOne, upVotes, true );
135+
136+ uint256 downVotes = 100 ;
137+ vm.prank (user2);
138+ podManager.vote (podOne, downVotes, false );
139+
140+ // Seed enough for the claim to succeed
141+ uint256 seeded = 100 ;
142+ deal (address (reppo), admin, seeded);
143+ vm.startPrank (admin);
144+ reppo.approve (address (podManager), seeded);
145+ podManager.seedREPPOEmissionsBySubnetOwner (1 , seeded);
146+ vm.stopPrank ();
147+
148+ // Advance epoch so claim is valid
149+ veReppo.setCurrentEpoch (epoch + 1 );
150+
151+ vm.prank (user1);
152+ podManager.claimVoterEmissions (user1, podOne, epoch);
153+ uint256 afterBal = reppo.balanceOf (user1);
154+
155+ assertEq (afterBal, 0 );
156+ }
157+
158+ function test_voterGetsNothingWhenPodHasNeutralVotes () public {
159+ uint256 epoch = veReppo.currentEpoch ();
160+ uint256 podOne = _mintPod (admin, 1 );
161+
162+ uint256 votes = 100 ;
163+ vm.prank (user1);
164+ podManager.vote (podOne, votes, true );
165+
166+ vm.prank (user2);
167+ podManager.vote (podOne, votes, false );
168+
169+ // Seed enough for the claim to succeed
170+ uint256 seeded = 100 ;
171+ deal (address (reppo), admin, seeded);
172+ vm.startPrank (admin);
173+ reppo.approve (address (podManager), seeded);
174+ podManager.seedREPPOEmissionsBySubnetOwner (1 , seeded);
175+ vm.stopPrank ();
176+
177+ // Advance epoch so claim is valid
178+ veReppo.setCurrentEpoch (epoch + 1 );
179+
180+ vm.prank (user1);
181+ vm.expectRevert (bytes ("PodNetNeutralNoEmissions() " ));
182+ podManager.claimVoterEmissions (user1, podOne, epoch);
183+ uint256 afterBalUser1 = reppo.balanceOf (user1);
184+
185+ vm.prank (user2);
186+ vm.expectRevert (bytes ("PodNetNeutralNoEmissions() " ));
187+ podManager.claimVoterEmissions (user2, podOne, epoch);
188+ uint256 afterBalUser2 = reppo.balanceOf (user2);
189+
190+ assertEq (afterBalUser1, 0 );
191+ assertEq (afterBalUser2, 0 );
192+ }
193+
194+ function testRevert_whenVoterTriesToClaimMultipleTimesForSameEpoch () public {
195+ uint256 epoch = veReppo.currentEpoch ();
196+ uint256 podOne = _mintPod (user1, 1 );
197+
198+ uint256 votes = 100 ;
199+ vm.prank (user2);
200+ podManager.vote (podOne, votes, true );
201+
202+ // Seed enough for the claim to succeed
203+ uint256 seeded = 100 ;
204+ deal (address (reppo), admin, seeded);
205+ vm.startPrank (admin);
206+ reppo.approve (address (podManager), seeded);
207+ podManager.seedREPPOEmissionsBySubnetOwner (1 , seeded);
208+ vm.stopPrank ();
209+
210+ // Advance epoch so claim is valid
211+ veReppo.setCurrentEpoch (epoch + 1 );
212+
213+ vm.prank (user2);
214+ podManager.claimVoterEmissions (user2, podOne, epoch);
215+
216+ vm.prank (user2);
217+ vm.expectRevert (bytes ("EmissionsAlreadyClaimed() " ));
218+ podManager.claimVoterEmissions (user2, podOne, epoch);
219+ }
59220}
0 commit comments