Skip to content

Commit bec647b

Browse files
committed
feat: avoid reentrancy attacks on PRT
1 parent a4249a5 commit bec647b

1 file changed

Lines changed: 26 additions & 4 deletions

File tree

prt/contracts/src/tournament/Tournament.sol

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,20 @@ contract Tournament is ITournament {
107107
_;
108108
}
109109

110+
/// @notice Acquires the lock at the start,
111+
/// and releases the lock at the end.
112+
/// Avoids reentrancy attacks.
113+
modifier withLock() {
114+
_acquireLock();
115+
_;
116+
_releaseLock();
117+
}
118+
110119
/// @notice Refunds the message sender with the amount
111120
/// of Ether wasted on gas on this function call plus
112121
/// a profit, capped by the current contract balance
113122
/// and a fraction of the bond value.
123+
/// Also acquires the lock beforehand and releases it afterward.
114124
/// @param gasEstimate A worst-case gas estimate for the modified function
115125
/// forge-lint: disable-next-line(unwrapped-modifier-logic)
116126
modifier refundable(uint256 gasEstimate) {
@@ -224,7 +234,7 @@ contract Tournament is ITournament {
224234
bytes32[] calldata _proof,
225235
Tree.Node _leftNode,
226236
Tree.Node _rightNode
227-
) external payable override tournamentOpen {
237+
) external payable override withLock tournamentOpen {
228238
require(msg.value >= bondValue(), InsufficientBond());
229239

230240
Tree.Node _commitmentRoot = _leftNode.join(_rightNode);
@@ -359,7 +369,7 @@ contract Tournament is ITournament {
359369
/// * Winner is the root tournament winner.
360370
/// - NON-ROOT:
361371
/// * Winner is the inner winner that will be used by the parent tournament.
362-
function tryRecoveringBond() public override returns (bool) {
372+
function tryRecoveringBond() public override withLock returns (bool) {
363373
require(isFinished(), TournamentNotFinished());
364374

365375
(bool hasDangling, Tree.Node winningCommitment) =
@@ -372,6 +382,10 @@ contract Tournament is ITournament {
372382
uint256 contractBalance = address(this).balance;
373383
(bool success,) = winner.call{value: contractBalance}("");
374384

385+
// This is the only part of the function body that is not
386+
// compliant to the checks-interactions-effects pattern.
387+
// So, in order to avoid reentrancy attacks, this function
388+
// is modified to acquire (and release) the lock.
375389
if (success) {
376390
deleteClaimer(winningCommitment);
377391
}
@@ -943,9 +957,17 @@ contract Tournament is ITournament {
943957
return a.min(b).min(c);
944958
}
945959

946-
function _refundableBefore() private returns (uint256 gasBefore) {
960+
function _acquireLock() private {
947961
require(!locked, ReentrancyDetected());
948962
locked = true;
963+
}
964+
965+
function _releaseLock() private {
966+
locked = false;
967+
}
968+
969+
function _refundableBefore() private returns (uint256 gasBefore) {
970+
_acquireLock();
949971
gasBefore = gasleft();
950972
}
951973

@@ -963,7 +985,7 @@ contract Tournament is ITournament {
963985
msg.sender.call{value: refundValue}("");
964986
emit PartialBondRefund(msg.sender, refundValue, status, ret);
965987

966-
locked = false;
988+
_releaseLock();
967989
}
968990

969991
/// @inheritdoc ITournament

0 commit comments

Comments
 (0)