Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
226 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* SPDX-License-Identitifer: MIT | ||
*/ | ||
|
||
pragma solidity ^0.4.24; | ||
|
||
import "../common/UnstructuredStorage.sol"; | ||
|
||
|
||
contract ReentrancyGuard { | ||
using UnstructuredStorage for bytes32; | ||
|
||
/* Hardcoded constants to save gas | ||
bytes32 internal constant REENTRANCY_MUTEX_POSITION = keccak256("aragonOS.reentrancyGuard.mutex"); | ||
*/ | ||
bytes32 private constant REENTRANCY_MUTEX_POSITION = 0xe855346402235fdd185c890e68d2c4ecad599b88587635ee285bce2fda58dacb; | ||
|
||
string private constant ERROR_REENTRANT = "REENTRANCY_REENTRANT_CALL"; | ||
|
||
modifier nonReentrant() { | ||
// Ensure mutex is unlocked | ||
require(!REENTRANCY_MUTEX_POSITION.getStorageBool(), ERROR_REENTRANT); | ||
|
||
// Lock mutex before function call | ||
REENTRANCY_MUTEX_POSITION.setStorageBool(true); | ||
|
||
// Perform function call | ||
_; | ||
|
||
// Unlock mutex after function call | ||
REENTRANCY_MUTEX_POSITION.setStorageBool(false); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
pragma solidity 0.4.24; | ||
|
||
import "../../common/ReentrancyGuard.sol"; | ||
import "../../common/UnstructuredStorage.sol"; | ||
|
||
|
||
contract ReentrantActor { | ||
bool reenterNonReentrant; | ||
|
||
constructor(bool _reenterNonReentrant) public { | ||
reenterNonReentrant = _reenterNonReentrant; | ||
} | ||
|
||
function reenter(ReentrancyGuardMock _mock) public { | ||
// Set the reentrancy target to 0 so we don't infinite loop | ||
ReentrantActor reentrancyTarget = ReentrantActor(0); | ||
|
||
if (reenterNonReentrant) { | ||
_mock.nonReentrantCall(reentrancyTarget); | ||
} else { | ||
_mock.reentrantCall(reentrancyTarget); | ||
} | ||
} | ||
} | ||
|
||
|
||
contract ReentrancyGuardMock is ReentrancyGuard { | ||
using UnstructuredStorage for bytes32; | ||
|
||
uint256 public callCounter; | ||
|
||
function nonReentrantCall(ReentrantActor _target) public nonReentrant { | ||
callCounter++; | ||
if (_target != address(0)) { | ||
_target.reenter(this); | ||
} | ||
} | ||
|
||
function reentrantCall(ReentrantActor _target) public { | ||
callCounter++; | ||
if (_target != address(0)) { | ||
_target.reenter(this); | ||
} | ||
} | ||
|
||
function setReentrancyMutex(bool _mutex) public { | ||
getReentrancyMutexPosition().setStorageBool(_mutex); | ||
} | ||
|
||
function getReentrancyMutexPosition() public pure returns (bytes32) { | ||
return keccak256("aragonOS.reentrancyGuard.mutex"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
const { assertRevert } = require('./helpers/assertThrow') | ||
|
||
const ZERO_ADDR = '0x0000000000000000000000000000000000000000' | ||
|
||
contract('ReentrancyGuard', accounts => { | ||
let reentrancyMock | ||
|
||
async function assertReentrancyGuard(guard, msg) { | ||
assert.equal( | ||
await web3.eth.getStorageAt(reentrancyMock.address, (await reentrancyMock.getReentrancyMutexPosition())), | ||
guard, | ||
msg | ||
) | ||
} | ||
|
||
beforeEach(async () => { | ||
reentrancyMock = await artifacts.require('ReentrancyGuardMock').new() | ||
}) | ||
|
||
it('starts with false mutex', async () => { | ||
await assertReentrancyGuard(false, 're-entrancy mutex should start false') | ||
}) | ||
|
||
it('starts with no calls', async () => { | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 0, 'should start with no calls') | ||
}) | ||
|
||
it('can call re-entrant function normally', async () => { | ||
await reentrancyMock.reentrantCall(ZERO_ADDR) | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 1, 'should have registered one call') | ||
await assertReentrancyGuard(false, 're-entrancy mutex should end false') | ||
}) | ||
|
||
it('can call non-re-entrant function normally', async () => { | ||
await reentrancyMock.nonReentrantCall(ZERO_ADDR) | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 1, 'should have registered one call') | ||
await assertReentrancyGuard(false, 're-entrancy mutex should end false') | ||
}) | ||
|
||
context('> Enabled re-entrancy mutex', () => { | ||
beforeEach(async () => { | ||
// Manually set re-entrancy mutex | ||
await reentrancyMock.setReentrancyMutex(true) | ||
}) | ||
|
||
it('can call re-entrant function if re-entrancy mutex is enabled', async () => { | ||
await reentrancyMock.reentrantCall(ZERO_ADDR) | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 1, 'should have called') | ||
}) | ||
|
||
it('can not call non-re-entrant function if re-entrancy mutex is enabled', async () => { | ||
await assertRevert(async () => { | ||
await reentrancyMock.nonReentrantCall(ZERO_ADDR) | ||
}) | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 0, 'should not have called') | ||
}) | ||
}) | ||
|
||
context('> Re-entering through contract', async () => { | ||
let reentrantActor | ||
|
||
afterEach(async () => { | ||
await assertReentrancyGuard(false, 're-entrancy mutex should end false') | ||
}) | ||
|
||
context('> Re-enters re-entrant call', async () => { | ||
before(async () => { | ||
reentrantActor = await artifacts.require('ReentrantActor').new(false) | ||
}) | ||
|
||
it('allows re-entering re-entrant call', async () => { | ||
await reentrancyMock.reentrantCall(reentrantActor.address) | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 2, 'should have called twice') | ||
}) | ||
|
||
it('allows entering non-re-entrant call from re-entrant call', async () => { | ||
await reentrancyMock.nonReentrantCall(reentrantActor.address) | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 2, 'should have called twice') | ||
}) | ||
}) | ||
|
||
context('> Re-enters non-reentrant call', async () => { | ||
before(async () => { | ||
reentrantActor = await artifacts.require('ReentrantActor').new(true) | ||
}) | ||
|
||
it('disallows re-entering non-re-entrant call', async () => { | ||
await assertRevert(async () => { | ||
await reentrancyMock.nonReentrantCall(reentrantActor.address) | ||
}) | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 0, 'should not have completed any calls') | ||
}) | ||
|
||
it('allows entering non-entrant call from re-entrant call', async () => { | ||
await reentrancyMock.reentrantCall(reentrantActor.address) | ||
assert.equal((await reentrancyMock.callCounter()).toString(), 2, 'should have called twice') | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters