add registerAllowedSigner to NativeOrdersFeature (#195)

* add registerAllowedSigner to NativeOrdersFeature

* fix PR reference in changelog

* prettier

* add cancel pair orders functions for signers

* refactored cancelPairOrders logic, changed naming of signerRegistry to orderSigner registry everywhere, cleaned up tests

* updated changelog for protocol-utils, made batchCancelPairOrders functions more efficient

* clean up function documentation

* added additional tests for batchCancelPairsWithSigner functions, added additional checks for events being emitted correctly

* fix typos in function documentation

* fix typo in function documentation

* update docs

* update comments on cancel functions, small tweaks to docs
This commit is contained in:
Alex Kroeger
2021-04-26 14:32:35 -07:00
committed by GitHub
parent 2b120d0669
commit 9a17ce1383
21 changed files with 1180 additions and 134 deletions

View File

@@ -7,7 +7,15 @@ import {
randomAddress,
verifyEventsFromLogs,
} from '@0x/contracts-test-utils';
import { LimitOrder, LimitOrderFields, OrderStatus, RevertErrors, RfqOrder, RfqOrderFields } from '@0x/protocol-utils';
import {
LimitOrder,
LimitOrderFields,
OrderStatus,
RevertErrors,
RfqOrder,
RfqOrderFields,
SignatureType,
} from '@0x/protocol-utils';
import { AnyRevertError, BigNumber } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
@@ -25,7 +33,11 @@ import {
getRandomRfqOrder,
NativeOrdersTestEnvironment,
} from '../utils/orders';
import { TestMintableERC20TokenContract, TestRfqOriginRegistrationContract } from '../wrappers';
import {
TestMintableERC20TokenContract,
TestOrderSignerRegistryWithContractWalletContract,
TestRfqOriginRegistrationContract,
} from '../wrappers';
blockchainTests.resets('NativeOrdersFeature', env => {
const { NULL_ADDRESS, MAX_UINT256, NULL_BYTES32, ZERO_AMOUNT } = constants;
@@ -36,17 +48,28 @@ blockchainTests.resets('NativeOrdersFeature', env => {
let taker: string;
let notMaker: string;
let notTaker: string;
let contractWalletOwner: string;
let contractWalletSigner: string;
let zeroEx: IZeroExContract;
let verifyingContract: string;
let makerToken: TestMintableERC20TokenContract;
let takerToken: TestMintableERC20TokenContract;
let wethToken: TestMintableERC20TokenContract;
let testRfqOriginRegistration: TestRfqOriginRegistrationContract;
let contractWallet: TestOrderSignerRegistryWithContractWalletContract;
let testUtils: NativeOrdersTestEnvironment;
before(async () => {
let owner;
[owner, maker, taker, notMaker, notTaker] = await env.getAccountAddressesAsync();
[
owner,
maker,
taker,
notMaker,
notTaker,
contractWalletOwner,
contractWalletSigner,
] = await env.getAccountAddressesAsync();
[makerToken, takerToken, wethToken] = await Promise.all(
[...new Array(3)].map(async () =>
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
@@ -82,6 +105,21 @@ blockchainTests.resets('NativeOrdersFeature', env => {
env.txDefaults,
artifacts,
);
// contract wallet for signer delegation
contractWallet = await TestOrderSignerRegistryWithContractWalletContract.deployFrom0xArtifactAsync(
artifacts.TestOrderSignerRegistryWithContractWallet,
env.provider,
{
from: contractWalletOwner,
},
artifacts,
zeroEx.address,
);
await contractWallet
.approveERC20(makerToken.address, zeroEx.address, MAX_UINT256)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
testUtils = new NativeOrdersTestEnvironment(
maker,
taker,
@@ -1569,4 +1607,432 @@ blockchainTests.resets('NativeOrdersFeature', env => {
}
});
});
describe('registerAllowedSigner()', () => {
it('fires appropriate events', async () => {
const receiptAllow = await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
verifyEventsFromLogs(
receiptAllow.logs,
[
{
maker: contractWallet.address,
signer: contractWalletSigner,
allowed: true,
},
],
IZeroExEvents.OrderSignerRegistered,
);
// then disallow signer
const receiptDisallow = await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, false)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
verifyEventsFromLogs(
receiptDisallow.logs,
[
{
maker: contractWallet.address,
signer: contractWalletSigner,
allowed: false,
},
],
IZeroExEvents.OrderSignerRegistered,
);
});
it('allows for fills on orders signed by a approved signer', async () => {
const order = getTestRfqOrder({ maker: contractWallet.address });
const sig = await order.getSignatureWithProviderAsync(
env.provider,
SignatureType.EthSign,
contractWalletSigner,
);
// covers taker
await testUtils.prepareBalancesForOrdersAsync([order]);
// need to provide contract wallet with a balance
await makerToken.mint(contractWallet.address, order.makerAmount).awaitTransactionSuccessAsync();
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
await zeroEx.fillRfqOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker });
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
assertOrderInfoEquals(info, {
status: OrderStatus.Filled,
orderHash: order.getHash(),
takerTokenFilledAmount: order.takerAmount,
});
});
it('disallows fills if the signer is revoked', async () => {
const order = getTestRfqOrder({ maker: contractWallet.address });
const sig = await order.getSignatureWithProviderAsync(
env.provider,
SignatureType.EthSign,
contractWalletSigner,
);
// covers taker
await testUtils.prepareBalancesForOrdersAsync([order]);
// need to provide contract wallet with a balance
await makerToken.mint(contractWallet.address, order.makerAmount).awaitTransactionSuccessAsync();
// first allow signer
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
// then disallow signer
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, false)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
const tx = zeroEx.fillRfqOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker });
return expect(tx).to.revertWith(
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(
order.getHash(),
contractWalletSigner,
order.maker,
),
);
});
it(`doesn't allow fills with an unapproved signer`, async () => {
const order = getTestRfqOrder({ maker: contractWallet.address });
const sig = await order.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, maker);
// covers taker
await testUtils.prepareBalancesForOrdersAsync([order]);
// need to provide contract wallet with a balance
await makerToken.mint(contractWallet.address, order.makerAmount).awaitTransactionSuccessAsync();
const tx = zeroEx.fillRfqOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker });
return expect(tx).to.revertWith(
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), maker, order.maker),
);
});
it(`allows an approved signer to cancel an RFQ order`, async () => {
const order = getTestRfqOrder({ maker: contractWallet.address });
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
const receipt = await zeroEx
.cancelRfqOrder(order)
.awaitTransactionSuccessAsync({ from: contractWalletSigner });
verifyEventsFromLogs(
receipt.logs,
[{ maker: contractWallet.address, orderHash: order.getHash() }],
IZeroExEvents.OrderCancelled,
);
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
assertOrderInfoEquals(info, {
status: OrderStatus.Cancelled,
orderHash: order.getHash(),
takerTokenFilledAmount: new BigNumber(0),
});
});
it(`allows an approved signer to cancel a limit order`, async () => {
const order = getTestLimitOrder({ maker: contractWallet.address });
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
const receipt = await zeroEx
.cancelLimitOrder(order)
.awaitTransactionSuccessAsync({ from: contractWalletSigner });
verifyEventsFromLogs(
receipt.logs,
[{ maker: contractWallet.address, orderHash: order.getHash() }],
IZeroExEvents.OrderCancelled,
);
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
assertOrderInfoEquals(info, {
status: OrderStatus.Cancelled,
orderHash: order.getHash(),
takerTokenFilledAmount: new BigNumber(0),
});
});
it(`doesn't allow an unapproved signer to cancel an RFQ order`, async () => {
const order = getTestRfqOrder({ maker: contractWallet.address });
const tx = zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
return expect(tx).to.revertWith(
new RevertErrors.NativeOrders.OnlyOrderMakerAllowed(order.getHash(), maker, order.maker),
);
});
it(`doesn't allow an unapproved signer to cancel a limit order`, async () => {
const order = getTestLimitOrder({ maker: contractWallet.address });
const tx = zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
return expect(tx).to.revertWith(
new RevertErrors.NativeOrders.OnlyOrderMakerAllowed(order.getHash(), maker, order.maker),
);
});
it(`allows a signer to cancel pair RFQ orders`, async () => {
const order = getTestRfqOrder({ maker: contractWallet.address, salt: new BigNumber(1) });
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
// Cancel salts <= the order's
const minValidSalt = order.salt.plus(1);
const receipt = await zeroEx
.cancelPairRfqOrdersWithSigner(
contractWallet.address,
makerToken.address,
takerToken.address,
minValidSalt,
)
.awaitTransactionSuccessAsync({ from: contractWalletSigner });
verifyEventsFromLogs(
receipt.logs,
[
{
maker: contractWallet.address,
makerToken: makerToken.address,
takerToken: takerToken.address,
minValidSalt,
},
],
IZeroExEvents.PairCancelledRfqOrders,
);
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
assertOrderInfoEquals(info, {
status: OrderStatus.Cancelled,
orderHash: order.getHash(),
takerTokenFilledAmount: new BigNumber(0),
});
});
it(`doesn't allow an unapproved signer to cancel pair RFQ orders`, async () => {
const minValidSalt = new BigNumber(2);
const tx = zeroEx
.cancelPairRfqOrdersWithSigner(
contractWallet.address,
makerToken.address,
takerToken.address,
minValidSalt,
)
.awaitTransactionSuccessAsync({ from: maker });
return expect(tx).to.revertWith(
new RevertErrors.NativeOrders.InvalidSignerError(contractWallet.address, maker),
);
});
it(`allows a signer to cancel pair limit orders`, async () => {
const order = getTestLimitOrder({ maker: contractWallet.address, salt: new BigNumber(1) });
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
// Cancel salts <= the order's
const minValidSalt = order.salt.plus(1);
const receipt = await zeroEx
.cancelPairLimitOrdersWithSigner(
contractWallet.address,
makerToken.address,
takerToken.address,
minValidSalt,
)
.awaitTransactionSuccessAsync({ from: contractWalletSigner });
verifyEventsFromLogs(
receipt.logs,
[
{
maker: contractWallet.address,
makerToken: makerToken.address,
takerToken: takerToken.address,
minValidSalt,
},
],
IZeroExEvents.PairCancelledLimitOrders,
);
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
assertOrderInfoEquals(info, {
status: OrderStatus.Cancelled,
orderHash: order.getHash(),
takerTokenFilledAmount: new BigNumber(0),
});
});
it(`doesn't allow an unapproved signer to cancel pair limit orders`, async () => {
const minValidSalt = new BigNumber(2);
const tx = zeroEx
.cancelPairLimitOrdersWithSigner(
contractWallet.address,
makerToken.address,
takerToken.address,
minValidSalt,
)
.awaitTransactionSuccessAsync({ from: maker });
return expect(tx).to.revertWith(
new RevertErrors.NativeOrders.InvalidSignerError(contractWallet.address, maker),
);
});
it(`allows a signer to cancel multiple RFQ order pairs`, async () => {
const orders = [
getTestRfqOrder({ maker: contractWallet.address, salt: new BigNumber(1) }),
// Flip the tokens for the other order.
getTestRfqOrder({
makerToken: takerToken.address,
takerToken: makerToken.address,
maker: contractWallet.address,
salt: new BigNumber(1),
}),
];
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
const minValidSalt = new BigNumber(2);
const receipt = await zeroEx
.batchCancelPairRfqOrdersWithSigner(
contractWallet.address,
[makerToken.address, takerToken.address],
[takerToken.address, makerToken.address],
[minValidSalt, minValidSalt],
)
.awaitTransactionSuccessAsync({ from: contractWalletSigner });
verifyEventsFromLogs(
receipt.logs,
[
{
maker: contractWallet.address,
makerToken: makerToken.address,
takerToken: takerToken.address,
minValidSalt,
},
{
maker: contractWallet.address,
makerToken: takerToken.address,
takerToken: makerToken.address,
minValidSalt,
},
],
IZeroExEvents.PairCancelledRfqOrders,
);
const statuses = (await Promise.all(orders.map(o => zeroEx.getRfqOrderInfo(o).callAsync()))).map(
oi => oi.status,
);
expect(statuses).to.deep.eq([OrderStatus.Cancelled, OrderStatus.Cancelled]);
});
it(`doesn't allow an unapproved signer to batch cancel pair rfq orders`, async () => {
const minValidSalt = new BigNumber(2);
const tx = zeroEx
.batchCancelPairRfqOrdersWithSigner(
contractWallet.address,
[makerToken.address, takerToken.address],
[takerToken.address, makerToken.address],
[minValidSalt, minValidSalt],
)
.awaitTransactionSuccessAsync({ from: maker });
return expect(tx).to.revertWith(
new RevertErrors.NativeOrders.InvalidSignerError(contractWallet.address, maker),
);
});
it(`allows a signer to cancel multiple limit order pairs`, async () => {
const orders = [
getTestLimitOrder({ maker: contractWallet.address, salt: new BigNumber(1) }),
// Flip the tokens for the other order.
getTestLimitOrder({
makerToken: takerToken.address,
takerToken: makerToken.address,
maker: contractWallet.address,
salt: new BigNumber(1),
}),
];
await contractWallet
.registerAllowedOrderSigner(contractWalletSigner, true)
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
const minValidSalt = new BigNumber(2);
const receipt = await zeroEx
.batchCancelPairLimitOrdersWithSigner(
contractWallet.address,
[makerToken.address, takerToken.address],
[takerToken.address, makerToken.address],
[minValidSalt, minValidSalt],
)
.awaitTransactionSuccessAsync({ from: contractWalletSigner });
verifyEventsFromLogs(
receipt.logs,
[
{
maker: contractWallet.address,
makerToken: makerToken.address,
takerToken: takerToken.address,
minValidSalt,
},
{
maker: contractWallet.address,
makerToken: takerToken.address,
takerToken: makerToken.address,
minValidSalt,
},
],
IZeroExEvents.PairCancelledLimitOrders,
);
const statuses = (await Promise.all(orders.map(o => zeroEx.getLimitOrderInfo(o).callAsync()))).map(
oi => oi.status,
);
expect(statuses).to.deep.eq([OrderStatus.Cancelled, OrderStatus.Cancelled]);
});
it(`doesn't allow an unapproved signer to batch cancel pair limit orders`, async () => {
const minValidSalt = new BigNumber(2);
const tx = zeroEx
.batchCancelPairLimitOrdersWithSigner(
contractWallet.address,
[makerToken.address, takerToken.address],
[takerToken.address, makerToken.address],
[minValidSalt, minValidSalt],
)
.awaitTransactionSuccessAsync({ from: maker });
return expect(tx).to.revertWith(
new RevertErrors.NativeOrders.InvalidSignerError(contractWallet.address, maker),
);
});
});
});