EP Native Orders (#27)

* `@0x/contracts-zero-ex`: add limit orders feature
`@0x/contracts-utils`: add `uint128` functions to `LibSafeMathV06`

* `@0x/contract-addresses`: Update ganache snapshot addresses

* `@0x/contracts-zero-ex`: Mask EIP712 struct hash values.

* `@0x/contracts-zero-ex`: Add more limit order tests

* `@0x/contracts-zero-ex`: Fix typos

* `@0x/contracts-zero-ex`: Compute fee collector address after protocol fee zero check.

* `@0x/contracts-zero-ex`: Remove WETH payment logic from fee collector fixin

* `@0x/contracts-zero-ex`: Convert all ETH to WETH in `FeeCollector`.

* `@0x/contracts-zero-ex`: Address review feedback

* `@0x/contracts-zero-ex`: Export more utils

* `@0x/contracts-zero-ex`: Rename `LimitOrdersFeatures`, `LibLimitOrders`, etc. into `*NativeOrders*`.
`@0x/contracts-zero-ex`: Emit `protocolFeePaid` in native order fill events.
`@0x/contracts-zero-ex`: Refactor to get around stack limits.
`@0x/contracts-zero-ex`: Use different storage mappings for RFQ and limit order pair cancellations.

* `@0x/contracts-zero-ex`: Add `getProtocolFeeMultiplier()` and `transferProtocolFeesForPools()` to `NativeOrdersFeature`.

* `@0x/contracts-zero-ex`: Fix broken tests

* update orders docs

* `@0x/contracts-zero-ex`: Add more tests to `NativeOrdersFeature`

* rebuild after rebase

* `@0x/contract-addresses`: Fix changelog booboo

* `@0x/contracts-zero-ex`: Add method selectors output to generated artifacts

* `@0x/contracts-zero-ex`: Add maker address to order cancel events.
`@0x/contracts-zreo-ex`: Remove `UpTo` suffix from order pair cancellation functions.
`@0x/contracts-zreo-ex`: Address misc review comments.

* `@0x/contracts-zero-ex`: More SafeMath in native orders

* update orders docs

Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
Lawrence Forman
2020-11-17 15:39:40 -05:00
committed by GitHub
parent 4f82543bdf
commit 561b60a24d
40 changed files with 4275 additions and 348 deletions

View File

@@ -1,25 +1,20 @@
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors, BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import { blockchainTests, expect } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors, BigNumber, hexUtils } from '@0x/utils';
import { artifacts } from './artifacts';
import { FeeCollectorContract, TestProtocolFeesContract, TestStakingContract, TestWethContract } from './wrappers';
import { FeeCollectorContract, TestFixinProtocolFeesContract, TestStakingContract, TestWethContract } from './wrappers';
blockchainTests.resets('ProtocolFees', env => {
let payer: string;
const FEE_MULTIPLIER = 70e3;
let taker: string;
let unauthorized: string;
let protocolFees: TestProtocolFeesContract;
let protocolFees: TestFixinProtocolFeesContract;
let staking: TestStakingContract;
let weth: TestWethContract;
let singleFeeAmount: BigNumber;
before(async () => {
[payer, unauthorized] = await env.getAccountAddressesAsync();
protocolFees = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
artifacts.TestProtocolFees,
env.provider,
env.txDefaults,
artifacts,
);
[taker, unauthorized] = await env.getAccountAddressesAsync();
weth = await TestWethContract.deployFrom0xArtifactAsync(
artifacts.TestWeth,
env.provider,
@@ -33,30 +28,26 @@ blockchainTests.resets('ProtocolFees', env => {
artifacts,
weth.address,
);
await weth.mint(payer, constants.ONE_ETHER).awaitTransactionSuccessAsync();
await weth.approve(protocolFees.address, constants.ONE_ETHER).awaitTransactionSuccessAsync({ from: payer });
protocolFees = await TestFixinProtocolFeesContract.deployFrom0xArtifactAsync(
artifacts.TestFixinProtocolFees,
env.provider,
{ ...env.txDefaults, from: taker },
artifacts,
weth.address,
staking.address,
FEE_MULTIPLIER,
);
singleFeeAmount = await protocolFees.getSingleProtocolFee().callAsync();
await weth.mint(taker, singleFeeAmount).awaitTransactionSuccessAsync();
await weth.approve(protocolFees.address, singleFeeAmount).awaitTransactionSuccessAsync({ from: taker });
});
async function collectAsync(
poolId: string,
amount: BigNumber,
etherValue: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> {
return protocolFees
.collectProtocolFee(poolId, amount, weth.address)
.awaitTransactionSuccessAsync({ from: payer, value: etherValue });
}
async function transferFeesAsync(poolId: string): Promise<TransactionReceiptWithDecodedLogs> {
return protocolFees.transferFeesForPool(poolId, staking.address, weth.address).awaitTransactionSuccessAsync();
}
describe('FeeCollector', () => {
it('should disallow unauthorized initialization', async () => {
const pool = hexUtils.random();
await collectAsync(pool, constants.ONE_ETHER, constants.ZERO_AMOUNT);
await transferFeesAsync(pool);
await protocolFees.collectProtocolFee(pool).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool).awaitTransactionSuccessAsync();
const feeCollector = new FeeCollectorContract(
await protocolFees.getFeeCollector(pool).callAsync(),
@@ -74,91 +65,70 @@ blockchainTests.resets('ProtocolFees', env => {
describe('_collectProtocolFee()', () => {
const pool1 = hexUtils.random();
const pool2 = hexUtils.random();
let feeCollector1Address: string;
let feeCollector2Address: string;
it('should revert if WETH transfer fails', async () => {
const tooMuch = constants.ONE_ETHER.plus(1);
const tx = collectAsync(pool1, constants.ONE_ETHER.plus(1), constants.ZERO_AMOUNT);
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
weth.address,
payer,
undefined,
tooMuch,
undefined,
),
);
before(async () => {
feeCollector1Address = await protocolFees.getFeeCollector(pool1).callAsync();
feeCollector2Address = await protocolFees.getFeeCollector(pool2).callAsync();
});
it('should revert if insufficient ETH transferred', async () => {
const tooLittle = constants.ONE_ETHER.minus(1);
const tx = collectAsync(pool1, constants.ONE_ETHER, tooLittle);
const tooLittle = singleFeeAmount.minus(1);
const tx = protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: tooLittle });
return expect(tx).to.revertWith('FixinProtocolFees/ETHER_TRANSFER_FALIED');
});
it('should accept WETH fee', async () => {
const beforeWETH = await weth.balanceOf(payer).callAsync();
await collectAsync(pool1, constants.ONE_ETHER, constants.ZERO_AMOUNT);
const afterWETH = await weth.balanceOf(payer).callAsync();
return expect(beforeWETH.minus(afterWETH)).to.bignumber.eq(constants.ONE_ETHER);
});
it('should accept ETH fee', async () => {
const beforeWETH = await weth.balanceOf(payer).callAsync();
const beforeETH = await env.web3Wrapper.getBalanceInWeiAsync(payer);
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER);
const afterWETH = await weth.balanceOf(payer).callAsync();
const afterETH = await env.web3Wrapper.getBalanceInWeiAsync(payer);
const beforeETH = await env.web3Wrapper.getBalanceInWeiAsync(taker);
await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
const afterETH = await env.web3Wrapper.getBalanceInWeiAsync(taker);
// We check for greater than 1 ether spent to allow for spending on gas.
await expect(beforeETH.minus(afterETH)).to.bignumber.gt(constants.ONE_ETHER);
return expect(beforeWETH).to.bignumber.eq(afterWETH);
});
// We check for greater than fee spent to allow for spending on gas.
await expect(beforeETH.minus(afterETH)).to.bignumber.gt(singleFeeAmount);
it('should transfer both ETH and WETH', async () => {
await collectAsync(pool1, constants.ONE_ETHER, constants.ZERO_AMOUNT);
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER);
await transferFeesAsync(pool1);
const balanceWETH = await weth.balanceOf(staking.address).callAsync();
// We leave 1 wei behind of both ETH and WETH.
return expect(balanceWETH).to.bignumber.eq(constants.ONE_ETHER.times(2).minus(2));
await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector1Address)).to.bignumber.eq(
singleFeeAmount,
);
});
it('should accept ETH after first transfer', async () => {
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER);
await transferFeesAsync(pool1);
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER);
await transferFeesAsync(pool1);
await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
const balanceWETH = await weth.balanceOf(staking.address).callAsync();
// We leave 1 wei behind of both ETH and WETH
return expect(balanceWETH).to.bignumber.eq(constants.ONE_ETHER.times(2).minus(2));
// We leave 1 wei of WETH behind.
await expect(balanceWETH).to.bignumber.eq(singleFeeAmount.times(2).minus(1));
await expect(await weth.balanceOf(feeCollector1Address).callAsync()).to.bignumber.equal(1);
// And no ETH.
await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector1Address)).to.bignumber.eq(0);
});
it('should attribute fees correctly', async () => {
const pool1Amount = new BigNumber(12345);
const pool2Amount = new BigNumber(45678);
await collectAsync(pool1, pool1Amount, pool1Amount); // ETH
await transferFeesAsync(pool1);
await collectAsync(pool2, pool2Amount, constants.ZERO_AMOUNT); // WETH
await transferFeesAsync(pool2);
await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
await protocolFees.collectProtocolFee(pool2).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool2).awaitTransactionSuccessAsync();
const pool1Balance = await staking.balanceForPool(pool1).callAsync();
const pool2Balance = await staking.balanceForPool(pool2).callAsync();
const balanceWETH = await weth.balanceOf(staking.address).callAsync();
await expect(balanceWETH).to.bignumber.equal(pool1Balance.plus(pool2Balance));
await expect(balanceWETH).to.bignumber.equal(singleFeeAmount.times(2).minus(2));
// We leave 1 wei behind of both ETH and WETH.
await expect(pool1Balance).to.bignumber.equal(pool1Amount.minus(2));
// Here we paid in WETH, so there's just 1 wei of WETH held back.
return expect(pool2Balance).to.bignumber.equal(pool2Amount.minus(1));
// We leave 1 wei of WETH behind.
await expect(pool1Balance).to.bignumber.equal(singleFeeAmount.minus(1));
await expect(pool2Balance).to.bignumber.equal(singleFeeAmount.minus(1));
await expect(await weth.balanceOf(feeCollector1Address).callAsync()).to.bignumber.equal(1);
await expect(await weth.balanceOf(feeCollector2Address).callAsync()).to.bignumber.equal(1);
await expect(pool2Balance).to.bignumber.equal(singleFeeAmount.minus(1));
// And no ETH.
await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector1Address)).to.bignumber.eq(0);
await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector2Address)).to.bignumber.eq(0);
});
});
});