Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Reentrancy-Guard modifier to external MP methods #1334

Merged
merged 39 commits into from
May 5, 2020
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
12ae7bf
replace transfer with requestTransfer/cancelTransfer/transferPassedRe…
nicholaspai Apr 28, 2020
f1cf668
add requestTransferPassTimestamp to PositionData struct
nicholaspai Apr 28, 2020
ae729fa
enforce check-effects-interaction pattern throughout NFCT
nicholaspai Apr 29, 2020
56b4ca8
merge master
nicholaspai Apr 29, 2020
141d88a
fix tests
nicholaspai Apr 29, 2020
5adc740
Merge branch 'master' into npai/nfct-reentrancy-guard
nicholaspai Apr 29, 2020
ff42ea5
add Reentrancy guard to FeePayer, currently out of bytecode size
nicholaspai Apr 29, 2020
68598cd
Fixed gas limit
chrismaree Apr 29, 2020
9a3344d
fixed branch
chrismaree Apr 29, 2020
aa24554
add ReentrancyGuard to all NFCT contracts
nicholaspai Apr 29, 2020
c7d2100
increase gas in dapp-test ci
nicholaspai Apr 29, 2020
b76a9ff
extend ReentrancyGuard and add viewLock method
nicholaspai Apr 29, 2020
8347a75
rename local ReentryGuard, add TODO's
nicholaspai Apr 30, 2020
32f60e8
remove nonReentrant from internal method
nicholaspai Apr 30, 2020
2f3f5f4
make EMPCreator Lockable
nicholaspai Apr 30, 2020
c478dd1
merge master
nicholaspai Apr 30, 2020
50143fa
add nonReentrant to all state-modifying public methods but no constru…
nicholaspai May 1, 2020
a7567e2
add view-only lock to all public view methods
nicholaspai May 1, 2020
e4532f5
revert pfc() in Liquidatable
nicholaspai May 1, 2020
8b34b5b
update comment
nicholaspai May 1, 2020
00321a6
fix comments
nicholaspai May 1, 2020
8509bcc
add comment about pfc
nicholaspai May 3, 2020
796001d
Merge branch 'master' into npai/nfct-reentrancy-guard
nicholaspai May 3, 2020
b168e94
add unit tests for Lockable.sol
nicholaspai May 4, 2020
207e9cb
add credits
nicholaspai May 4, 2020
856dd44
add address(this).callViewFunction tests
nicholaspai May 4, 2020
e992961
slither ignore
nicholaspai May 4, 2020
61bef03
fix typo in Lockable, move nonReentrant to EMP from FeePayer
nicholaspai May 4, 2020
9ad2009
add nonReentrantView to public pfc() methods
nicholaspai May 5, 2020
acf1b3b
fix local variable shadowing
nicholaspai May 5, 2020
6c635d8
add nonReentrancy to all constructors
nicholaspai May 5, 2020
00dafe1
add require messages
nicholaspai May 5, 2020
9026624
add require messages after merge with master
nicholaspai May 5, 2020
5552bf1
merge master
nicholaspai May 5, 2020
126fc0d
make sure nonReentrant modifier is last
nicholaspai May 5, 2020
696f4a0
merge master
nicholaspai May 5, 2020
c8e84ef
Merge branch 'master' into npai/nfct-reentrancy-guard
nicholaspai May 5, 2020
996e773
move pfc() public method to feepayer
nicholaspai May 5, 2020
55bdb2e
rename pfc to collateralPool to avoid local variable shadowing
nicholaspai May 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
docker:
- image: circleci/node:lts
- image: trufflesuite/ganache-cli
command: ganache-cli -i 1234 -l 6720000
command: ganache-cli -i 1234 -l 9000000
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
working_directory: ~/protocol
steps:
- restore_cache:
Expand Down Expand Up @@ -149,7 +149,7 @@ jobs:
docker:
- image: circleci/node:lts
- image: trufflesuite/ganache-cli
command: ganache-cli -i 1234 -l 6720000 -p 9545 -m "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"
command: ganache-cli -i 1234 -l 9000000 -p 9545 -m "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"
working_directory: ~/protocol
steps:
- restore_cache:
Expand Down
2 changes: 1 addition & 1 deletion ci/run_slither.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ run_slither() {

# print out slither version for debugging
slither --version
slither --exclude=divide-before-multiply,locked-ether,unused-return,timestamp,naming-convention,pragma,solc-version,external-function,reentrancy-benign,reentrancy-no-eth,arbitrary-send,incorrect-equality,reentrancy-events,assembly --filter-paths="@openzeppelin|WETH9.sol" $1
slither --exclude=divide-before-multiply,locked-ether,unused-return,timestamp,naming-convention,pragma,solc-version,external-function,reentrancy-benign,reentrancy-no-eth,arbitrary-send,incorrect-equality,reentrancy-events,assembly --filter-paths="@openzeppelin|WETH9.sol|ReentrancyAttack.sol|ReentrancyMock.sol" $1
}

run_slither $PROTOCOL_DIR/core
2 changes: 1 addition & 1 deletion common/globalTruffleConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const infuraApiKey = process.env.INFURA_API_KEY ? process.env.INFURA_API_KEY : "

// Default options
const gasPx = 20000000000; // 20 gwei
const gas = 6720000; // Conservative estimate of the block gas limit.
const gas = 9000000; // Conservative estimate of the block gas limit.

// Adds a public network.
// Note: All public networks can be accessed using keys from GCS using the ManagedSecretProvider or using a mnemonic in the
Expand Down
11 changes: 6 additions & 5 deletions core/contracts/common/implementation/AddressWhitelist.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "./Lockable.sol";


/**
* @title A contract to track a whitelist of addresses.
*/
contract AddressWhitelist is Ownable {
contract AddressWhitelist is Ownable, Lockable {
enum Status { None, In, Out }
mapping(address => Status) public whitelist;

Expand All @@ -19,7 +20,7 @@ contract AddressWhitelist is Ownable {
* @notice Adds an address to the whitelist.
* @param newElement the new address to add.
*/
function addToWhitelist(address newElement) external onlyOwner {
function addToWhitelist(address newElement) external nonReentrant() onlyOwner {
// Ignore if address is already included
if (whitelist[newElement] == Status.In) {
return;
Expand All @@ -39,7 +40,7 @@ contract AddressWhitelist is Ownable {
* @notice Removes an address from the whitelist.
* @param elementToRemove the existing address to remove.
*/
function removeFromWhitelist(address elementToRemove) external onlyOwner {
function removeFromWhitelist(address elementToRemove) external nonReentrant() onlyOwner {
if (whitelist[elementToRemove] != Status.Out) {
whitelist[elementToRemove] = Status.Out;
emit RemovedFromWhitelist(elementToRemove);
Expand All @@ -51,7 +52,7 @@ contract AddressWhitelist is Ownable {
* @param elementToCheck the address to check.
* @return True if `elementToCheck` is on the whitelist, or False.
*/
function isOnWhitelist(address elementToCheck) external view returns (bool) {
function isOnWhitelist(address elementToCheck) external view nonReentrantView() returns (bool) {
return whitelist[elementToCheck] == Status.In;
}

Expand All @@ -63,7 +64,7 @@ contract AddressWhitelist is Ownable {
* the empty index.
* @return activeWhitelist the list of addresses on the whitelist.
*/
function getWhitelist() external view returns (address[] memory activeWhitelist) {
function getWhitelist() external view nonReentrantView() returns (address[] memory activeWhitelist) {
// Determine size of whitelist first
uint256 activeCount = 0;
for (uint256 i = 0; i < whitelistIndices.length; i++) {
Expand Down
63 changes: 63 additions & 0 deletions core/contracts/common/implementation/Lockable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
pragma solidity ^0.6.0;


/**
* @title A contract that provides modifiers to prevent reentrancy to state-changing and view-only methods. This contract
* is inspired by https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol
* and https://github.com/balancer-labs/balancer-core/blob/master/contracts/BPool.sol.
*/
contract Lockable {
bool private _notEntered;

constructor() internal {
// Storing an initial non-zero value makes deployment a bit more
// expensive, but in exchange the refund on every call to nonReentrant
// will be lower in amount. Since refunds are capped to a percetange of
// the total transaction's gas, it is best to keep them low in cases
// like this one, to increase the likelihood of the full refund coming
// into effect.
_notEntered = true;
}

/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_preEntranceCheck();
_preEntranceSet();
_;
_postEntranceReset();
}

/**
* @dev Designed to prevent a view-only method from being re-entered during a call to a `nonReentrant()` state-changing method.
*/
modifier nonReentrantView() {
_preEntranceCheck();
_;
}

// Internal methods are used to avoid copying the require statement's bytecode to every `nonReentrant()` method.
// On entry into a function, `_preEntranceCheck()` should always be called to check if the function is being re-entered.
// Then, if the function modifies state, it should call `_postEntranceSet()`, perform its logic, and then call `_postEntranceReset()`.
// View-only methods can simply call `_preEntranceCheck()` to make sure that it is not being re-entered.
function _preEntranceCheck() internal view {
// On the first call to nonReentrant, _notEntered will be true
require(_notEntered, "ReentrancyGuard: reentrant call");
}

function _preEntranceSet() internal {
// Any calls to nonReentrant after this point will fail
_notEntered = false;
}

function _postEntranceReset() internal {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
}
12 changes: 12 additions & 0 deletions core/contracts/common/test/ReentrancyAttack.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pragma solidity ^0.6.0;


// Tests reentrancy guards defined in Lockable.sol.
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/mocks/ReentrancyAttack.sol.
contract ReentrancyAttack {
function callSender(bytes4 data) public {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = msg.sender.call(abi.encodeWithSelector(data));
require(success, "ReentrancyAttack: failed call");
}
}
65 changes: 65 additions & 0 deletions core/contracts/common/test/ReentrancyMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
pragma solidity ^0.6.0;

import "../implementation/Lockable.sol";
import "./ReentrancyAttack.sol";


// Tests reentrancy guards defined in Lockable.sol.
// Extends https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/mocks/ReentrancyMock.sol.
contract ReentrancyMock is Lockable {
uint256 public counter;

constructor() public {
counter = 0;
}

function callback() external nonReentrant {
_count();
}

function countAndSend(ReentrancyAttack attacker) external nonReentrant {
_count();
bytes4 func = bytes4(keccak256("callback()"));
attacker.callSender(func);
}

function countAndCall(ReentrancyAttack attacker) external nonReentrant {
_count();
bytes4 func = bytes4(keccak256("getCount()"));
attacker.callSender(func);
}

function countLocalRecursive(uint256 n) public nonReentrant {
if (n > 0) {
_count();
countLocalRecursive(n - 1);
}
}

function countThisRecursive(uint256 n) public nonReentrant {
if (n > 0) {
_count();
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", n - 1));
require(success, "ReentrancyMock: failed call");
}
}

function countLocalCall() public nonReentrant {
getCount();
}

function countThisCall() public nonReentrant {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(this).call(abi.encodeWithSignature("getCount()"));
require(success, "ReentrancyMock: failed call");
}

function getCount() public view nonReentrantView returns (uint256) {
return counter;
}

function _count() private {
counter += 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import "./Liquidatable.sol";


contract ExpiringMultiParty is Liquidatable {
constructor(ConstructorParams memory params) public Liquidatable(params) {}
constructor(ConstructorParams memory params) public Liquidatable(params) nonReentrant() {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does nonReentrant() go into effect before or after the Liquidatable constructor gets called? Is it possible to put this modifier before the Liquidatable constructor is called? Or if it's not, maybe it makes sense to add nonReentrant() to all the constructors (since they shouldn't interfere with one another)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructors do not appear to interfere with each other so I just went ahead and put it on all of them.

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma experimental ABIEncoderV2;
import "../../oracle/implementation/ContractCreator.sol";
import "../../common/implementation/Testable.sol";
import "../../common/implementation/AddressWhitelist.sol";
import "../../common/implementation/Lockable.sol";
import "./ExpiringMultiParty.sol";


Expand All @@ -12,7 +13,7 @@ import "./ExpiringMultiParty.sol";
* @notice Factory contract to create and register new instances of expiring multiparty contracts.
* Responsible for constraining the parameters used to construct a new EMP.
*/
contract ExpiringMultiPartyCreator is ContractCreator, Testable {
contract ExpiringMultiPartyCreator is ContractCreator, Testable, Lockable {
using FixedPoint for FixedPoint.Unsigned;

/****************************************
Expand Down Expand Up @@ -82,7 +83,7 @@ contract ExpiringMultiPartyCreator is ContractCreator, Testable {
address _collateralTokenWhitelist,
address _tokenFactoryAddress,
address _timerAddress
) public ContractCreator(_finderAddress) Testable(_timerAddress) {
) public ContractCreator(_finderAddress) Testable(_timerAddress) nonReentrant() {
collateralTokenWhitelist = AddressWhitelist(_collateralTokenWhitelist);
tokenFactoryAddress = _tokenFactoryAddress;
uint32[16] memory timestamps = [
Expand Down Expand Up @@ -114,7 +115,7 @@ contract ExpiringMultiPartyCreator is ContractCreator, Testable {
* @param params is a `ConstructorParams` object from ExpiringMultiParty.
* @return address of the deployed ExpiringMultiParty contract
*/
function createExpiringMultiParty(Params memory params) public returns (address) {
function createExpiringMultiParty(Params memory params) public nonReentrant() returns (address) {
ExpiringMultiParty derivative = new ExpiringMultiParty(_convertParams(params));

address[] memory parties = new address[](1);
Expand Down
25 changes: 13 additions & 12 deletions core/contracts/financial-templates/implementation/FeePayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "../../common/implementation/Lockable.sol";
import "../../common/implementation/FixedPoint.sol";
import "../../common/implementation/Testable.sol";
import "../../oracle/interfaces/StoreInterface.sol";
Expand All @@ -16,7 +17,7 @@ import "../../oracle/implementation/Constants.sol";
* contract is abstract as each derived contract that inherits `FeePayer` must implement `pfc()`.
*/

abstract contract FeePayer is Testable {
abstract contract FeePayer is Testable, Lockable {
using SafeMath for uint256;
using FixedPoint for FixedPoint.Unsigned;
using SafeERC20 for IERC20;
Expand Down Expand Up @@ -88,13 +89,13 @@ abstract contract FeePayer is Testable {
* regular fee in a week or more then a late penalty is applied which is sent to the caller.
* @return totalPaid Amount of collateral that the contract paid (sum of the amount paid to the Store and caller).
*/
function payRegularFees() public returns (FixedPoint.Unsigned memory totalPaid) {
function payRegularFees() public nonReentrant() returns (FixedPoint.Unsigned memory totalPaid) {
StoreInterface store = _getStore();
uint256 time = getCurrentTime();
FixedPoint.Unsigned memory _pfc = pfc();
FixedPoint.Unsigned memory pfc = _pfc();

// Exit early if there is no pfc (thus, no fees to be paid).
if (_pfc.isEqual(0)) {
if (pfc.isEqual(0)) {
return totalPaid;
}

Expand All @@ -106,7 +107,7 @@ abstract contract FeePayer is Testable {
(FixedPoint.Unsigned memory regularFee, FixedPoint.Unsigned memory latePenalty) = store.computeRegularFee(
lastPaymentTime,
time,
_pfc
pfc
);
lastPaymentTime = time;

Expand All @@ -115,7 +116,7 @@ abstract contract FeePayer is Testable {
totalPaid = regularFee.add(latePenalty);

// Adjust the cumulative fee multiplier by the fee paid and the current PFC.
_adjustCumulativeFeeMultiplier(totalPaid, _pfc);
_adjustCumulativeFeeMultiplier(totalPaid, pfc);

if (regularFee.isGreaterThan(0)) {
collateralCurrency.safeIncreaseAllowance(address(store), regularFee.rawValue);
Expand All @@ -140,13 +141,13 @@ abstract contract FeePayer is Testable {
collateralCurrency.safeTransferFrom(payer, address(this), amount.rawValue);
} else {
// If the payer is the contract, adjust the cumulativeFeeMultiplier to compensate.
FixedPoint.Unsigned memory _pfc = pfc();
FixedPoint.Unsigned memory pfc = _pfc();

// The final fee must be < pfc or the fee will be larger than 100%.
require(_pfc.isGreaterThan(amount));
require(pfc.isGreaterThan(amount));

// Adjust the cumulative fee multiplier by the fee paid and the current PFC.
_adjustCumulativeFeeMultiplier(amount, _pfc);
_adjustCumulativeFeeMultiplier(amount, pfc);
}

emit FinalFeesPaid(amount.rawValue);
Expand All @@ -158,10 +159,10 @@ abstract contract FeePayer is Testable {

/**
* @notice Gets the current profit from corruption for this contract in terms of the collateral currency.
* @dev Derived contracts are expected to implement this function so the payRegularFees()
* method can correctly compute the owed regular fees.
* @dev Derived contracts are expected to implement a public `pfc()` method that calls this internal function so that the payRegularFees()
* method can correctly compute the owed regular fees. The public `pfc()` can therefore have the `nonReentrantView()` modifier.
*/
function pfc() public virtual view returns (FixedPoint.Unsigned memory);
function _pfc() internal virtual view returns (FixedPoint.Unsigned memory);

/****************************************
* INTERNAL FUNCTIONS *
Expand Down
Loading