Update exchange reentrancy tests to work with lazy reentrancy detection.

This commit is contained in:
Lawrence 2019-03-14 11:27:13 -04:00 committed by Amir Bandeali
parent cf0e57d7ce
commit ff0ad53c11
4 changed files with 183 additions and 71 deletions

View File

@ -21,7 +21,9 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-erc20/contracts/src/ERC20Token.sol"; import "@0x/contracts-erc20/contracts/src/ERC20Token.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "../src/interfaces/IExchange.sol"; import "../src/interfaces/IExchange.sol";
import "../src/interfaces/IAssetProxy.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
@ -31,6 +33,8 @@ contract ReentrantERC20Token is
{ {
using LibBytes for bytes; using LibBytes for bytes;
uint8 constant BATCH_SIZE = 3;
// solhint-disable-next-line var-name-mixedcase // solhint-disable-next-line var-name-mixedcase
IExchange internal EXCHANGE; IExchange internal EXCHANGE;
@ -52,8 +56,6 @@ contract ReentrantERC20Token is
} }
uint8 internal currentFunctionId = 0; uint8 internal currentFunctionId = 0;
LibOrder.Order[2] internal orders;
bytes[2] internal signatures;
constructor (address _exchange) constructor (address _exchange)
public public
@ -61,27 +63,15 @@ contract ReentrantERC20Token is
EXCHANGE = IExchange(_exchange); EXCHANGE = IExchange(_exchange);
} }
/// @dev Set the reentrancy params. /// @dev Set the exchange function to reenter.
/// Because reentrancy is lazily-checked (at the end of the transaction), /// @param _currentFunctionId A number that corresponds to an entry in the
/// all parameters must be valid in so only the reentrancy revert occurs, /// ExchangeFunction enum
/// as opposed to something like an invalid fill error. function setReentrantFunction(
/// Additionally, these should be distinct from the exchange paramaters uint8 _currentFunctionId
/// used to initiate the reentrancy attack.
/// @param _currentFunctionId Id that corresponds to function name.
/// @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 public
{ {
currentFunctionId = _currentFunctionId; 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
@ -98,90 +88,211 @@ contract ReentrantERC20Token is
returns (bool) returns (bool)
{ {
bytes memory callData; bytes memory callData;
// Create callData for function that corresponds to currentFunctionId // Create callData for function that corresponds to currentFunctionId
if (currentFunctionId == uint8(ExchangeFunction.FILL_ORDER)) { if (currentFunctionId == uint8(ExchangeFunction.FILL_ORDER)) {
LibOrder.Order memory order = _createOrders(1)[0];
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.fillOrder.selector, EXCHANGE.fillOrder.selector,
orders[0], order,
orders[0].takerAssetAmount, order.takerAssetAmount,
signatures[0] _createWalletSignatures(1)[0]
); );
} else if (currentFunctionId == uint8(ExchangeFunction.FILL_OR_KILL_ORDER)) { } else if (currentFunctionId == uint8(ExchangeFunction.FILL_OR_KILL_ORDER)) {
LibOrder.Order memory order = _createOrders(1)[0];
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.fillOrKillOrder.selector, EXCHANGE.fillOrKillOrder.selector,
orders[0], order,
orders[0].takerAssetAmount, order.takerAssetAmount,
signatures[0] _createWalletSignatures(1)[0]
); );
} else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_ORDERS)) { } else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_ORDERS)) {
LibOrder.Order[] memory orders = _createOrders(BATCH_SIZE);
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.batchFillOrders.selector, EXCHANGE.batchFillOrders.selector,
orders, orders,
orders[0].takerAssetFillAmount + orders[1].takerAssetFillAmount, _getTakerFillAmounts(orders),
signatures _createWalletSignatures(BATCH_SIZE)
); );
} else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_OR_KILL_ORDERS)) { } else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_OR_KILL_ORDERS)) {
LibOrder.Order[] memory orders = _createOrders(BATCH_SIZE);
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.batchFillOrKillOrders.selector, EXCHANGE.batchFillOrKillOrders.selector,
orders[0], orders,
orders[0].takerAssetFillAmount + orders[1].takerAssetFillAmount, _getTakerFillAmounts(orders),
signatures _createWalletSignatures(BATCH_SIZE)
); );
} else if (currentFunctionId == uint8(ExchangeFunction.MARKET_BUY_ORDERS)) { } else if (currentFunctionId == uint8(ExchangeFunction.MARKET_BUY_ORDERS)) {
LibOrder.Order[] memory orders = _createOrders(BATCH_SIZE);
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.marketBuyOrders.selector, EXCHANGE.marketBuyOrders.selector,
orders, orders,
orders[0].takerAssetFillAmount + orders[1].takerAssetFillAmount, _sumTakerFillAmounts(orders),
signatures _createWalletSignatures(BATCH_SIZE)
); );
} else if (currentFunctionId == uint8(ExchangeFunction.MARKET_SELL_ORDERS)) { } else if (currentFunctionId == uint8(ExchangeFunction.MARKET_SELL_ORDERS)) {
LibOrder.Order[] memory orders = _createOrders(BATCH_SIZE);
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.marketSellOrders.selector, EXCHANGE.marketSellOrders.selector,
orders, orders,
orders[0].takerAssetFillAmount + orders[1].takerAssetFillAmount, _sumTakerFillAmounts(orders),
signatures _createWalletSignatures(BATCH_SIZE)
); );
} else if (currentFunctionId == uint8(ExchangeFunction.MATCH_ORDERS)) { } else if (currentFunctionId == uint8(ExchangeFunction.MATCH_ORDERS)) {
LibOrder.Order[2] memory orders = _createMatchedOrders();
bytes[] memory signatures = _createWalletSignatures(2);
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.matchOrders.selector, EXCHANGE.matchOrders.selector,
orders[0], orders[0],
order[1], orders[1],
signature[0], signatures[0],
signature[1] signatures[1]
); );
} else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDER)) { } else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDER)) {
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.cancelOrder.selector, EXCHANGE.cancelOrder.selector,
orders[0] _createOrders(1)[0]
); );
} else if (currentFunctionId == uint8(ExchangeFunction.BATCH_CANCEL_ORDERS)) { } else if (currentFunctionId == uint8(ExchangeFunction.BATCH_CANCEL_ORDERS)) {
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.batchCancelOrders.selector, EXCHANGE.batchCancelOrders.selector,
orders _createOrders(BATCH_SIZE)
); );
} else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDERS_UP_TO)) { } else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDERS_UP_TO)) {
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.cancelOrdersUpTo.selector, EXCHANGE.cancelOrdersUpTo.selector,
orders[1].salt 1
); );
} else if (currentFunctionId == uint8(ExchangeFunction.SET_SIGNATURE_VALIDATOR_APPROVAL)) { } else if (currentFunctionId == uint8(ExchangeFunction.SET_SIGNATURE_VALIDATOR_APPROVAL)) {
callData = abi.encodeWithSelector( callData = abi.encodeWithSelector(
EXCHANGE.setSignatureValidatorApproval.selector, EXCHANGE.setSignatureValidatorApproval.selector,
address(0), _getRandomAddress(),
false false
); );
} else {
return true;
} }
if (callData.length > 0) {
// Reset the current function to reenter so we don't do this infinitely. // Reset the current function to reenter so we don't do this infinitely.
currentFunctionId = uint8(ExchangeFunction.NONE); currentFunctionId = uint8(ExchangeFunction.NONE);
// Call Exchange function. // Call Exchange function.
// Reentrancy guard is lazy-evaluated, so this will succeed. // Reentrancy guard is lazy-evaluated, so this should succeed.
address(EXCHANGE).call(callData); (bool success,) = address(EXCHANGE).call(callData);
} require(success);
// Always succeed.
return true; return true;
} }
/// @dev Validates the empty wallet signatures we generate.
function isValidSignature(
bytes32 orderHash,
bytes calldata signature
)
external
pure
returns (bool)
{
// Always return true.
return true;
}
/// @dev Create valid test orders where the maker is set to this contract
/// to succeed in cancel* calls.
function _createOrders(
uint8 count
)
internal
view
returns (LibOrder.Order[] memory orders)
{
orders = new LibOrder.Order[](count);
for (uint8 i = 0; i < count; i++) {
orders[i].makerAddress = address(this);
orders[i].takerAddress = address(0x0);
orders[i].feeRecipientAddress = _getRandomAddress();
orders[i].senderAddress = address(0x0);
orders[i].makerAssetAmount = 1 ether;
orders[i].takerAssetAmount = 2 ether;
orders[i].makerFee = 0;
orders[i].takerFee = 0;
orders[i].expirationTimeSeconds = now + 60 * 60 * 24;
orders[i].salt = now + i;
orders[i].makerAssetData = _createAssetData();
orders[i].takerAssetData = _createAssetData();
}
}
/// @dev Create two complementary test orders.
function _createMatchedOrders()
internal
view
returns (LibOrder.Order[2] memory orders) {
LibOrder.Order[] memory _orders = _createOrders(2);
orders[0] = _orders[0];
orders[1] = _orders[1];
orders[1].takerAssetAmount = orders[1].makerAssetAmount;
orders[1].makerAssetAmount = orders[0].takerAssetAmount;
}
function _getTakerFillAmounts(
LibOrder.Order[] memory orders
)
internal
pure
returns (uint256[] memory amounts)
{
amounts = new uint256[](orders.length);
for (uint8 i = 0; i < orders.length; i++) {
amounts[i] = orders[i].takerAssetAmount;
}
}
function _sumTakerFillAmounts(
LibOrder.Order[] memory orders
)
internal
pure
returns (uint256 total)
{
total = 0;
for (uint8 i = 0; i < orders.length; i++) {
total += orders[i].takerAssetAmount;
}
}
function _getRandomAddress() internal view returns (address)
{
return address(
bytes20(
keccak256(
abi.encodePacked(
blockhash(block.number-1), now)
)
)
);
}
/// @dev Create empty wallet-verified signatures.
function _createWalletSignatures(
uint8 count
)
internal
returns (bytes[] memory signatures)
{
signatures = new bytes[](count);
for (uint i = 0; i < count; i++) {
signatures[i] = new bytes(66);
signatures[i][65] = bytes1(uint8(0x4));
}
}
/// @dev Create asset data that points to this ERC20 contract.
function _createAssetData() internal view returns (bytes memory assetData){
assetData = new bytes(36);
assembly {
mstore(assetData, 36)
mstore(add(assetData, 32), 0xf47261b000000000000000000000000000000000000000000000000000000000)
mstore(add(assetData, 36), address)
}
return assetData;
}
} }

View File

@ -148,7 +148,6 @@ describe('Exchange core', () => {
txDefaults, txDefaults,
exchange.address, exchange.address,
); );
// Configure ERC20Proxy // Configure ERC20Proxy
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(exchange.address, { from: owner }); await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(exchange.address, { from: owner });
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(multiAssetProxy.address, { from: owner }); await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(multiAssetProxy.address, { from: owner });
@ -214,6 +213,8 @@ describe('Exchange core', () => {
}; };
const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
orderFactory = new OrderFactory(privateKey, defaultOrderParams); orderFactory = new OrderFactory(privateKey, defaultOrderParams);
// Grant the reentrant ERC20 token a
}); });
beforeEach(async () => { beforeEach(async () => {
await blockchainLifecycle.startAsync(); await blockchainLifecycle.startAsync();
@ -228,13 +229,13 @@ describe('Exchange core', () => {
}); });
const reentrancyTest = (functionNames: string[]) => { const reentrancyTest = (functionNames: string[]) => {
_.forEach(functionNames, async (functionName: string, functionId: number) => { _.forEach(functionNames, (functionName: string, functionId: number) => {
const description = `should not allow fillOrder to reenter the Exchange contract via ${functionName}`; const description = `should not allow fillOrder to reenter the Exchange contract via ${functionName}`;
it(description, async () => { it(description, async () => {
signedOrder = await orderFactory.newSignedOrderAsync({ signedOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: await assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await reentrantErc20Token.setCurrentFunction.awaitTransactionSuccessAsync(functionId); await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId);
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
RevertReason.ReentrancyIllegal, RevertReason.ReentrancyIllegal,

View File

@ -576,12 +576,12 @@ describe('matchOrders', () => {
feeRecipientAddress: feeRecipientAddressRight, feeRecipientAddress: feeRecipientAddressRight,
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
RevertReason.TransferFailed, RevertReason.ReentrancyIllegal,
); );
}); });
}); });

View File

@ -143,12 +143,12 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress),
RevertReason.TransferFailed, RevertReason.ReentrancyIllegal,
); );
}); });
}); });
@ -234,7 +234,7 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress);
@ -453,12 +453,12 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
exchangeWrapper.batchFillOrdersAsync([signedOrder], takerAddress), exchangeWrapper.batchFillOrdersAsync([signedOrder], takerAddress),
RevertReason.TransferFailed, RevertReason.ReentrancyIllegal,
); );
}); });
}); });
@ -522,12 +522,12 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
exchangeWrapper.batchFillOrKillOrdersAsync([signedOrder], takerAddress), exchangeWrapper.batchFillOrKillOrdersAsync([signedOrder], takerAddress),
RevertReason.TransferFailed, RevertReason.ReentrancyIllegal,
); );
}); });
}); });
@ -608,7 +608,7 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await exchangeWrapper.batchFillOrdersNoThrowAsync([signedOrder], takerAddress); await exchangeWrapper.batchFillOrdersNoThrowAsync([signedOrder], takerAddress);
@ -740,14 +740,14 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
exchangeWrapper.marketSellOrdersAsync([signedOrder], takerAddress, { exchangeWrapper.marketSellOrdersAsync([signedOrder], takerAddress, {
takerAssetFillAmount: signedOrder.takerAssetAmount, takerAssetFillAmount: signedOrder.takerAssetAmount,
}), }),
RevertReason.TransferFailed, RevertReason.ReentrancyIllegal,
); );
}); });
}); });
@ -854,7 +854,7 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await exchangeWrapper.marketSellOrdersNoThrowAsync([signedOrder], takerAddress, { await exchangeWrapper.marketSellOrdersNoThrowAsync([signedOrder], takerAddress, {
@ -1001,14 +1001,14 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await expectTransactionFailedAsync( await expectTransactionFailedAsync(
exchangeWrapper.marketBuyOrdersAsync([signedOrder], takerAddress, { exchangeWrapper.marketBuyOrdersAsync([signedOrder], takerAddress, {
makerAssetFillAmount: signedOrder.makerAssetAmount, makerAssetFillAmount: signedOrder.makerAssetAmount,
}), }),
RevertReason.TransferFailed, RevertReason.ReentrancyIllegal,
); );
}); });
}); });
@ -1113,7 +1113,7 @@ describe('Exchange wrappers', () => {
makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address),
}); });
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), await reentrantErc20Token.setReentrantFunction.sendTransactionAsync(functionId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
await exchangeWrapper.marketBuyOrdersNoThrowAsync([signedOrder], takerAddress, { await exchangeWrapper.marketBuyOrdersNoThrowAsync([signedOrder], takerAddress, {