@0x:contracts-exchange Added tests for fillOrder protocol fees

This commit is contained in:
Alex Towle
2019-08-28 00:14:28 -07:00
parent 7f17033ce3
commit dd0d848530
8 changed files with 452 additions and 90 deletions

29
contracts/exchange/8:p Normal file
View File

@@ -0,0 +1,29 @@
import { blockchainTests, constants } from '@0x/contracts-test-utils';
import { artifacts, ExchangeContract, TestProtocolFeesContract, TestProtocolFeesReceiverContract } from '../src';
blockchainTests('Protocol Fee Payments', env => {
let testProtocolFees: TestProtocolFeesContract;
let testProtocolFeesReceiver: TestProtocolFeesReceiverContract;
before(async () => {
testProtocolFees = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
artifacts.TestProtocolFees,
env.provider,
env.txDefaults,
{},
);
testProtocolFeesReceiver = await TestProtocolFeesReceiverContract.deployFrom0xArtifactAsync(
artifacts.TestProtocolFeesReceiver,
env.provider,
env.txDefaults,
{},
);
});
blockchainTests.resets('fillOrder Protocol Fees', () => {
it('should pay protocol fee in ETH when the correct value is sent', async () => {
await testProtocolFeesReceiver.testFillOrderProtocolFees.awaitTransactionSuccessAsync(;
});
});
});

View File

@@ -0,0 +1,74 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "../src/Exchange.sol";
contract TestProtocolFees is
Exchange
{
// solhint-disable no-empty-blocks
constructor ()
public
Exchange(1337)
{}
// @dev Expose a setter to the `protocolFeeCollector` state variable.
// @param newProtocolFeeCollector The address that should be made the `protocolFeeCollector`.
function setProtocolFeeCollector(address newProtocolFeeCollector)
external
{
protocolFeeCollector = newProtocolFeeCollector;
}
// @dev Expose a setter to the `protocolFeeMultiplier` state variable.
// @param newProtocolFeeMultiplier The number that should be made the `protocolFeeMultiplier`.
function setProtocolFeeMultiplier(uint256 newProtocolFeeMultiplier)
external
{
protocolFeeMultiplier = newProtocolFeeMultiplier;
}
// @dev Stub out the `_assertFillableOrder` function because we don't actually
// care about order validation in these tests.
function _assertFillableOrder(
LibOrder.Order memory,
LibOrder.OrderInfo memory,
address,
bytes memory
)
internal
view
{} // solhint-disable-line no-empty-blocks
// @dev Stub out the `_assertFillableOrder` function because we don't actually
// care about transfering through proxies in these tests.
function _dispatchTransferFrom(
bytes32 orderHash,
bytes memory assetData,
address from,
address to,
uint256 amount
)
internal
{} // solhint-disable-line no-empty-blocks
}

View File

@@ -0,0 +1,173 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "./TestProtocolFees.sol";
// Disable solhint to allow for more informative comments.
// solhint-disable
contract TestProtocolFeesReceiver {
// Attach LibSafeMath to uint256
using LibSafeMath for uint256;
/* Testing Constants */
// A constant to represent a maker address.
address internal constant makerAddress1 = address(1);
// A constant to represent a maker address that is distinct from the
// other maker address.
address internal constant makerAddress2 = address(2);
/* Testing State */
struct TestLog {
address loggedMaker;
address loggedPayer;
uint256 loggedProtocolFeePaid;
uint256 loggedValue;
}
TestLog[] testLogs;
/* Testing Functions */
function testFillOrderProtocolFees(
TestProtocolFees testProtocolFees,
uint256 protocolFeeMultiplier,
bool shouldSetProtocolFeeCollector
)
external
payable
{
// Set up the exchange state for the test.
setExchangeState(
testProtocolFees,
protocolFeeMultiplier,
shouldSetProtocolFeeCollector
);
// Forward all of the value sent to the contract to `fillOrder()` with empty arguments.
LibOrder.Order memory order;
order.makerAddress = makerAddress1;
order.makerAssetAmount = 1;
order.takerAssetAmount = 1;
testProtocolFees.fillOrder.value(msg.value)(order, 0, new bytes(0));
// Ensure that the results of the test were correct.
verifyFillOrderTestResults(protocolFeeMultiplier, shouldSetProtocolFeeCollector);
// Clear all state that was set to ensure that future tests are unaffected by this one.
clearState(testProtocolFees);
}
function verifyFillOrderTestResults(
uint256 protocolFeeMultiplier,
bool shouldSetProtocolFeeCollector
)
internal
{
// If the `protocolFeeCollector` was set, then this contract should have been called.
if (shouldSetProtocolFeeCollector) {
// Calculate the protocol fee that should be paid.
uint256 protocolFee = tx.gasprice.safeMul(protocolFeeMultiplier);
// Ensure that one TestLog was recorded.
require(testLogs.length == 1, "Incorrect TestLog length for fillOrder test");
// Get an alias to the test log.
TestLog memory log = testLogs[0];
// If the forwarded value was greater than the protocol fee, the protocol fee should
// have been sent back to this contract.
if (msg.value >= protocolFee) {
// Ensure that the protocolFee was forwarded to this contract.
require(
log.loggedValue == protocolFee,
"Incorrect eth was received during fillOrder test when adequate ETH was sent"
);
} else {
// Ensure that the protocolFee was forwarded to this contract.
require(
log.loggedValue == 0,
"Incorrect eth was received during fillOrder test when inadequate ETH was sent"
);
}
// Ensure that the correct data was logged during this test.
require(log.loggedMaker == makerAddress1, "Incorrect maker address was logged for fillOrder test");
require(log.loggedPayer == address(this), "Incorrect taker address was logged for fillOrder test");
require(log.loggedProtocolFeePaid == protocolFee, "Incorrect protocol fee was logged for fillOrder test");
} else {
// Ensure that `protocolFeePaid()` was not called.
require(testLogs.length == 0, "protocolFeePaid was called");
}
}
function setExchangeState(
TestProtocolFees testProtocolFees,
uint256 protocolFeeMultiplier,
bool shouldSetProtocolFeeCollector
)
internal
{
if (shouldSetProtocolFeeCollector) {
testProtocolFees.setProtocolFeeCollector(address(this));
}
testProtocolFees.setProtocolFeeMultiplier(protocolFeeMultiplier);
}
function clearState(TestProtocolFees testProtocolFees)
internal
{
// Clear exchange state
testProtocolFees.setProtocolFeeCollector(address(0));
testProtocolFees.setProtocolFeeMultiplier(0);
msg.sender.transfer(address(this).balance);
// Clear this contract's state
delete testLogs;
}
function payProtocolFee(
address makerAddress,
address payerAddress,
uint256 protocolFeePaid
)
external
payable
{
testLogs.push(TestLog({
loggedMaker: makerAddress,
loggedPayer: payerAddress,
loggedProtocolFeePaid: protocolFeePaid,
loggedValue: msg.value
}));
}
function ()
external
payable
{}
}

View File

@@ -34,6 +34,8 @@ import * as ReentrancyTester from '../generated-artifacts/ReentrancyTester.json'
import * as TestAssetProxyDispatcher from '../generated-artifacts/TestAssetProxyDispatcher.json';
import * as TestExchangeInternals from '../generated-artifacts/TestExchangeInternals.json';
import * as TestLibExchangeRichErrorDecoder from '../generated-artifacts/TestLibExchangeRichErrorDecoder.json';
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
import * as TestProtocolFeesReceiver from '../generated-artifacts/TestProtocolFeesReceiver.json';
import * as TestSignatureValidator from '../generated-artifacts/TestSignatureValidator.json';
import * as TestTransactions from '../generated-artifacts/TestTransactions.json';
import * as TestValidatorWallet from '../generated-artifacts/TestValidatorWallet.json';
@@ -70,6 +72,8 @@ export const artifacts = {
TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact,
TestExchangeInternals: TestExchangeInternals as ContractArtifact,
TestLibExchangeRichErrorDecoder: TestLibExchangeRichErrorDecoder as ContractArtifact,
TestProtocolFees: TestProtocolFees as ContractArtifact,
TestProtocolFeesReceiver: TestProtocolFeesReceiver as ContractArtifact,
TestSignatureValidator: TestSignatureValidator as ContractArtifact,
TestTransactions: TestTransactions as ContractArtifact,
TestValidatorWallet: TestValidatorWallet as ContractArtifact,

View File

@@ -32,6 +32,8 @@ export * from '../generated-wrappers/reentrancy_tester';
export * from '../generated-wrappers/test_asset_proxy_dispatcher';
export * from '../generated-wrappers/test_exchange_internals';
export * from '../generated-wrappers/test_lib_exchange_rich_error_decoder';
export * from '../generated-wrappers/test_protocol_fees';
export * from '../generated-wrappers/test_protocol_fees_receiver';
export * from '../generated-wrappers/test_signature_validator';
export * from '../generated-wrappers/test_transactions';
export * from '../generated-wrappers/test_validator_wallet';

View File

@@ -1,110 +1,78 @@
import { blockchainTests, constants, expect, LogDecoder } from '@0x/contracts-test-utils';
import { BigNumber, OwnableRevertErrors } from '@0x/utils';
import { LogWithDecodedArgs } from 'ethereum-types';
import { blockchainTests } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import {
artifacts,
ExchangeContract,
ExchangeUpdatedProtocolFeeCollectorAddressEventArgs,
ExchangeUpdatedProtocolFeeMultiplierEventArgs,
} from '../src';
import { artifacts, TestProtocolFeesContract, TestProtocolFeesReceiverContract } from '../src';
blockchainTests.resets('MixinProtocolFees', env => {
let accounts: string[];
let exchange: ExchangeContract;
let logDecoder: LogDecoder;
let nonOwner: string;
let owner: string;
let protocolFeeCollector: string;
blockchainTests('Protocol Fee Payments', env => {
let testProtocolFees: TestProtocolFeesContract;
let testProtocolFeesReceiver: TestProtocolFeesReceiverContract;
// The protocolFeeMultiplier that will be used to test the update functions.
const protocolFeeMultiplier = new BigNumber(15000);
const DEFAULT_GAS_PRICE = new BigNumber(20000);
const DEFAULT_PROTOCOL_FEE_MULTIPLIER = new BigNumber(150);
const DEFAULT_PROTOCOL_FEE = DEFAULT_GAS_PRICE.times(DEFAULT_PROTOCOL_FEE_MULTIPLIER);
before(async () => {
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0];
nonOwner = accounts[1];
protocolFeeCollector = accounts[2];
// Update the from address of the txDefaults. This is the address that will become the owner.
env.txDefaults.from = owner;
// Deploy the exchange contract.
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
testProtocolFees = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
artifacts.TestProtocolFees,
env.provider,
env.txDefaults,
{},
);
testProtocolFeesReceiver = await TestProtocolFeesReceiverContract.deployFrom0xArtifactAsync(
artifacts.TestProtocolFeesReceiver,
env.provider,
env.txDefaults,
{},
new BigNumber(1337),
);
// Configure the log decoder
logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
});
blockchainTests.resets('updateProtocolFeeMultiplier', () => {
it('should revert if msg.sender != owner', async () => {
const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner);
// Ensure that the transaction reverts with the expected rich error.
const tx = exchange.updateProtocolFeeCollectorAddress.sendTransactionAsync(protocolFeeCollector, {
from: nonOwner,
});
return expect(tx).to.revertWith(expectedError);
});
it('should succeed and emit an UpdatedProtocolFeeMultiplier event if msg.sender == owner', async () => {
// Call the `updateProtocolFeeMultiplier()` function and get the receipt.
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await exchange.updateProtocolFeeMultiplier.sendTransactionAsync(protocolFeeMultiplier, {
from: owner,
}),
blockchainTests.resets('fillOrder Protocol Fees', () => {
it('should not pay protocol fee when there is no registered protocol fee collector', async () => {
await testProtocolFeesReceiver.testFillOrderProtocolFees.awaitTransactionSuccessAsync(
testProtocolFees.address,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
false,
{
gasPrice: DEFAULT_GAS_PRICE,
value: DEFAULT_PROTOCOL_FEE,
},
);
// Verify that the protocolFeeCollector address was actually updated to the correct address.
const updated = await exchange.protocolFeeMultiplier.callAsync();
expect(updated).bignumber.to.be.eq(protocolFeeMultiplier);
// Ensure that the correct `UpdatedProtocolFeeCollectorAddress` event was logged.
// tslint:disable:no-unnecessary-type-assertion
const updatedEvent = receipt.logs[0] as LogWithDecodedArgs<ExchangeUpdatedProtocolFeeMultiplierEventArgs>;
expect(updatedEvent.event).to.be.eq('UpdatedProtocolFeeMultiplier');
expect(updatedEvent.args.oldProtocolFeeMultiplier).bignumber.to.be.eq(constants.ZERO_AMOUNT);
expect(updatedEvent.args.updatedProtocolFeeMultiplier).bignumber.to.be.eq(protocolFeeMultiplier);
});
});
blockchainTests.resets('updateProtocolFeeCollectorAddress', () => {
it('should revert if msg.sender != owner', async () => {
const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner);
// Ensure that the transaction reverts with the expected rich error.
const tx = exchange.updateProtocolFeeCollectorAddress.sendTransactionAsync(protocolFeeCollector, {
from: nonOwner,
});
return expect(tx).to.revertWith(expectedError);
});
it('should succeed and emit an UpdatedProtocolFeeCollectorAddress event if msg.sender == owner', async () => {
// Call the `updateProtocolFeeCollectorAddress()` function and get the receipt.
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await exchange.updateProtocolFeeCollectorAddress.sendTransactionAsync(protocolFeeCollector, {
from: owner,
}),
it('should pay protocol fee in WETH when too little value is sent', async () => {
await testProtocolFeesReceiver.testFillOrderProtocolFees.awaitTransactionSuccessAsync(
testProtocolFees.address,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
true,
{
gasPrice: DEFAULT_GAS_PRICE,
value: DEFAULT_PROTOCOL_FEE.minus(new BigNumber(10)),
},
);
});
// Verify that the protocolFeeCollector address was actually updated to the correct address.
const updated = await exchange.protocolFeeCollector.callAsync();
expect(updated).to.be.eq(protocolFeeCollector);
it('should pay protocol fee in ETH when the correct value is sent', async () => {
await testProtocolFeesReceiver.testFillOrderProtocolFees.awaitTransactionSuccessAsync(
testProtocolFees.address,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
true,
{
gasPrice: DEFAULT_GAS_PRICE,
value: DEFAULT_PROTOCOL_FEE,
},
);
});
// Ensure that the correct `UpdatedProtocolFeeCollectorAddress` event was logged.
// tslint:disable:no-unnecessary-type-assertion
const updatedEvent = receipt.logs[0] as LogWithDecodedArgs<
ExchangeUpdatedProtocolFeeCollectorAddressEventArgs
>;
expect(updatedEvent.event).to.be.eq('UpdatedProtocolFeeCollectorAddress');
expect(updatedEvent.args.oldProtocolFeeCollector).to.be.eq(constants.NULL_ADDRESS);
expect(updatedEvent.args.updatedProtocolFeeCollector).to.be.eq(protocolFeeCollector);
it('should pay protocol fee in ETH when extra value is sent', async () => {
await testProtocolFeesReceiver.testFillOrderProtocolFees.awaitTransactionSuccessAsync(
testProtocolFees.address,
DEFAULT_PROTOCOL_FEE_MULTIPLIER,
true,
{
gasPrice: DEFAULT_GAS_PRICE,
value: DEFAULT_PROTOCOL_FEE.plus(new BigNumber(10)),
},
);
});
});
});

View File

@@ -0,0 +1,110 @@
import { blockchainTests, constants, expect, LogDecoder } from '@0x/contracts-test-utils';
import { BigNumber, OwnableRevertErrors } from '@0x/utils';
import { LogWithDecodedArgs } from 'ethereum-types';
import {
artifacts,
ExchangeContract,
ExchangeUpdatedProtocolFeeCollectorAddressEventArgs,
ExchangeUpdatedProtocolFeeMultiplierEventArgs,
} from '../src';
blockchainTests.resets('MixinProtocolFees', env => {
let accounts: string[];
let exchange: ExchangeContract;
let logDecoder: LogDecoder;
let nonOwner: string;
let owner: string;
let protocolFeeCollector: string;
// The protocolFeeMultiplier that will be used to test the update functions.
const protocolFeeMultiplier = new BigNumber(15000);
before(async () => {
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0];
nonOwner = accounts[1];
protocolFeeCollector = accounts[2];
// Update the from address of the txDefaults. This is the address that will become the owner.
env.txDefaults.from = owner;
// Deploy the exchange contract.
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
artifacts.Exchange,
env.provider,
env.txDefaults,
{},
new BigNumber(1337),
);
// Configure the log decoder
logDecoder = new LogDecoder(env.web3Wrapper, artifacts);
});
blockchainTests.resets('updateProtocolFeeMultiplier', () => {
it('should revert if msg.sender != owner', async () => {
const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner);
// Ensure that the transaction reverts with the expected rich error.
const tx = exchange.updateProtocolFeeCollectorAddress.sendTransactionAsync(protocolFeeCollector, {
from: nonOwner,
});
return expect(tx).to.revertWith(expectedError);
});
it('should succeed and emit an UpdatedProtocolFeeMultiplier event if msg.sender == owner', async () => {
// Call the `updateProtocolFeeMultiplier()` function and get the receipt.
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await exchange.updateProtocolFeeMultiplier.sendTransactionAsync(protocolFeeMultiplier, {
from: owner,
}),
);
// Verify that the protocolFeeCollector address was actually updated to the correct address.
const updated = await exchange.protocolFeeMultiplier.callAsync();
expect(updated).bignumber.to.be.eq(protocolFeeMultiplier);
// Ensure that the correct `UpdatedProtocolFeeCollectorAddress` event was logged.
// tslint:disable:no-unnecessary-type-assertion
const updatedEvent = receipt.logs[0] as LogWithDecodedArgs<ExchangeUpdatedProtocolFeeMultiplierEventArgs>;
expect(updatedEvent.event).to.be.eq('UpdatedProtocolFeeMultiplier');
expect(updatedEvent.args.oldProtocolFeeMultiplier).bignumber.to.be.eq(constants.ZERO_AMOUNT);
expect(updatedEvent.args.updatedProtocolFeeMultiplier).bignumber.to.be.eq(protocolFeeMultiplier);
});
});
blockchainTests.resets('updateProtocolFeeCollectorAddress', () => {
it('should revert if msg.sender != owner', async () => {
const expectedError = new OwnableRevertErrors.OnlyOwnerError(nonOwner, owner);
// Ensure that the transaction reverts with the expected rich error.
const tx = exchange.updateProtocolFeeCollectorAddress.sendTransactionAsync(protocolFeeCollector, {
from: nonOwner,
});
return expect(tx).to.revertWith(expectedError);
});
it('should succeed and emit an UpdatedProtocolFeeCollectorAddress event if msg.sender == owner', async () => {
// Call the `updateProtocolFeeCollectorAddress()` function and get the receipt.
const receipt = await logDecoder.getTxWithDecodedLogsAsync(
await exchange.updateProtocolFeeCollectorAddress.sendTransactionAsync(protocolFeeCollector, {
from: owner,
}),
);
// Verify that the protocolFeeCollector address was actually updated to the correct address.
const updated = await exchange.protocolFeeCollector.callAsync();
expect(updated).to.be.eq(protocolFeeCollector);
// Ensure that the correct `UpdatedProtocolFeeCollectorAddress` event was logged.
// tslint:disable:no-unnecessary-type-assertion
const updatedEvent = receipt.logs[0] as LogWithDecodedArgs<
ExchangeUpdatedProtocolFeeCollectorAddressEventArgs
>;
expect(updatedEvent.event).to.be.eq('UpdatedProtocolFeeCollectorAddress');
expect(updatedEvent.args.oldProtocolFeeCollector).to.be.eq(constants.NULL_ADDRESS);
expect(updatedEvent.args.updatedProtocolFeeCollector).to.be.eq(protocolFeeCollector);
});
});
});

View File

@@ -32,6 +32,8 @@
"generated-artifacts/TestAssetProxyDispatcher.json",
"generated-artifacts/TestExchangeInternals.json",
"generated-artifacts/TestLibExchangeRichErrorDecoder.json",
"generated-artifacts/TestProtocolFees.json",
"generated-artifacts/TestProtocolFeesReceiver.json",
"generated-artifacts/TestSignatureValidator.json",
"generated-artifacts/TestTransactions.json",
"generated-artifacts/TestValidatorWallet.json",