@@ -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