Converting tests to support new lazy reentrancy checking.
This commit is contained in:
@@ -34,11 +34,6 @@ contract ReentrantERC20Token is
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
IExchange internal EXCHANGE;
|
||||
|
||||
bytes internal constant REENTRANCY_ILLEGAL_REVERT_REASON = abi.encodeWithSelector(
|
||||
bytes4(keccak256("Error(string)")),
|
||||
"REENTRANCY_ILLEGAL"
|
||||
);
|
||||
|
||||
// All of these functions are potentially vulnerable to reentrancy
|
||||
// We do not test any "noThrow" functions because `fillOrderNoThrow` makes a delegatecall to `fillOrder`
|
||||
enum ExchangeFunction {
|
||||
@@ -52,10 +47,13 @@ contract ReentrantERC20Token is
|
||||
CANCEL_ORDER,
|
||||
BATCH_CANCEL_ORDERS,
|
||||
CANCEL_ORDERS_UP_TO,
|
||||
SET_SIGNATURE_VALIDATOR_APPROVAL
|
||||
SET_SIGNATURE_VALIDATOR_APPROVAL,
|
||||
NONE
|
||||
}
|
||||
|
||||
uint8 internal currentFunctionId = 0;
|
||||
LibOrder.Order[2] internal orders;
|
||||
bytes[2] internal signatures;
|
||||
|
||||
constructor (address _exchange)
|
||||
public
|
||||
@@ -63,15 +61,31 @@ contract ReentrantERC20Token is
|
||||
EXCHANGE = IExchange(_exchange);
|
||||
}
|
||||
|
||||
/// @dev Set the current function that will be called when `transferFrom` is called.
|
||||
/// @dev Set the reentrancy params.
|
||||
/// Because reentrancy is lazily-checked (at the end of the transaction),
|
||||
/// all parameters must be valid in so only the reentrancy revert occurs,
|
||||
/// as opposed to something like an invalid fill error.
|
||||
/// Additionally, these should be distinct from the exchange paramaters
|
||||
/// used to initiate the reentrancy attack.
|
||||
/// @param _currentFunctionId Id that corresponds to function name.
|
||||
function setCurrentFunction(uint8 _currentFunctionId)
|
||||
external
|
||||
/// @param _orders Order to pass to functions.
|
||||
/// @param _signatures Signature for the maker of each order in _orders
|
||||
function setUpUsTheBomb(
|
||||
uint8 _currentFunctionId,
|
||||
LibOrder.Order[2] memory _orders,
|
||||
bytes[2] memory _signature
|
||||
)
|
||||
public
|
||||
{
|
||||
currentFunctionId = _currentFunctionId;
|
||||
for (uint32 i = 0; i < 2; i++) {
|
||||
orders[i] = _orders[i];
|
||||
signatures[i] = _signatures[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev A version of `transferFrom` that attempts to reenter the Exchange contract.
|
||||
/// @dev A version of `transferFrom` that attempts to reenter the Exchange contract
|
||||
/// once.
|
||||
/// @param _from The address of the sender
|
||||
/// @param _to The address of the recipient
|
||||
/// @param _value The amount of token to be transferred
|
||||
@@ -83,72 +97,63 @@ contract ReentrantERC20Token is
|
||||
external
|
||||
returns (bool)
|
||||
{
|
||||
// This order would normally be invalid, but it will be used strictly for testing reentrnacy.
|
||||
// Any reentrancy checks will happen before any other checks that invalidate the order.
|
||||
LibOrder.Order memory order;
|
||||
|
||||
// Initialize remaining null parameters
|
||||
bytes memory signature;
|
||||
LibOrder.Order[] memory orders;
|
||||
uint256[] memory takerAssetFillAmounts;
|
||||
bytes[] memory signatures;
|
||||
bytes memory callData;
|
||||
|
||||
// Create callData for function that corresponds to currentFunctionId
|
||||
if (currentFunctionId == uint8(ExchangeFunction.FILL_ORDER)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.fillOrder.selector,
|
||||
order,
|
||||
0,
|
||||
signature
|
||||
orders[0],
|
||||
orders[0].takerAssetAmount,
|
||||
signatures[0]
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.FILL_OR_KILL_ORDER)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.fillOrKillOrder.selector,
|
||||
order,
|
||||
0,
|
||||
signature
|
||||
orders[0],
|
||||
orders[0].takerAssetAmount,
|
||||
signatures[0]
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_ORDERS)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.batchFillOrders.selector,
|
||||
orders,
|
||||
takerAssetFillAmounts,
|
||||
orders[0].takerAssetFillAmount + orders[1].takerAssetFillAmount,
|
||||
signatures
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_OR_KILL_ORDERS)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.batchFillOrKillOrders.selector,
|
||||
orders,
|
||||
takerAssetFillAmounts,
|
||||
orders[0],
|
||||
orders[0].takerAssetFillAmount + orders[1].takerAssetFillAmount,
|
||||
signatures
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.MARKET_BUY_ORDERS)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.marketBuyOrders.selector,
|
||||
orders,
|
||||
0,
|
||||
orders[0].takerAssetFillAmount + orders[1].takerAssetFillAmount,
|
||||
signatures
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.MARKET_SELL_ORDERS)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.marketSellOrders.selector,
|
||||
orders,
|
||||
0,
|
||||
orders[0].takerAssetFillAmount + orders[1].takerAssetFillAmount,
|
||||
signatures
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.MATCH_ORDERS)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.matchOrders.selector,
|
||||
order,
|
||||
order,
|
||||
signature,
|
||||
signature
|
||||
orders[0],
|
||||
order[1],
|
||||
signature[0],
|
||||
signature[1]
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDER)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.cancelOrder.selector,
|
||||
order
|
||||
orders[0]
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.BATCH_CANCEL_ORDERS)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
@@ -158,7 +163,7 @@ contract ReentrantERC20Token is
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDERS_UP_TO)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
EXCHANGE.cancelOrdersUpTo.selector,
|
||||
0
|
||||
orders[1].salt
|
||||
);
|
||||
} else if (currentFunctionId == uint8(ExchangeFunction.SET_SIGNATURE_VALIDATOR_APPROVAL)) {
|
||||
callData = abi.encodeWithSelector(
|
||||
@@ -168,21 +173,15 @@ contract ReentrantERC20Token is
|
||||
);
|
||||
}
|
||||
|
||||
// Call Exchange function, swallow error
|
||||
address(EXCHANGE).call(callData);
|
||||
|
||||
// Revert reason is 100 bytes
|
||||
bytes memory returnData = new bytes(100);
|
||||
|
||||
// Copy return data
|
||||
assembly {
|
||||
returndatacopy(add(returnData, 32), 0, 100)
|
||||
if (callData.length > 0) {
|
||||
// Reset the current function to reenter so we don't do this infinitely.
|
||||
currentFunctionId = uint8(ExchangeFunction.NONE);
|
||||
// Call Exchange function.
|
||||
// Reentrancy guard is lazy-evaluated, so this will succeed.
|
||||
address(EXCHANGE).call(callData);
|
||||
}
|
||||
|
||||
// Revert if function reverted with REENTRANCY_ILLEGAL error
|
||||
require(!REENTRANCY_ILLEGAL_REVERT_REASON.equals(returnData));
|
||||
|
||||
// Transfer will return true if function failed for any other reason
|
||||
// Always succeed.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ describe('Exchange core', () => {
|
||||
await reentrantErc20Token.setCurrentFunction.awaitTransactionSuccessAsync(functionId);
|
||||
await expectTransactionFailedAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.TransferFailed,
|
||||
RevertReason.ReentrancyIllegal,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user