diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b4a0b063da..c98ce623b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + + * `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) + ## Unreleased * `TimelockController`: Added a new `admin` constructor parameter that is assigned the admin role instead of the deployer account. ([#3722](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3722)) diff --git a/contracts/mocks/ReentrancyMock.sol b/contracts/mocks/ReentrancyMock.sol index 43425dd6e36..161e1d3d8ed 100644 --- a/contracts/mocks/ReentrancyMock.sol +++ b/contracts/mocks/ReentrancyMock.sol @@ -40,4 +40,12 @@ contract ReentrancyMock is ReentrancyGuard { function _count() private { counter += 1; } + + function guardedCheckEntered() public nonReentrant { + require(_reentrancyGuardEntered()); + } + + function unguardedCheckNotEntered() public view { + require(!_reentrancyGuardEntered()); + } } diff --git a/contracts/security/ReentrancyGuard.sol b/contracts/security/ReentrancyGuard.sol index 3108ac9f426..24ba5407bd1 100644 --- a/contracts/security/ReentrancyGuard.sol +++ b/contracts/security/ReentrancyGuard.sol @@ -66,4 +66,12 @@ abstract contract ReentrancyGuard { // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } + + /** + * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a + * `nonReentrant` function in the call stack. + */ + function _reentrancyGuardEntered() internal view returns (bool) { + return _status == _ENTERED; + } } diff --git a/test/security/ReentrancyGuard.test.js b/test/security/ReentrancyGuard.test.js index c0116d54989..c7f7d469751 100644 --- a/test/security/ReentrancyGuard.test.js +++ b/test/security/ReentrancyGuard.test.js @@ -23,6 +23,14 @@ contract('ReentrancyGuard', function (accounts) { this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyAttack: failed call'); }); + it('_reentrancyGuardEntered should be true when guarded', async function () { + await this.reentrancyMock.guardedCheckEntered(); + }); + + it('_reentrancyGuardEntered should be false when unguarded', async function () { + await this.reentrancyMock.unguardedCheckNotEntered(); + }); + // The following are more side-effects than intended behavior: // I put them here as documentation, and to monitor any changes // in the side-effects.